From 5de4fe2d263b5fd59899edc6ad6dea116415ad4a Mon Sep 17 00:00:00 2001 From: hylin Date: Fri, 18 Sep 2026 02:24:48 +0800 Subject: [PATCH] fix: send the content_type partition_via_api accepts `content_type` has been the second positional parameter of `partition_via_api()` since the helper was introduced in #518, and the docstring describes it as "a string defining the file content in MIME type". It is never read: both `shared.Files(...)` call sites pass only `content` and `file_name`, so the value is silently discarded and the server falls back to sniffing the file name. `shared.Files` does have a `content_type` field, so nothing was missing on the SDK side -- only the hand-off. Populate it when the caller supplies a value: partition_via_api(filename=..., content_type="text/markdown") # before: files.content_type is None # after: files.content_type == "text/markdown" The field is left unset when no `content_type` is given, so the SDK keeps inferring the type from the file name exactly as before. The parameter is not moved, so existing positional calls are unaffected. --- CHANGELOG.md | 2 ++ test_unstructured/partition/test_api.py | 44 +++++++++++++++++++++++++ unstructured/partition/api.py | 7 +++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70e87842ec..9b623ad57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- **`partition_via_api()` now sends the `content_type` it accepts.** The parameter has been part of the signature since the helper was introduced and is documented as "a string defining the file content in MIME type", but it was never read: the `shared.Files` object was built from `content` and `file_name` only, so the caller's MIME type never reached the API and the server fell back to sniffing the file name. `shared.Files` has a `content_type` field, which is now populated when the caller supplies one; omitting it leaves the field unset so the existing inference is unchanged. + - **Recognize HTML and Markdown loose-list items.** A list item containing a single ordinary text block (such as `
  • text

  • `) now produces a `ListItem`, preserving inline annotations and list depth. Multi-paragraph items and specialized blocks retain their existing behavior. Resolves #3499. - **`partition_doc()` and `partition_ppt()` no longer fail on a document whose name contains multi-byte characters.** `convert_office_doc()` decoded `soffice` stdout and stderr with a strict UTF-8 decode purely to log them and to check whether stdout was empty. LibreOffice echoes the input path using the console encoding, which on Windows is the locale codepage, so a document whose name or path contains multi-byte characters raised `UnicodeDecodeError` and aborted a conversion that would otherwise have succeeded. All three decode sites now go through one helper using `errors="backslashreplace"`, which keeps the message pure ASCII -- readable, still loggable by a handler using the locale codepage, and showing the offending bytes. Resolves #3652. diff --git a/test_unstructured/partition/test_api.py b/test_unstructured/partition/test_api.py index 1206c259f0..262691e0f9 100644 --- a/test_unstructured/partition/test_api.py +++ b/test_unstructured/partition/test_api.py @@ -52,6 +52,50 @@ def test_partition_via_api_with_filename_correctly_calls_sdk( assert elements[0].metadata.filetype == "message/rfc822" +def test_partition_via_api_sends_an_explicit_content_type_for_a_filename( + request: FixtureRequest, +): + """`content_type` is a documented parameter and `shared.Files` has the field for it.""" + partition_mock_ = method_mock( + request, General, "partition", return_value=FakeResponse(status_code=200) + ) + + partition_via_api( + filename=example_doc_path("eml/fake-email.eml"), content_type="message/rfc822" + ) + + sent = partition_mock_.call_args_list[0].kwargs["request"] + assert sent.partition_parameters.files.content_type == "message/rfc822" + + +def test_partition_via_api_sends_an_explicit_content_type_for_a_file(request: FixtureRequest): + partition_mock_ = method_mock( + request, General, "partition", return_value=FakeResponse(status_code=200) + ) + + with open(example_doc_path("eml/fake-email.eml"), "rb") as f: + partition_via_api( + file=f.read(), + metadata_filename=example_doc_path("eml/fake-email.eml"), + content_type="message/rfc822", + ) + + sent = partition_mock_.call_args_list[0].kwargs["request"] + assert sent.partition_parameters.files.content_type == "message/rfc822" + + +def test_partition_via_api_leaves_content_type_unset_when_not_given(request: FixtureRequest): + """Omitting it must keep the SDK inferring the type from the file name, as before.""" + partition_mock_ = method_mock( + request, General, "partition", return_value=FakeResponse(status_code=200) + ) + + partition_via_api(filename=example_doc_path("eml/fake-email.eml")) + + sent = partition_mock_.call_args_list[0].kwargs["request"] + assert sent.partition_parameters.files.content_type is None + + def test_partition_via_api_with_file_correctly_calls_sdk( request: FixtureRequest, expected_call_: list[Any] ): diff --git a/unstructured/partition/api.py b/unstructured/partition/api.py index 2583c74bf6..2809b3cffb 100644 --- a/unstructured/partition/api.py +++ b/unstructured/partition/api.py @@ -98,11 +98,16 @@ def partition_via_api( base_url = api_url[:-19] if "/general/v0/general" in api_url else api_url sdk = UnstructuredClient(api_key_auth=api_key, server_url=base_url) + # -- `shared.Files` carries a `content_type` field; leave it unset when the caller did not + # -- specify one so the SDK keeps inferring the MIME type from the file name as before. + content_type_kwargs = {"content_type": content_type} if content_type is not None else {} + if filename is not None: with open(filename, "rb") as f: files = shared.Files( content=f.read(), file_name=filename, + **content_type_kwargs, ) elif file is not None: @@ -111,7 +116,7 @@ def partition_via_api( "If file is specified in partition_via_api, " "metadata_filename must be specified as well.", ) - files = shared.Files(content=file, file_name=metadata_filename) + files = shared.Files(content=file, file_name=metadata_filename, **content_type_kwargs) req = operations.PartitionRequest( partition_parameters=shared.PartitionParameters(files=files, **request_kwargs)