From ccfb47d2c5fbf452fabcee6465c1aa3e63f9feed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=93=9D=E7=A0=B4=E7=A2=8E=E5=8D=8A=E5=9C=86/=E7=A0=B4?= =?UTF-8?q?=E7=A2=8E=E8=93=9D=E8=B0=83?= Date: Thu, 20 Aug 2026 13:23:03 +0000 Subject: [PATCH] feat: support ODT document extraction (#39973) Co-authored-by: Crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Asuka Minato --- api/constants/__init__.py | 2 + .../nodes/test_document_extractor_node.py | 42 +++++++++++++++++++ api/tests/unit_tests/test_constants.py | 25 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 api/tests/unit_tests/test_constants.py diff --git a/api/constants/__init__.py b/api/constants/__init__.py index 8698fb855de..095cfbb8610 100644 --- a/api/constants/__init__.py +++ b/api/constants/__init__.py @@ -36,6 +36,7 @@ _UNSTRUCTURED_DOCUMENT_EXTENSION_BASE: frozenset[str] = frozenset( "pptx", "xml", "epub", + "odt", ) ) _DEFAULT_DOCUMENT_EXTENSION_BASE: frozenset[str] = frozenset( @@ -53,6 +54,7 @@ _DEFAULT_DOCUMENT_EXTENSION_BASE: frozenset[str] = frozenset( "csv", "vtt", "properties", + "odt", ) ) diff --git a/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py b/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py index d8d3c1ecc2e..ee094732b65 100644 --- a/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/test_document_extractor_node.py @@ -441,6 +441,48 @@ def test_extract_text_from_excel_numeric_type_column(mock_excel_file): assert expected_manual == result +@pytest.mark.parametrize( + ("extension", "mime_type", "route_label"), + [ + (".odt", "text/plain", "extension"), + (None, "application/vnd.oasis.opendocument.text", "mime_type"), + ], +) +def test_extract_text_from_file_routes_odt_inputs_to_graphon_odt_extractor( + document_extractor_node, + extension, + mime_type, + route_label, +): + file = Mock(spec=File) + file.extension = extension + file.mime_type = mime_type + + def fake_partition(file_content, *, suffix, unstructured_api_config, load_local_partition, render_element): + assert file_content == b"odt content" + assert suffix == ".odt" + assert unstructured_api_config == document_extractor_node._unstructured_api_config + assert load_local_partition.__name__ == "_load_partition_odt" + assert render_element is not None + return f"extracted through {route_label}" + + with ( + patch( + "graphon.nodes.document_extractor.node._download_file_content", + return_value=b"odt content", + ) as mock_download, + patch("graphon.nodes.document_extractor.node._partition_unstructured_file", side_effect=fake_partition), + ): + text = _extract_text_from_file( + document_extractor_node.http_client, + file, + unstructured_api_config=document_extractor_node._unstructured_api_config, + ) + + assert text == f"extracted through {route_label}" + mock_download.assert_called_once_with(document_extractor_node.http_client, file) + + @pytest.mark.parametrize( ("extension", "mime_type"), [ diff --git a/api/tests/unit_tests/test_constants.py b/api/tests/unit_tests/test_constants.py new file mode 100644 index 00000000000..e40744a894a --- /dev/null +++ b/api/tests/unit_tests/test_constants.py @@ -0,0 +1,25 @@ +import importlib + +import pytest + +import constants +from configs import dify_config + + +@pytest.mark.parametrize("etl_type", ["SelfHosted", "Unstructured"]) +def test_document_extensions_include_odt_for_document_etl_modes(monkeypatch: pytest.MonkeyPatch, etl_type: str) -> None: + original_etl_type = dify_config.ETL_TYPE + original_unstructured_api_url = dify_config.UNSTRUCTURED_API_URL + + try: + monkeypatch.setattr(dify_config, "ETL_TYPE", etl_type) + monkeypatch.setattr(dify_config, "UNSTRUCTURED_API_URL", None) + + reloaded_constants = importlib.reload(constants) + + assert "odt" in reloaded_constants.DOCUMENT_EXTENSIONS + assert "ODT" in reloaded_constants.DOCUMENT_EXTENSIONS + finally: + monkeypatch.setattr(dify_config, "ETL_TYPE", original_etl_type) + monkeypatch.setattr(dify_config, "UNSTRUCTURED_API_URL", original_unstructured_api_url) + importlib.reload(constants)