From 76cb439b50d4e95091d541033930b7cbaa6f10fa Mon Sep 17 00:00:00 2001 From: dex0shubham Date: Wed, 15 Jul 2026 13:52:01 +0100 Subject: [PATCH 1/2] fix(split): serialize split_class as a JSON string in multipart form data --- src/landingai_ade/_client.py | 9 +++++++-- tests/test_split_params.py | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 tests/test_split_params.py diff --git a/src/landingai_ade/_client.py b/src/landingai_ade/_client.py index b94c19d..be9f012 100644 --- a/src/landingai_ade/_client.py +++ b/src/landingai_ade/_client.py @@ -1,6 +1,7 @@ from __future__ import annotations import os +import json import importlib.metadata from typing import TYPE_CHECKING, Any, Dict, Union, Mapping, Iterable, Optional, cast from pathlib import Path @@ -846,7 +847,9 @@ def split( original_markdown_url = markdown_url body = deepcopy_with_paths( { - "split_class": split_class, + # The API expects split_class as a single JSON string form field, + # not flattened multipart fields (https://github.com/landing-ai/ade-python/issues/76). + "split_class": json.dumps(list(split_class)), "markdown": markdown, "markdown_url": markdown_url, "model": model, @@ -1563,7 +1566,9 @@ async def split( original_markdown_url = markdown_url body = deepcopy_with_paths( { - "split_class": split_class, + # The API expects split_class as a single JSON string form field, + # not flattened multipart fields (https://github.com/landing-ai/ade-python/issues/76). + "split_class": json.dumps(list(split_class)), "markdown": markdown, "markdown_url": markdown_url, "model": model, diff --git a/tests/test_split_params.py b/tests/test_split_params.py new file mode 100644 index 0000000..315397f --- /dev/null +++ b/tests/test_split_params.py @@ -0,0 +1,37 @@ +# Regression tests for https://github.com/landing-ai/ade-python/issues/76: +# split_class must be sent as a single JSON string form field, not flattened +# into split_class[0][name]-style multipart fields. +from __future__ import annotations + +import json +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from landingai_ade import LandingAIADE, AsyncLandingAIADE + +SPLIT_CLASS = [ + {"name": "Bank Statement", "description": "Bank account activity summary."}, + {"name": "Pay Stub", "description": "Employee earnings detail.", "identifier": "Pay Stub Date"}, +] + + +def test_sync_split_class_sent_as_json_string() -> None: + with LandingAIADE(apikey="test-key", base_url="http://localhost") as client: + with patch.object(client, "post", return_value=MagicMock()) as post: + client.split(split_class=SPLIT_CLASS, markdown="some markdown") + + body = post.call_args.kwargs["body"] + assert body["split_class"] == json.dumps(SPLIT_CLASS) + assert json.loads(body["split_class"]) == SPLIT_CLASS + + +@pytest.mark.asyncio +async def test_async_split_class_sent_as_json_string() -> None: + async with AsyncLandingAIADE(apikey="test-key", base_url="http://localhost") as client: + with patch.object(client, "post", new_callable=AsyncMock, return_value=MagicMock()) as post: + await client.split(split_class=SPLIT_CLASS, markdown="some markdown") + + body = post.call_args.kwargs["body"] + assert body["split_class"] == json.dumps(SPLIT_CLASS) + assert json.loads(body["split_class"]) == SPLIT_CLASS From 7a773b91e7857f8f0bad796ad3670718a4c53a98 Mon Sep 17 00:00:00 2001 From: dex0shubham Date: Thu, 16 Jul 2026 14:23:36 +0100 Subject: [PATCH 2/2] fix(split): preserve already-JSON-encoded split_class strings --- src/landingai_ade/_client.py | 8 ++++---- .../types/client_split_params.py | 2 +- tests/test_split_params.py | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/landingai_ade/_client.py b/src/landingai_ade/_client.py index be9f012..f3e5d4e 100644 --- a/src/landingai_ade/_client.py +++ b/src/landingai_ade/_client.py @@ -797,7 +797,7 @@ def section( def split( self, *, - split_class: Iterable[client_split_params.SplitClass], + split_class: Union[str, Iterable[client_split_params.SplitClass]], markdown: Union[FileTypes, str, None] | Omit = omit, markdown_url: Optional[str] | Omit = omit, model: Optional[str] | Omit = omit, @@ -849,7 +849,7 @@ def split( { # The API expects split_class as a single JSON string form field, # not flattened multipart fields (https://github.com/landing-ai/ade-python/issues/76). - "split_class": json.dumps(list(split_class)), + "split_class": split_class if isinstance(split_class, str) else json.dumps(list(split_class)), "markdown": markdown, "markdown_url": markdown_url, "model": model, @@ -1516,7 +1516,7 @@ async def section( async def split( self, *, - split_class: Iterable[client_split_params.SplitClass], + split_class: Union[str, Iterable[client_split_params.SplitClass]], markdown: Union[FileTypes, str, None] | Omit = omit, markdown_url: Optional[str] | Omit = omit, model: Optional[str] | Omit = omit, @@ -1568,7 +1568,7 @@ async def split( { # The API expects split_class as a single JSON string form field, # not flattened multipart fields (https://github.com/landing-ai/ade-python/issues/76). - "split_class": json.dumps(list(split_class)), + "split_class": split_class if isinstance(split_class, str) else json.dumps(list(split_class)), "markdown": markdown, "markdown_url": markdown_url, "model": model, diff --git a/src/landingai_ade/types/client_split_params.py b/src/landingai_ade/types/client_split_params.py index abede14..046b711 100644 --- a/src/landingai_ade/types/client_split_params.py +++ b/src/landingai_ade/types/client_split_params.py @@ -9,7 +9,7 @@ class ClientSplitParams(TypedDict, total=False): - split_class: Required[Iterable[SplitClass]] + split_class: Required[Union[str, Iterable[SplitClass]]] """List of split classification options/configuration. Can be provided as JSON string in form data. diff --git a/tests/test_split_params.py b/tests/test_split_params.py index 315397f..28601c2 100644 --- a/tests/test_split_params.py +++ b/tests/test_split_params.py @@ -26,6 +26,26 @@ def test_sync_split_class_sent_as_json_string() -> None: assert json.loads(body["split_class"]) == SPLIT_CLASS +def test_sync_split_class_json_string_passed_through() -> None: + # An already JSON-encoded split_class (documented in the README) must not be re-serialized. + encoded = json.dumps(SPLIT_CLASS) + with LandingAIADE(apikey="test-key", base_url="http://localhost") as client: + with patch.object(client, "post", return_value=MagicMock()) as post: + client.split(split_class=encoded, markdown="some markdown") + + assert post.call_args.kwargs["body"]["split_class"] == encoded + + +@pytest.mark.asyncio +async def test_async_split_class_json_string_passed_through() -> None: + encoded = json.dumps(SPLIT_CLASS) + async with AsyncLandingAIADE(apikey="test-key", base_url="http://localhost") as client: + with patch.object(client, "post", new_callable=AsyncMock, return_value=MagicMock()) as post: + await client.split(split_class=encoded, markdown="some markdown") + + assert post.call_args.kwargs["body"]["split_class"] == encoded + + @pytest.mark.asyncio async def test_async_split_class_sent_as_json_string() -> None: async with AsyncLandingAIADE(apikey="test-key", base_url="http://localhost") as client: