From 329a522306d3d525470a9c4779af5ec48e5707de Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:12:27 +0000 Subject: [PATCH 001/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 40b629f2..057ae562 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-1b16fbf5337f188d0b66a5992f0d241be80c46c45412ef9830cb19b11437d1c6.yml -openapi_spec_hash: 88f89b5803058bfa20d5da05c2bcf754 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-0b1593276da08586fa59362aacb34e1ce3ba4c490ac7565a5da750c375423c08.yml +openapi_spec_hash: 39e1aec21098a9c4e58a160219cdffda config_hash: 9dd1f73da997aefc8516b226e0e7fed7 From 4e52aa4bd603d8d3c41caf0cb0e25cb2dc2082d4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:36:21 +0000 Subject: [PATCH 002/174] feat(api): manual updates --- .stats.yml | 4 ++-- src/reducto/_client.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 057ae562..cb85bcf4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-0b1593276da08586fa59362aacb34e1ce3ba4c490ac7565a5da750c375423c08.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f001845b97fa35ac65a90a8596ad27faa1915dd14ff5970d328a9b2ba0b8b7f5.yml openapi_spec_hash: 39e1aec21098a9c4e58a160219cdffda -config_hash: 9dd1f73da997aefc8516b226e0e7fed7 +config_hash: f5af4807897e53748df2db501d3430a3 diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 901feebe..03182941 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -335,6 +335,10 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self.post( "/upload", body=maybe_transform({"file": file}, client_upload_params.ClientUploadParams), @@ -638,6 +642,10 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self.post( "/upload", body=await async_maybe_transform({"file": file}, client_upload_params.ClientUploadParams), From 51997655084b4213c2d57a3ef700e24ea4bde9e9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:12:35 +0000 Subject: [PATCH 003/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index cb85bcf4..849c51dd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f001845b97fa35ac65a90a8596ad27faa1915dd14ff5970d328a9b2ba0b8b7f5.yml -openapi_spec_hash: 39e1aec21098a9c4e58a160219cdffda +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-dc702cf2fd1817e60aff4022238010579b501929a75f6eb244e943962bc30687.yml +openapi_spec_hash: 0a51e09f9b535589c7b382f8d55ee237 config_hash: f5af4807897e53748df2db501d3430a3 From 275d4a04dddf896ee47754dfd08d62a794bb0b86 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 23:12:30 +0000 Subject: [PATCH 004/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 849c51dd..4a30d9bb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-dc702cf2fd1817e60aff4022238010579b501929a75f6eb244e943962bc30687.yml -openapi_spec_hash: 0a51e09f9b535589c7b382f8d55ee237 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a03444aca05084374af7903ab2264138d91d917b13d1ca9885f68a1df13f9a4f.yml +openapi_spec_hash: 556e03d0fd9d0578e60d1612b12d0d81 config_hash: f5af4807897e53748df2db501d3430a3 From fd75ce3995cff7d8bfb9b56196c96e8c928855c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 01:12:32 +0000 Subject: [PATCH 005/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4a30d9bb..187276b7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a03444aca05084374af7903ab2264138d91d917b13d1ca9885f68a1df13f9a4f.yml -openapi_spec_hash: 556e03d0fd9d0578e60d1612b12d0d81 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-4aedc63546ffc06ad076d7e22ff2195c83c88b3e53da7b975e957f484871fdda.yml +openapi_spec_hash: 206816a6246ec8a66ec92b8438259119 config_hash: f5af4807897e53748df2db501d3430a3 From 9569811363241346e31dfd0bc52f0061649836e5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 02:12:30 +0000 Subject: [PATCH 006/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 187276b7..63973db3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-4aedc63546ffc06ad076d7e22ff2195c83c88b3e53da7b975e957f484871fdda.yml -openapi_spec_hash: 206816a6246ec8a66ec92b8438259119 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c542f7a4dc833e411d95cebf8b18658cec2165c0bf2ea9d8fd4fb22a8439181d.yml +openapi_spec_hash: 0375ff8d15d566b650f1123fc2d19381 config_hash: f5af4807897e53748df2db501d3430a3 From 3cd26464584f7699c87339df7a0e7c16ec715058 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 03:12:37 +0000 Subject: [PATCH 007/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 63973db3..ad91af57 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c542f7a4dc833e411d95cebf8b18658cec2165c0bf2ea9d8fd4fb22a8439181d.yml -openapi_spec_hash: 0375ff8d15d566b650f1123fc2d19381 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-edc01c54949a158b2f7c9a0e2cd75082a25732f19bbcad24295ddf4dcd5bfc63.yml +openapi_spec_hash: 1a98763ccd32108de2448ba4391c8fab config_hash: f5af4807897e53748df2db501d3430a3 From 961c57078514aa454ff0d6d4df24935891042084 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 19:12:44 +0000 Subject: [PATCH 008/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ad91af57..c2f05769 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-edc01c54949a158b2f7c9a0e2cd75082a25732f19bbcad24295ddf4dcd5bfc63.yml -openapi_spec_hash: 1a98763ccd32108de2448ba4391c8fab +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8b225e5605bed3927beaca916c03c525df08ba114ea1e2cd07765e6c3379bbc0.yml +openapi_spec_hash: 042770ee09fbd932ca8b80b9257cbaa1 config_hash: f5af4807897e53748df2db501d3430a3 From 78826a2e9b6bd797edb03258789841ba855112df Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 21:15:41 +0000 Subject: [PATCH 009/174] feat(api): manual updates --- .stats.yml | 2 +- api.md | 71 ++++++---- src/reducto/resources/classify.py | 2 +- src/reducto/resources/edit.py | 17 +-- src/reducto/resources/extract.py | 2 +- src/reducto/resources/parse.py | 2 +- src/reducto/resources/pipeline.py | 12 +- src/reducto/resources/split.py | 12 +- src/reducto/types/__init__.py | 44 ++++-- src/reducto/types/async_config_v3_param.py | 26 +--- src/reducto/types/edit_run_job_params.py | 28 +--- src/reducto/types/enhance_param.py | 45 +----- src/reducto/types/extract_run_response.py | 2 +- src/reducto/types/job_get_response.py | 12 +- src/reducto/types/parse_run_response.py | 4 +- .../types/pipeline_run_job_response.py | 9 -- src/reducto/types/retrieval_param.py | 25 +--- src/reducto/types/shared/__init__.py | 29 ++++ .../types/shared/advanced_citations_config.py | 12 ++ .../shared/advanced_processing_options.py | 133 ++++++++++++++++++ .../types/shared/array_extract_config.py | 29 ++++ .../async_edit_response.py} | 6 +- .../{ => shared}/async_extract_response.py | 2 +- .../{ => shared}/async_parse_response.py | 2 +- .../types/shared/async_pipeline_response.py | 9 ++ .../async_split_response.py} | 6 +- .../types/shared/base_processing_options.py | 67 +++++++++ src/reducto/types/shared/chunking.py | 29 ++++ src/reducto/types/shared/chunking_config.py | 29 ++++ .../types/{ => shared}/classify_response.py | 2 +- .../types/shared/direct_webhook_config.py | 14 ++ .../types/{ => shared}/edit_response.py | 6 +- src/reducto/types/shared/enrich_config.py | 23 +++ .../shared/experimental_processing_options.py | 129 +++++++++++++++++ .../types/{ => shared}/extract_response.py | 4 +- src/reducto/types/shared/figure_agentic.py | 25 ++++ .../types/shared/figure_summary_config.py | 25 ++++ .../shared/large_table_chunking_config.py | 21 +++ .../types/{ => shared}/parse_response.py | 6 +- .../types/{ => shared}/pipeline_response.py | 6 +- .../types/shared/split_large_tables.py | 38 +++++ .../types/{ => shared}/split_response.py | 6 +- .../types/shared/svix_webhook_config.py | 18 +++ src/reducto/types/shared/table_agentic.py | 15 ++ .../types/shared/table_summary_config.py | 15 ++ src/reducto/types/shared/text_agentic.py | 18 +++ .../types/shared/webhook_config_new.py | 28 ++++ src/reducto/types/shared_params/__init__.py | 8 ++ src/reducto/types/shared_params/chunking.py | 29 ++++ .../shared_params/direct_webhook_config.py | 13 ++ .../types/shared_params/figure_agentic.py | 25 ++++ .../types/shared_params/split_large_tables.py | 38 +++++ .../shared_params/svix_webhook_config.py | 19 +++ .../types/shared_params/table_agentic.py | 15 ++ .../types/shared_params/text_agentic.py | 18 +++ .../types/shared_params/webhook_config_new.py | 29 ++++ src/reducto/types/spreadsheet_param.py | 36 +---- tests/api_resources/test_classify.py | 2 +- tests/api_resources/test_edit.py | 18 +-- tests/api_resources/test_extract.py | 2 +- tests/api_resources/test_parse.py | 2 +- tests/api_resources/test_pipeline.py | 21 ++- tests/api_resources/test_split.py | 21 ++- 63 files changed, 1088 insertions(+), 275 deletions(-) delete mode 100644 src/reducto/types/pipeline_run_job_response.py create mode 100644 src/reducto/types/shared/advanced_citations_config.py create mode 100644 src/reducto/types/shared/advanced_processing_options.py create mode 100644 src/reducto/types/shared/array_extract_config.py rename src/reducto/types/{edit_run_job_response.py => shared/async_edit_response.py} (50%) rename src/reducto/types/{ => shared}/async_extract_response.py (84%) rename src/reducto/types/{ => shared}/async_parse_response.py (84%) create mode 100644 src/reducto/types/shared/async_pipeline_response.py rename src/reducto/types/{split_run_job_response.py => shared/async_split_response.py} (50%) create mode 100644 src/reducto/types/shared/base_processing_options.py create mode 100644 src/reducto/types/shared/chunking.py create mode 100644 src/reducto/types/shared/chunking_config.py rename src/reducto/types/{ => shared}/classify_response.py (97%) create mode 100644 src/reducto/types/shared/direct_webhook_config.py rename src/reducto/types/{ => shared}/edit_response.py (84%) create mode 100644 src/reducto/types/shared/enrich_config.py create mode 100644 src/reducto/types/shared/experimental_processing_options.py rename src/reducto/types/{ => shared}/extract_response.py (89%) create mode 100644 src/reducto/types/shared/figure_agentic.py create mode 100644 src/reducto/types/shared/figure_summary_config.py create mode 100644 src/reducto/types/shared/large_table_chunking_config.py rename src/reducto/types/{ => shared}/parse_response.py (97%) rename src/reducto/types/{ => shared}/pipeline_response.py (92%) create mode 100644 src/reducto/types/shared/split_large_tables.py rename src/reducto/types/{ => shared}/split_response.py (91%) create mode 100644 src/reducto/types/shared/svix_webhook_config.py create mode 100644 src/reducto/types/shared/table_agentic.py create mode 100644 src/reducto/types/shared/table_summary_config.py create mode 100644 src/reducto/types/shared/text_agentic.py create mode 100644 src/reducto/types/shared/webhook_config_new.py create mode 100644 src/reducto/types/shared_params/chunking.py create mode 100644 src/reducto/types/shared_params/direct_webhook_config.py create mode 100644 src/reducto/types/shared_params/figure_agentic.py create mode 100644 src/reducto/types/shared_params/split_large_tables.py create mode 100644 src/reducto/types/shared_params/svix_webhook_config.py create mode 100644 src/reducto/types/shared_params/table_agentic.py create mode 100644 src/reducto/types/shared_params/text_agentic.py create mode 100644 src/reducto/types/shared_params/webhook_config_new.py diff --git a/.stats.yml b/.stats.yml index c2f05769..4418a538 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8b225e5605bed3927beaca916c03c525df08ba114ea1e2cd07765e6c3379bbc0.yml openapi_spec_hash: 042770ee09fbd932ca8b80b9257cbaa1 -config_hash: f5af4807897e53748df2db501d3430a3 +config_hash: d834b8e3612580690c900b6c3f6423dd diff --git a/api.md b/api.md index f0f17287..7f279889 100644 --- a/api.md +++ b/api.md @@ -1,7 +1,38 @@ # Shared Types ```python -from reducto.types import Upload +from reducto.types import ( + AdvancedCitationsConfig, + AdvancedProcessingOptions, + ArrayExtractConfig, + AsyncEditResponse, + AsyncExtractResponse, + AsyncParseResponse, + AsyncPipelineResponse, + AsyncSplitResponse, + BaseProcessingOptions, + Chunking, + ChunkingConfig, + ClassifyResponse, + DirectWebhookConfig, + EditResponse, + EnrichConfig, + ExperimentalProcessingOptions, + ExtractResponse, + FigureAgentic, + FigureSummaryConfig, + LargeTableChunkingConfig, + ParseResponse, + PipelineResponse, + SplitLargeTables, + SplitResponse, + SvixWebhookConfig, + TableAgentic, + TableSummaryConfig, + TextAgentic, + Upload, + WebhookConfigNew, +) ``` # Reducto @@ -25,10 +56,8 @@ Types: from reducto.types import ( AsyncConfigV3, AsyncParseConfig, - AsyncParseResponse, Enhance, Formatting, - ParseResponse, Retrieval, Settings, Spreadsheet, @@ -39,7 +68,7 @@ from reducto.types import ( Methods: - client.parse.run(\*\*params) -> ParseRunResponse -- client.parse.run_job(\*\*params) -> AsyncParseResponse +- client.parse.run_job(\*\*params) -> AsyncParseResponse # Extract @@ -48,7 +77,6 @@ Types: ```python from reducto.types import ( AsyncExtractConfig, - AsyncExtractResponse, ExtractSettings, ExtractUsage, Instructions, @@ -61,65 +89,58 @@ from reducto.types import ( Methods: - client.extract.run(\*\*params) -> ExtractRunResponse -- client.extract.run_job(\*\*params) -> AsyncExtractResponse +- client.extract.run_job(\*\*params) -> AsyncExtractResponse # Split Types: ```python -from reducto.types import ( - DeepSplitPageEvidence, - ParseUsage, - SplitCategory, - SplitResponse, - SplitTableOptions, - SplitRunJobResponse, -) +from reducto.types import DeepSplitPageEvidence, ParseUsage, SplitCategory, SplitTableOptions ``` Methods: -- client.split.run(\*\*params) -> SplitResponse -- client.split.run_job(\*\*params) -> SplitRunJobResponse +- client.split.run(\*\*params) -> SplitResponse +- client.split.run_job(\*\*params) -> AsyncSplitResponse # Edit Types: ```python -from reducto.types import BoundingBox, EditOptions, EditResponse, EditWidget, EditRunJobResponse +from reducto.types import BoundingBox, EditOptions, EditWidget ``` Methods: -- client.edit.run(\*\*params) -> EditResponse -- client.edit.run_job(\*\*params) -> EditRunJobResponse +- client.edit.run(\*\*params) -> EditResponse +- client.edit.run_job(\*\*params) -> AsyncEditResponse # Pipeline Types: ```python -from reducto.types import PipelineResponse, PipelineSettings, PipelineRunJobResponse +from reducto.types import PipelineSettings ``` Methods: -- client.pipeline.run(\*\*params) -> PipelineResponse -- client.pipeline.run_job(\*\*params) -> PipelineRunJobResponse +- client.pipeline.run(\*\*params) -> PipelineResponse +- client.pipeline.run_job(\*\*params) -> AsyncPipelineResponse # Classify Types: ```python -from reducto.types import ClassifyResponse, PageRange +from reducto.types import PageRange ``` Methods: -- client.classify.run(\*\*params) -> ClassifyResponse +- client.classify.run(\*\*params) -> ClassifyResponse # Webhook @@ -138,7 +159,7 @@ Methods: Types: ```python -from reducto.types import ExtractResponse, JobGetResponse, JobGetAllResponse +from reducto.types import JobGetResponse, JobGetAllResponse ``` Methods: diff --git a/src/reducto/resources/classify.py b/src/reducto/resources/classify.py index 8702e75f..18128dfa 100644 --- a/src/reducto/resources/classify.py +++ b/src/reducto/resources/classify.py @@ -18,7 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.classify_response import ClassifyResponse +from ..types.shared.classify_response import ClassifyResponse __all__ = ["ClassifyResource", "AsyncClassifyResource"] diff --git a/src/reducto/resources/edit.py b/src/reducto/resources/edit.py index 48cae916..9a3a56dc 100644 --- a/src/reducto/resources/edit.py +++ b/src/reducto/resources/edit.py @@ -18,10 +18,11 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.edit_response import EditResponse from ..types.edit_widget_param import EditWidgetParam from ..types.edit_options_param import EditOptionsParam -from ..types.edit_run_job_response import EditRunJobResponse +from ..types.shared.edit_response import EditResponse +from ..types.shared.async_edit_response import AsyncEditResponse +from ..types.shared_params.webhook_config_new import WebhookConfigNew __all__ = ["EditResource", "AsyncEditResource"] @@ -117,14 +118,14 @@ def run_job( edit_options: EditOptionsParam | Omit = omit, form_schema: Optional[Iterable[EditWidgetParam]] | Omit = omit, priority: bool | Omit = omit, - webhook: edit_run_job_params.Webhook | Omit = omit, + webhook: WebhookConfigNew | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EditRunJobResponse: + ) -> AsyncEditResponse: """Edit Async Args: @@ -171,7 +172,7 @@ def run_job( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=EditRunJobResponse, + cast_to=AsyncEditResponse, ) @@ -266,14 +267,14 @@ async def run_job( edit_options: EditOptionsParam | Omit = omit, form_schema: Optional[Iterable[EditWidgetParam]] | Omit = omit, priority: bool | Omit = omit, - webhook: edit_run_job_params.Webhook | Omit = omit, + webhook: WebhookConfigNew | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> EditRunJobResponse: + ) -> AsyncEditResponse: """Edit Async Args: @@ -320,7 +321,7 @@ async def run_job( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=EditRunJobResponse, + cast_to=AsyncEditResponse, ) diff --git a/src/reducto/resources/extract.py b/src/reducto/resources/extract.py index 699226c5..743f1705 100644 --- a/src/reducto/resources/extract.py +++ b/src/reducto/resources/extract.py @@ -26,8 +26,8 @@ from ..types.parse_options_param import ParseOptionsParam from ..types.extract_run_response import ExtractRunResponse from ..types.async_config_v3_param import AsyncConfigV3Param -from ..types.async_extract_response import AsyncExtractResponse from ..types.extract_settings_param import ExtractSettingsParam +from ..types.shared.async_extract_response import AsyncExtractResponse __all__ = ["ExtractResource", "AsyncExtractResource"] diff --git a/src/reducto/resources/parse.py b/src/reducto/resources/parse.py index 5beacb07..1d23710e 100644 --- a/src/reducto/resources/parse.py +++ b/src/reducto/resources/parse.py @@ -28,8 +28,8 @@ from ..types.formatting_param import FormattingParam from ..types.spreadsheet_param import SpreadsheetParam from ..types.parse_run_response import ParseRunResponse -from ..types.async_parse_response import AsyncParseResponse from ..types.async_config_v3_param import AsyncConfigV3Param +from ..types.shared.async_parse_response import AsyncParseResponse __all__ = ["ParseResource", "AsyncParseResource"] diff --git a/src/reducto/resources/pipeline.py b/src/reducto/resources/pipeline.py index 025334d8..f65d6c3c 100644 --- a/src/reducto/resources/pipeline.py +++ b/src/reducto/resources/pipeline.py @@ -16,10 +16,10 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.pipeline_response import PipelineResponse from ..types.async_config_v3_param import AsyncConfigV3Param from ..types.pipeline_settings_param import PipelineSettingsParam -from ..types.pipeline_run_job_response import PipelineRunJobResponse +from ..types.shared.pipeline_response import PipelineResponse +from ..types.shared.async_pipeline_response import AsyncPipelineResponse __all__ = ["PipelineResource", "AsyncPipelineResource"] @@ -111,7 +111,7 @@ def run_job( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> PipelineRunJobResponse: + ) -> AsyncPipelineResponse: """ Pipeline Async @@ -153,7 +153,7 @@ def run_job( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=PipelineRunJobResponse, + cast_to=AsyncPipelineResponse, ) @@ -244,7 +244,7 @@ async def run_job( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> PipelineRunJobResponse: + ) -> AsyncPipelineResponse: """ Pipeline Async @@ -286,7 +286,7 @@ async def run_job( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=PipelineRunJobResponse, + cast_to=AsyncPipelineResponse, ) diff --git a/src/reducto/resources/split.py b/src/reducto/resources/split.py index ae2bcab7..553a42bb 100644 --- a/src/reducto/resources/split.py +++ b/src/reducto/resources/split.py @@ -18,12 +18,12 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options -from ..types.split_response import SplitResponse from ..types.parse_options_param import ParseOptionsParam from ..types.split_category_param import SplitCategoryParam from ..types.async_config_v3_param import AsyncConfigV3Param -from ..types.split_run_job_response import SplitRunJobResponse +from ..types.shared.split_response import SplitResponse from ..types.split_table_options_param import SplitTableOptionsParam +from ..types.shared.async_split_response import AsyncSplitResponse __all__ = ["SplitResource", "AsyncSplitResource"] @@ -126,7 +126,7 @@ def run_job( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SplitRunJobResponse: + ) -> AsyncSplitResponse: """ Split Async @@ -175,7 +175,7 @@ def run_job( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SplitRunJobResponse, + cast_to=AsyncSplitResponse, ) @@ -277,7 +277,7 @@ async def run_job( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SplitRunJobResponse: + ) -> AsyncSplitResponse: """ Split Async @@ -326,7 +326,7 @@ async def run_job( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=SplitRunJobResponse, + cast_to=AsyncSplitResponse, ) diff --git a/src/reducto/types/__init__.py b/src/reducto/types/__init__.py index 9994a7aa..bf5908b3 100644 --- a/src/reducto/types/__init__.py +++ b/src/reducto/types/__init__.py @@ -2,28 +2,53 @@ from __future__ import annotations -from .shared import Upload as Upload +from .shared import ( + Upload as Upload, + Chunking as Chunking, + TextAgentic as TextAgentic, + EditResponse as EditResponse, + EnrichConfig as EnrichConfig, + TableAgentic as TableAgentic, + FigureAgentic as FigureAgentic, + ParseResponse as ParseResponse, + SplitResponse as SplitResponse, + ChunkingConfig as ChunkingConfig, + ExtractResponse as ExtractResponse, + ClassifyResponse as ClassifyResponse, + PipelineResponse as PipelineResponse, + SplitLargeTables as SplitLargeTables, + WebhookConfigNew as WebhookConfigNew, + AsyncEditResponse as AsyncEditResponse, + SvixWebhookConfig as SvixWebhookConfig, + ArrayExtractConfig as ArrayExtractConfig, + AsyncParseResponse as AsyncParseResponse, + AsyncSplitResponse as AsyncSplitResponse, + TableSummaryConfig as TableSummaryConfig, + DirectWebhookConfig as DirectWebhookConfig, + FigureSummaryConfig as FigureSummaryConfig, + AsyncExtractResponse as AsyncExtractResponse, + AsyncPipelineResponse as AsyncPipelineResponse, + BaseProcessingOptions as BaseProcessingOptions, + AdvancedCitationsConfig as AdvancedCitationsConfig, + LargeTableChunkingConfig as LargeTableChunkingConfig, + AdvancedProcessingOptions as AdvancedProcessingOptions, + ExperimentalProcessingOptions as ExperimentalProcessingOptions, +) from .v3_extract import V3Extract as V3Extract from .edit_widget import EditWidget as EditWidget from .parse_usage import ParseUsage as ParseUsage from .bounding_box import BoundingBox as BoundingBox -from .edit_response import EditResponse as EditResponse from .enhance_param import EnhanceParam as EnhanceParam from .extract_usage import ExtractUsage as ExtractUsage -from .parse_response import ParseResponse as ParseResponse from .settings_param import SettingsParam as SettingsParam -from .split_response import SplitResponse as SplitResponse from .edit_run_params import EditRunParams as EditRunParams from .retrieval_param import RetrievalParam as RetrievalParam -from .extract_response import ExtractResponse as ExtractResponse from .formatting_param import FormattingParam as FormattingParam from .job_get_response import JobGetResponse as JobGetResponse from .page_range_param import PageRangeParam as PageRangeParam from .parse_run_params import ParseRunParams as ParseRunParams from .split_run_params import SplitRunParams as SplitRunParams -from .classify_response import ClassifyResponse as ClassifyResponse from .edit_widget_param import EditWidgetParam as EditWidgetParam -from .pipeline_response import PipelineResponse as PipelineResponse from .spreadsheet_param import SpreadsheetParam as SpreadsheetParam from .bounding_box_param import BoundingBoxParam as BoundingBoxParam from .edit_options_param import EditOptionsParam as EditOptionsParam @@ -36,7 +61,6 @@ from .parse_options_param import ParseOptionsParam as ParseOptionsParam from .pipeline_run_params import PipelineRunParams as PipelineRunParams from .api_version_response import APIVersionResponse as APIVersionResponse -from .async_parse_response import AsyncParseResponse as AsyncParseResponse from .client_upload_params import ClientUploadParams as ClientUploadParams from .extract_run_response import ExtractRunResponse as ExtractRunResponse from .job_get_all_response import JobGetAllResponse as JobGetAllResponse @@ -45,15 +69,11 @@ from .split_run_job_params import SplitRunJobParams as SplitRunJobParams from .webhook_run_response import WebhookRunResponse as WebhookRunResponse from .async_config_v3_param import AsyncConfigV3Param as AsyncConfigV3Param -from .edit_run_job_response import EditRunJobResponse as EditRunJobResponse -from .async_extract_response import AsyncExtractResponse as AsyncExtractResponse from .extract_run_job_params import ExtractRunJobParams as ExtractRunJobParams from .extract_settings_param import ExtractSettingsParam as ExtractSettingsParam -from .split_run_job_response import SplitRunJobResponse as SplitRunJobResponse from .pipeline_run_job_params import PipelineRunJobParams as PipelineRunJobParams from .pipeline_settings_param import PipelineSettingsParam as PipelineSettingsParam from .async_parse_config_param import AsyncParseConfigParam as AsyncParseConfigParam from .deep_split_page_evidence import DeepSplitPageEvidence as DeepSplitPageEvidence -from .pipeline_run_job_response import PipelineRunJobResponse as PipelineRunJobResponse from .split_table_options_param import SplitTableOptionsParam as SplitTableOptionsParam from .async_extract_config_param import AsyncExtractConfigParam as AsyncExtractConfigParam diff --git a/src/reducto/types/async_config_v3_param.py b/src/reducto/types/async_config_v3_param.py index 8d2e2156..500b235f 100644 --- a/src/reducto/types/async_config_v3_param.py +++ b/src/reducto/types/async_config_v3_param.py @@ -3,30 +3,14 @@ from __future__ import annotations from typing import Union, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing_extensions import TypeAlias, TypedDict -from .._types import SequenceNotStr +from .shared_params.svix_webhook_config import SvixWebhookConfig +from .shared_params.direct_webhook_config import DirectWebhookConfig -__all__ = ["AsyncConfigV3Param", "Webhook", "WebhookSvixWebhookConfig", "WebhookDirectWebhookConfig"] +__all__ = ["AsyncConfigV3Param", "Webhook"] - -class WebhookSvixWebhookConfig(TypedDict, total=False): - channels: SequenceNotStr[str] - """ - A list of Svix channels the message will be delivered down, omit to send to all - channels. - """ - - mode: Literal["svix"] - - -class WebhookDirectWebhookConfig(TypedDict, total=False): - url: Required[str] - - mode: Literal["direct"] - - -Webhook: TypeAlias = Union[WebhookSvixWebhookConfig, WebhookDirectWebhookConfig] +Webhook: TypeAlias = Union[SvixWebhookConfig, DirectWebhookConfig] class AsyncConfigV3Param(TypedDict, total=False): diff --git a/src/reducto/types/edit_run_job_params.py b/src/reducto/types/edit_run_job_params.py index 5e3f628c..0d8372a2 100644 --- a/src/reducto/types/edit_run_job_params.py +++ b/src/reducto/types/edit_run_job_params.py @@ -3,14 +3,14 @@ from __future__ import annotations from typing import Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing_extensions import Required, TypeAlias, TypedDict -from .._types import SequenceNotStr from .edit_widget_param import EditWidgetParam from .edit_options_param import EditOptionsParam from .shared_params.upload import Upload +from .shared_params.webhook_config_new import WebhookConfigNew -__all__ = ["EditRunJobParams", "DocumentURL", "Webhook"] +__all__ = ["EditRunJobParams", "DocumentURL"] class EditRunJobParams(TypedDict, total=False): @@ -42,27 +42,7 @@ class EditRunJobParams(TypedDict, total=False): jobs. """ - webhook: Webhook + webhook: WebhookConfigNew DocumentURL: TypeAlias = Union[str, Upload] - - -class Webhook(TypedDict, total=False): - channels: SequenceNotStr[str] - """ - A list of Svix channels the message will be delivered down, omit to send to all - channels. - """ - - metadata: object - """JSON metadata included in webhook request body""" - - mode: Literal["disabled", "svix", "direct"] - """The mode to use for webhook delivery. - - Defaults to 'disabled'. We recommend using 'svix' for production environments. - """ - - url: str - """The URL to send the webhook to (if using direct webhoook).""" diff --git a/src/reducto/types/enhance_param.py b/src/reducto/types/enhance_param.py index 85d5569c..386c86f1 100644 --- a/src/reducto/types/enhance_param.py +++ b/src/reducto/types/enhance_param.py @@ -2,47 +2,16 @@ from __future__ import annotations -from typing import Union, Iterable, Optional -from typing_extensions import Literal, Required, TypeAlias, TypedDict +from typing import Union, Iterable +from typing_extensions import TypeAlias, TypedDict -__all__ = ["EnhanceParam", "Agentic", "AgenticTableAgentic", "AgenticFigureAgentic", "AgenticTextAgentic"] +from .shared_params.text_agentic import TextAgentic +from .shared_params.table_agentic import TableAgentic +from .shared_params.figure_agentic import FigureAgentic +__all__ = ["EnhanceParam", "Agentic"] -class AgenticTableAgentic(TypedDict, total=False): - scope: Required[Literal["table"]] - - prompt: Optional[str] - """Custom prompt for table agentic.""" - - -class AgenticFigureAgentic(TypedDict, total=False): - scope: Required[Literal["figure"]] - - advanced_chart_agent: bool - """If True, use the advanced chart agent. Defaults to False.""" - - prompt: Optional[str] - """Custom prompt for figure agentic.""" - - return_overlays: bool - """If True, return overlays for the figure. - - This is so you can use the overlays to double check the quality of the - extraction - """ - - -class AgenticTextAgentic(TypedDict, total=False): - scope: Required[Literal["text"]] - - prompt: Optional[str] - """Custom instructions for agentic text. - - Note: This only applies to form regions (key-value). - """ - - -Agentic: TypeAlias = Union[AgenticTableAgentic, AgenticFigureAgentic, AgenticTextAgentic] +Agentic: TypeAlias = Union[TableAgentic, FigureAgentic, TextAgentic] class EnhanceParam(TypedDict, total=False): diff --git a/src/reducto/types/extract_run_response.py b/src/reducto/types/extract_run_response.py index e343fcfa..d19ea49a 100644 --- a/src/reducto/types/extract_run_response.py +++ b/src/reducto/types/extract_run_response.py @@ -4,7 +4,7 @@ from typing_extensions import TypeAlias from .v3_extract import V3Extract -from .async_extract_response import AsyncExtractResponse +from .shared.async_extract_response import AsyncExtractResponse __all__ = ["ExtractRunResponse"] diff --git a/src/reducto/types/job_get_response.py b/src/reducto/types/job_get_response.py index 5190ae91..f38acf91 100644 --- a/src/reducto/types/job_get_response.py +++ b/src/reducto/types/job_get_response.py @@ -6,12 +6,12 @@ from .._models import BaseModel from .v3_extract import V3Extract -from .edit_response import EditResponse -from .parse_response import ParseResponse -from .split_response import SplitResponse -from .extract_response import ExtractResponse -from .classify_response import ClassifyResponse -from .pipeline_response import PipelineResponse +from .shared.edit_response import EditResponse +from .shared.parse_response import ParseResponse +from .shared.split_response import SplitResponse +from .shared.extract_response import ExtractResponse +from .shared.classify_response import ClassifyResponse +from .shared.pipeline_response import PipelineResponse __all__ = [ "JobGetResponse", diff --git a/src/reducto/types/parse_run_response.py b/src/reducto/types/parse_run_response.py index f12989e7..6c78116a 100644 --- a/src/reducto/types/parse_run_response.py +++ b/src/reducto/types/parse_run_response.py @@ -3,8 +3,8 @@ from typing import Union from typing_extensions import TypeAlias -from .parse_response import ParseResponse -from .async_parse_response import AsyncParseResponse +from .shared.parse_response import ParseResponse +from .shared.async_parse_response import AsyncParseResponse __all__ = ["ParseRunResponse"] diff --git a/src/reducto/types/pipeline_run_job_response.py b/src/reducto/types/pipeline_run_job_response.py deleted file mode 100644 index efe6e839..00000000 --- a/src/reducto/types/pipeline_run_job_response.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["PipelineRunJobResponse"] - - -class PipelineRunJobResponse(BaseModel): - job_id: str diff --git a/src/reducto/types/retrieval_param.py b/src/reducto/types/retrieval_param.py index 3cf32952..cbff9cf6 100644 --- a/src/reducto/types/retrieval_param.py +++ b/src/reducto/types/retrieval_param.py @@ -2,31 +2,12 @@ from __future__ import annotations -from typing import List, Optional +from typing import List from typing_extensions import Literal, TypedDict -__all__ = ["RetrievalParam", "Chunking"] +from .shared_params.chunking import Chunking - -class Chunking(TypedDict, total=False): - chunk_mode: Literal["variable", "section", "page", "disabled", "block", "page_sections"] - """Choose how to partition chunks. - - Variable mode chunks by character length and visual context. Section mode chunks - by section headers. Page mode chunks according to pages. Page sections mode - chunks first by page, then by sections within each page. Disabled returns one - single chunk. - """ - - chunk_overlap: int - """Number of characters of overlap to include from adjacent chunks. Defaults to 0.""" - - chunk_size: Optional[int] - """ - The approximate size of chunks (in characters) that the document will be split - into. Defaults to null, in which case the chunk size is variable between 250 - - 1500 characters. - """ +__all__ = ["RetrievalParam"] class RetrievalParam(TypedDict, total=False): diff --git a/src/reducto/types/shared/__init__.py b/src/reducto/types/shared/__init__.py index e9bf9399..9bb36d61 100644 --- a/src/reducto/types/shared/__init__.py +++ b/src/reducto/types/shared/__init__.py @@ -1,3 +1,32 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .upload import Upload as Upload +from .chunking import Chunking as Chunking +from .text_agentic import TextAgentic as TextAgentic +from .edit_response import EditResponse as EditResponse +from .enrich_config import EnrichConfig as EnrichConfig +from .table_agentic import TableAgentic as TableAgentic +from .figure_agentic import FigureAgentic as FigureAgentic +from .parse_response import ParseResponse as ParseResponse +from .split_response import SplitResponse as SplitResponse +from .chunking_config import ChunkingConfig as ChunkingConfig +from .extract_response import ExtractResponse as ExtractResponse +from .classify_response import ClassifyResponse as ClassifyResponse +from .pipeline_response import PipelineResponse as PipelineResponse +from .split_large_tables import SplitLargeTables as SplitLargeTables +from .webhook_config_new import WebhookConfigNew as WebhookConfigNew +from .async_edit_response import AsyncEditResponse as AsyncEditResponse +from .svix_webhook_config import SvixWebhookConfig as SvixWebhookConfig +from .array_extract_config import ArrayExtractConfig as ArrayExtractConfig +from .async_parse_response import AsyncParseResponse as AsyncParseResponse +from .async_split_response import AsyncSplitResponse as AsyncSplitResponse +from .table_summary_config import TableSummaryConfig as TableSummaryConfig +from .direct_webhook_config import DirectWebhookConfig as DirectWebhookConfig +from .figure_summary_config import FigureSummaryConfig as FigureSummaryConfig +from .async_extract_response import AsyncExtractResponse as AsyncExtractResponse +from .async_pipeline_response import AsyncPipelineResponse as AsyncPipelineResponse +from .base_processing_options import BaseProcessingOptions as BaseProcessingOptions +from .advanced_citations_config import AdvancedCitationsConfig as AdvancedCitationsConfig +from .advanced_processing_options import AdvancedProcessingOptions as AdvancedProcessingOptions +from .large_table_chunking_config import LargeTableChunkingConfig as LargeTableChunkingConfig +from .experimental_processing_options import ExperimentalProcessingOptions as ExperimentalProcessingOptions diff --git a/src/reducto/types/shared/advanced_citations_config.py b/src/reducto/types/shared/advanced_citations_config.py new file mode 100644 index 00000000..bc4f525a --- /dev/null +++ b/src/reducto/types/shared/advanced_citations_config.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["AdvancedCitationsConfig"] + + +class AdvancedCitationsConfig(BaseModel): + numerical_confidence: Optional[bool] = None + """If True, enable numeric citation confidence scores. Defaults to False.""" diff --git a/src/reducto/types/shared/advanced_processing_options.py b/src/reducto/types/shared/advanced_processing_options.py new file mode 100644 index 00000000..0fbb39d2 --- /dev/null +++ b/src/reducto/types/shared/advanced_processing_options.py @@ -0,0 +1,133 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from typing_extensions import Literal, TypeAlias + +from .. import page_range +from ..._models import BaseModel +from .large_table_chunking_config import LargeTableChunkingConfig + +__all__ = ["AdvancedProcessingOptions", "PageRange"] + +PageRange: TypeAlias = Union[page_range.PageRange, List[page_range.PageRange], List[int], List[str]] + + +class AdvancedProcessingOptions(BaseModel): + add_page_markers: Optional[bool] = None + """If True, add page markers to the output (e.g. + + [[PAGE 1 BEGINS HERE]] and [[PAGE 1 ENDS HERE]] added as blocks to the content). + Defaults to False. + """ + + continue_hierarchy: Optional[bool] = None + """ + A flag to indicate if the hierarchy of the document should be continued from + chunk to chunk. + """ + + document_password: Optional[str] = None + """Password to decrypt password-protected documents.""" + + enable_change_tracking: Optional[bool] = None + """ + Enables model-based detection of underlines and strikethroughs, adding / + tags to OCR text. Works with any extraction mode. Defaults to False. + """ + + enable_highlight_detection: Optional[bool] = None + """If True, enable highlight detection. + + Highlighted text will be surrounded by tags in the output. Defaults to + False. + """ + + exclude_hidden_rows_cols: Optional[bool] = None + """Skip hidden rows and cols in Excel files. Defaults to False.""" + + exclude_hidden_sheets: Optional[bool] = None + """Skip hidden sheets in Excel files. Defaults to False.""" + + filter_line_numbers: Optional[bool] = None + """If True, filter out line numbers from the output. Defaults to False.""" + + force_file_extension: Optional[str] = None + """Force the URL to be downloaded as a specific file extension (e.g. .png).""" + + ignore_watermarks: Optional[bool] = None + """If True, ignore and remove watermarks from OCR output. Defaults to False.""" + + include_color_information: Optional[bool] = None + """ + If True, preserve Excel cell colours in the extracted spreadsheet text using + LaTeX colour commands. + """ + + include_dropdown_information: Optional[bool] = None + """ + If True, include dropdown options and the selected value when rendering + spreadsheet cells. + """ + + include_formula_information: Optional[bool] = None + """ + If True, preserve formula information in spreadsheet cells by wrapping text with + LaTeX formula commands during parsing. + """ + + keep_line_breaks: Optional[bool] = None + """If line breaks should be preserved in the text.""" + + large_table_chunking: Optional[LargeTableChunkingConfig] = None + """ + The configuration options for large table chunking (currently only supported on + spreadsheet and CSV files). + """ + + merge_tables: Optional[bool] = None + """ + A flag to indicate if consecutive tables with the same number of columns should + be merged across breaks and spaces. + """ + + ocr_system: Optional[Literal["highres", "multilingual", "combined", "reducto", "legacy", "reducto-v2"]] = None + """The OCR system to use. + + Highres is recommended for documents with English characters. Legacy uses an + alternative OCR backend. + """ + + page_range: Optional[PageRange] = None + """The page range to process (1-indexed). + + By default, the entire document is processed. For spreadsheets, you can also + provide a list of sheet names. + """ + + persist_results: Optional[bool] = None + """If True, persist the results indefinitely. Defaults to False.""" + + read_comments: Optional[bool] = None + """If True, pull in PDF comments from the document. Defaults to False.""" + + remove_text_formatting: Optional[bool] = None + """If True, remove text formatting from the output (e.g. + + hyphens for list items). Defaults to False. + """ + + return_ocr_data: Optional[bool] = None + """If True, return OCR data in the result. Defaults to False.""" + + spreadsheet_table_clustering: Optional[Literal["default", "disabled", "intelligent"]] = None + """ + In a spreadsheet with different tables inside, we enable splitting up the tables + by default. Intelligent mode applies more powerful models for superior accuracy, + at 5× the default per-cell rate. Disabling will register as one large table. + """ + + table_output_format: Optional[Literal["html", "json", "md", "jsonbbox", "dynamic", "ai_json", "csv"]] = None + """The mode to use for table output. + + Dynamic returns md for simpler tables and html for more complex tables. + """ diff --git a/src/reducto/types/shared/array_extract_config.py b/src/reducto/types/shared/array_extract_config.py new file mode 100644 index 00000000..bf2c5a58 --- /dev/null +++ b/src/reducto/types/shared/array_extract_config.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ArrayExtractConfig"] + + +class ArrayExtractConfig(BaseModel): + enabled: Optional[bool] = None + """ + Array extraction allows you to extract long lists of information from lengthy + documents. It makes parallel calls on overlapping sections of the document. + """ + + mode: Optional[Literal["auto", "legacy", "streaming", "no_overlap"]] = None + """The array extraction version to use.""" + + pages_per_segment: Optional[int] = None + """Length of each segment, in pages, for parallel calls with array extraction.""" + + streaming_extract_item_density: Optional[int] = None + """Number of items to extract in each stream call. + + Lower numbers will increase quality but be much slower. 50 works well for most + documents with tables. + """ diff --git a/src/reducto/types/edit_run_job_response.py b/src/reducto/types/shared/async_edit_response.py similarity index 50% rename from src/reducto/types/edit_run_job_response.py rename to src/reducto/types/shared/async_edit_response.py index 967f46d8..da6fcba4 100644 --- a/src/reducto/types/edit_run_job_response.py +++ b/src/reducto/types/shared/async_edit_response.py @@ -1,9 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .._models import BaseModel +from ..._models import BaseModel -__all__ = ["EditRunJobResponse"] +__all__ = ["AsyncEditResponse"] -class EditRunJobResponse(BaseModel): +class AsyncEditResponse(BaseModel): job_id: str diff --git a/src/reducto/types/async_extract_response.py b/src/reducto/types/shared/async_extract_response.py similarity index 84% rename from src/reducto/types/async_extract_response.py rename to src/reducto/types/shared/async_extract_response.py index 7ee83afa..5bafe1ca 100644 --- a/src/reducto/types/async_extract_response.py +++ b/src/reducto/types/shared/async_extract_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .._models import BaseModel +from ..._models import BaseModel __all__ = ["AsyncExtractResponse"] diff --git a/src/reducto/types/async_parse_response.py b/src/reducto/types/shared/async_parse_response.py similarity index 84% rename from src/reducto/types/async_parse_response.py rename to src/reducto/types/shared/async_parse_response.py index 41610236..9df7fe5d 100644 --- a/src/reducto/types/async_parse_response.py +++ b/src/reducto/types/shared/async_parse_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .._models import BaseModel +from ..._models import BaseModel __all__ = ["AsyncParseResponse"] diff --git a/src/reducto/types/shared/async_pipeline_response.py b/src/reducto/types/shared/async_pipeline_response.py new file mode 100644 index 00000000..ca6a8829 --- /dev/null +++ b/src/reducto/types/shared/async_pipeline_response.py @@ -0,0 +1,9 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel + +__all__ = ["AsyncPipelineResponse"] + + +class AsyncPipelineResponse(BaseModel): + job_id: str diff --git a/src/reducto/types/split_run_job_response.py b/src/reducto/types/shared/async_split_response.py similarity index 50% rename from src/reducto/types/split_run_job_response.py rename to src/reducto/types/shared/async_split_response.py index 404b32fe..5294328b 100644 --- a/src/reducto/types/split_run_job_response.py +++ b/src/reducto/types/shared/async_split_response.py @@ -1,9 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .._models import BaseModel +from ..._models import BaseModel -__all__ = ["SplitRunJobResponse"] +__all__ = ["AsyncSplitResponse"] -class SplitRunJobResponse(BaseModel): +class AsyncSplitResponse(BaseModel): job_id: str diff --git a/src/reducto/types/shared/base_processing_options.py b/src/reducto/types/shared/base_processing_options.py new file mode 100644 index 00000000..50f69666 --- /dev/null +++ b/src/reducto/types/shared/base_processing_options.py @@ -0,0 +1,67 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel +from .chunking_config import ChunkingConfig +from .table_summary_config import TableSummaryConfig +from .figure_summary_config import FigureSummaryConfig + +__all__ = ["BaseProcessingOptions"] + + +class BaseProcessingOptions(BaseModel): + chunking: Optional[ChunkingConfig] = None + """The configuration options for chunking. + + Chunking is commonly used for RAG usecases. + """ + + extraction_mode: Optional[Literal["ocr", "metadata", "hybrid"]] = None + """The mode to use for extraction. + + Metadata/hybrid are only recommended with high quality metadata embeddings. + """ + + figure_summary: Optional[FigureSummaryConfig] = None + """The configuration options for figure summarization.""" + + filter_blocks: Optional[ + List[ + Literal[ + "Header", + "Footer", + "Title", + "Section Header", + "Page Number", + "List Item", + "Figure", + "Table", + "Key Value", + "Text", + "Comment", + "Signature", + ] + ] + ] = None + """A list of block types to filter from chunk content. + + Pass blocks to filter them from content. By default, no blocks are filtered. + """ + + force_url_result: Optional[bool] = None + """ + Force the result to be returned in URL form (by default only used for very large + responses). + """ + + ocr_mode: Optional[Literal["standard", "agentic"]] = None + """The mode to use for OCR. + + Agentic mode adds an extra pass, correcting any table/text mistakes at a small + cost. + """ + + table_summary: Optional[TableSummaryConfig] = None + """The configuration options for table summarization.""" diff --git a/src/reducto/types/shared/chunking.py b/src/reducto/types/shared/chunking.py new file mode 100644 index 00000000..b56c1250 --- /dev/null +++ b/src/reducto/types/shared/chunking.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["Chunking"] + + +class Chunking(BaseModel): + chunk_mode: Optional[Literal["variable", "section", "page", "disabled", "block", "page_sections"]] = None + """Choose how to partition chunks. + + Variable mode chunks by character length and visual context. Section mode chunks + by section headers. Page mode chunks according to pages. Page sections mode + chunks first by page, then by sections within each page. Disabled returns one + single chunk. + """ + + chunk_overlap: Optional[int] = None + """Number of characters of overlap to include from adjacent chunks. Defaults to 0.""" + + chunk_size: Optional[int] = None + """ + The approximate size of chunks (in characters) that the document will be split + into. Defaults to null, in which case the chunk size is variable between 250 - + 1500 characters. + """ diff --git a/src/reducto/types/shared/chunking_config.py b/src/reducto/types/shared/chunking_config.py new file mode 100644 index 00000000..078ad35d --- /dev/null +++ b/src/reducto/types/shared/chunking_config.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["ChunkingConfig"] + + +class ChunkingConfig(BaseModel): + chunk_mode: Optional[Literal["variable", "section", "page", "block", "disabled", "page_sections"]] = None + """Choose how to partition chunks. + + Variable mode chunks by character length and visual context. Section mode chunks + by section headers. Page mode chunks according to pages. Page sections mode + chunks first by page, then by sections within each page. Disabled returns one + single chunk. + """ + + chunk_overlap: Optional[int] = None + """Number of characters of overlap to include from adjacent chunks. Defaults to 0.""" + + chunk_size: Optional[int] = None + """ + The approximate size of chunks (in characters) that the document will be split + into. Defaults to None, in which case the chunk size is variable between 250 - + 1500 characters. + """ diff --git a/src/reducto/types/classify_response.py b/src/reducto/types/shared/classify_response.py similarity index 97% rename from src/reducto/types/classify_response.py rename to src/reducto/types/shared/classify_response.py index 05d58978..6b26edbf 100644 --- a/src/reducto/types/classify_response.py +++ b/src/reducto/types/shared/classify_response.py @@ -3,7 +3,7 @@ from typing import List, Optional from typing_extensions import Literal -from .._models import BaseModel +from ..._models import BaseModel __all__ = [ "ClassifyResponse", diff --git a/src/reducto/types/shared/direct_webhook_config.py b/src/reducto/types/shared/direct_webhook_config.py new file mode 100644 index 00000000..b0bff755 --- /dev/null +++ b/src/reducto/types/shared/direct_webhook_config.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["DirectWebhookConfig"] + + +class DirectWebhookConfig(BaseModel): + url: str + + mode: Optional[Literal["direct"]] = None diff --git a/src/reducto/types/edit_response.py b/src/reducto/types/shared/edit_response.py similarity index 84% rename from src/reducto/types/edit_response.py rename to src/reducto/types/shared/edit_response.py index 91c3217f..3eac5af4 100644 --- a/src/reducto/types/edit_response.py +++ b/src/reducto/types/shared/edit_response.py @@ -2,9 +2,9 @@ from typing import List, Optional -from .._models import BaseModel -from .edit_widget import EditWidget -from .parse_usage import ParseUsage +from ..._models import BaseModel +from ..edit_widget import EditWidget +from ..parse_usage import ParseUsage __all__ = ["EditResponse"] diff --git a/src/reducto/types/shared/enrich_config.py b/src/reducto/types/shared/enrich_config.py new file mode 100644 index 00000000..12ab6e81 --- /dev/null +++ b/src/reducto/types/shared/enrich_config.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["EnrichConfig"] + + +class EnrichConfig(BaseModel): + enabled: Optional[bool] = None + """ + If enabled, a large language/vision model will be used to postprocess the + extracted content. Note: enabling enrich requires tables be outputted in + markdown format. Defaults to False. + """ + + mode: Optional[Literal["standard", "page", "table"]] = None + """The mode to use for enrichment. Defaults to standard""" + + prompt: Optional[str] = None + """Add information to the prompt for enrichment.""" diff --git a/src/reducto/types/shared/experimental_processing_options.py b/src/reducto/types/shared/experimental_processing_options.py new file mode 100644 index 00000000..1f4d9ac2 --- /dev/null +++ b/src/reducto/types/shared/experimental_processing_options.py @@ -0,0 +1,129 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, Dict, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from .enrich_config import EnrichConfig + +__all__ = ["ExperimentalProcessingOptions"] + + +class ExperimentalProcessingOptions(BaseModel): + chunk_table_blocks: Optional[bool] = None + """ + If True, split table blocks into smaller chunks based on the specified chunk + size in the chunking option. Defaults to False. + """ + + danger_filter_wide_boxes: Optional[bool] = None + """You probably shouldn't use this. + + If True, filter out boxes with width greater than 50% of the document width. + Defaults to False. You probably don't want to use this. + """ + + detect_signatures: Optional[bool] = None + """If True, detect signatures in the document. Defaults to False.""" + + disable_office_external_links: Optional[bool] = None + """ + If True, configure LibreOffice conversion to block linked content from untrusted + documents. Defaults to True on-prem and False elsewhere. + """ + + embed_text_metadata_pdf: Optional[bool] = None + """ + If extracted OCR text metadata should be embedded back into the returned PDF, + overwriting any existing text. Defaults to False. + """ + + enable_checkboxes: Optional[bool] = None + """ + Use an experimental checkbox detection model to add checkboxes to the output, + defaults to False + """ + + enable_equations: Optional[bool] = None + """ + Use an experimental equation detection model to add equations to the output, + defaults to False + """ + + enable_scripts: Optional[bool] = None + """ + Add tag around subscripts and tag around superscripts, defaults to + False + """ + + enrich: Optional[EnrichConfig] = None + """The configuration options for enrichment.""" + + latency_sensitive: Optional[bool] = None + """If True, the job will be processed with lower latency and higher priority. + + Uses 2x the cost of a regular job. Defaults to False. + """ + + layout_enrichment: Optional[bool] = None + """ + Layout enrichment is a beta feature that improves our layout and reading order + performance at the cost of increased latency. Defaults to False. + """ + + layout_model: Optional[ + Literal[ + "default", "beta", "rfdetr", "rfdetr0302", "rfdetr0303", "rfdetrbase0218", "rfdetr0304", "qwen35_27b_0317" + ] + ] = None + """The layout model to use for the document. + + This will be deprecated in the future. + """ + + native_office_conversion: Optional[bool] = None + """ + Instead of using LibreOffice, when enabled, this flag uses a Windows VM to + convert files. This is slower but more accurate. + """ + + promptable_agentic_text_on_regular_blocks: Optional[bool] = None + """ + If True, enable two-stage LLM pipeline for agentic text correction on regular + text blocks. Defaults to False. + """ + + return_figure_images: Optional[bool] = None + """If figure images should be returned in the result. Defaults to False.""" + + return_page_images: Optional[bool] = None + """If full page images should be returned in the result. Defaults to False.""" + + return_table_images: Optional[bool] = None + """If table images should be returned in the result. Defaults to False.""" + + rotate_figures: Optional[bool] = None + """ + Use an orientation model to detect and rotate figures as needed, defaults to + False + """ + + rotate_pages: Optional[bool] = None + """Use an orientation model to detect and rotate pages as needed, defaults to True""" + + user_specified_timeout_seconds: Optional[float] = None + """A user specified timeout, defaults to None""" + + if TYPE_CHECKING: + # Some versions of Pydantic <2.8.0 have a bug and don’t allow assigning a + # value to this field, so for compatibility we avoid doing it at runtime. + __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride] + + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + else: + __pydantic_extra__: Dict[str, object] diff --git a/src/reducto/types/extract_response.py b/src/reducto/types/shared/extract_response.py similarity index 89% rename from src/reducto/types/extract_response.py rename to src/reducto/types/shared/extract_response.py index 947aa58e..1744516d 100644 --- a/src/reducto/types/extract_response.py +++ b/src/reducto/types/shared/extract_response.py @@ -2,8 +2,8 @@ from typing import List, Optional -from .._models import BaseModel -from .extract_usage import ExtractUsage +from ..._models import BaseModel +from ..extract_usage import ExtractUsage __all__ = ["ExtractResponse"] diff --git a/src/reducto/types/shared/figure_agentic.py b/src/reducto/types/shared/figure_agentic.py new file mode 100644 index 00000000..2846fe2d --- /dev/null +++ b/src/reducto/types/shared/figure_agentic.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FigureAgentic"] + + +class FigureAgentic(BaseModel): + scope: Literal["figure"] + + advanced_chart_agent: Optional[bool] = None + """If True, use the advanced chart agent. Defaults to False.""" + + prompt: Optional[str] = None + """Custom prompt for figure agentic.""" + + return_overlays: Optional[bool] = None + """If True, return overlays for the figure. + + This is so you can use the overlays to double check the quality of the + extraction + """ diff --git a/src/reducto/types/shared/figure_summary_config.py b/src/reducto/types/shared/figure_summary_config.py new file mode 100644 index 00000000..7e8a9d59 --- /dev/null +++ b/src/reducto/types/shared/figure_summary_config.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["FigureSummaryConfig"] + + +class FigureSummaryConfig(BaseModel): + advanced_chart_agent: Optional[bool] = None + """If True, use the advanced chart agent. Defaults to False.""" + + enabled: Optional[bool] = None + """If figure summarization should be performed.""" + + override: Optional[bool] = None + """If the figure summary prompt should override our default prompt.""" + + prompt: Optional[str] = None + """Add information to the prompt for figure summarization. + + Note any visual cues that should be incorporated. Example: 'When provided a + diagram, extract all of the figure content verbatim.' + """ diff --git a/src/reducto/types/shared/large_table_chunking_config.py b/src/reducto/types/shared/large_table_chunking_config.py new file mode 100644 index 00000000..f0b033d6 --- /dev/null +++ b/src/reducto/types/shared/large_table_chunking_config.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["LargeTableChunkingConfig"] + + +class LargeTableChunkingConfig(BaseModel): + enabled: Optional[bool] = None + """ + If large tables should be chunked into smaller tables, currently only supported + on spreadsheet and CSV files. + """ + + size: Optional[int] = None + """The max row/column size for a table to be chunked. + + Defaults to 50. Header rows/columns are persisted based on heuristics. + """ diff --git a/src/reducto/types/parse_response.py b/src/reducto/types/shared/parse_response.py similarity index 97% rename from src/reducto/types/parse_response.py rename to src/reducto/types/shared/parse_response.py index d9fa50da..8b54180f 100644 --- a/src/reducto/types/parse_response.py +++ b/src/reducto/types/shared/parse_response.py @@ -3,9 +3,9 @@ from typing import Dict, List, Union, Optional from typing_extensions import Literal, TypeAlias -from .._models import BaseModel -from .parse_usage import ParseUsage -from .bounding_box import BoundingBox +from ..._models import BaseModel +from ..parse_usage import ParseUsage +from ..bounding_box import BoundingBox __all__ = [ "ParseResponse", diff --git a/src/reducto/types/pipeline_response.py b/src/reducto/types/shared/pipeline_response.py similarity index 92% rename from src/reducto/types/pipeline_response.py rename to src/reducto/types/shared/pipeline_response.py index cd7faafb..5b9b44de 100644 --- a/src/reducto/types/pipeline_response.py +++ b/src/reducto/types/shared/pipeline_response.py @@ -3,9 +3,9 @@ from typing import List, Union, Optional from typing_extensions import TypeAlias -from .._models import BaseModel -from .v3_extract import V3Extract -from .parse_usage import ParseUsage +from ..._models import BaseModel +from ..v3_extract import V3Extract +from ..parse_usage import ParseUsage from .edit_response import EditResponse from .parse_response import ParseResponse from .split_response import SplitResponse diff --git a/src/reducto/types/shared/split_large_tables.py b/src/reducto/types/shared/split_large_tables.py new file mode 100644 index 00000000..33409b77 --- /dev/null +++ b/src/reducto/types/shared/split_large_tables.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Union, Optional +from typing_extensions import TypeAlias + +from ..._models import BaseModel + +__all__ = ["SplitLargeTables", "Size", "SizeSplitLargeTableSizes"] + + +class SizeSplitLargeTableSizes(BaseModel): + column: Optional[int] = None + """The number of columns to include in each chunk when splitting large tables. + + Does not chunk columns if set to None. + """ + + row: Optional[int] = None + """The number of rows to include in each chunk when splitting large tables. + + Does not chunk rows if set to None. + """ + + +Size: TypeAlias = Union[int, SizeSplitLargeTableSizes] + + +class SplitLargeTables(BaseModel): + enabled: Optional[bool] = None + """If True, split large tables into smaller tables. Defaults to True.""" + + size: Optional[Size] = None + """The size of the tables to split into. + + Defaults to 50. Use 'row' and 'column' to independently specify the number of + rows and columns to include when splitting. If you only want to split by rows or + columns, set the other value to None. + """ diff --git a/src/reducto/types/split_response.py b/src/reducto/types/shared/split_response.py similarity index 91% rename from src/reducto/types/split_response.py rename to src/reducto/types/shared/split_response.py index b3544af4..18cae9ef 100644 --- a/src/reducto/types/split_response.py +++ b/src/reducto/types/shared/split_response.py @@ -3,9 +3,9 @@ from typing import Dict, List, Union, Optional from typing_extensions import Literal, TypeAlias -from .._models import BaseModel -from .parse_usage import ParseUsage -from .deep_split_page_evidence import DeepSplitPageEvidence +from ..._models import BaseModel +from ..parse_usage import ParseUsage +from ..deep_split_page_evidence import DeepSplitPageEvidence __all__ = [ "SplitResponse", diff --git a/src/reducto/types/shared/svix_webhook_config.py b/src/reducto/types/shared/svix_webhook_config.py new file mode 100644 index 00000000..4cbb5627 --- /dev/null +++ b/src/reducto/types/shared/svix_webhook_config.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["SvixWebhookConfig"] + + +class SvixWebhookConfig(BaseModel): + channels: Optional[List[str]] = None + """ + A list of Svix channels the message will be delivered down, omit to send to all + channels. + """ + + mode: Optional[Literal["svix"]] = None diff --git a/src/reducto/types/shared/table_agentic.py b/src/reducto/types/shared/table_agentic.py new file mode 100644 index 00000000..9ff59a81 --- /dev/null +++ b/src/reducto/types/shared/table_agentic.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TableAgentic"] + + +class TableAgentic(BaseModel): + scope: Literal["table"] + + prompt: Optional[str] = None + """Custom prompt for table agentic.""" diff --git a/src/reducto/types/shared/table_summary_config.py b/src/reducto/types/shared/table_summary_config.py new file mode 100644 index 00000000..f4b4c776 --- /dev/null +++ b/src/reducto/types/shared/table_summary_config.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["TableSummaryConfig"] + + +class TableSummaryConfig(BaseModel): + enabled: Optional[bool] = None + """If table summarization should be performed.""" + + prompt: Optional[str] = None + """Add information to the prompt for table summarization.""" diff --git a/src/reducto/types/shared/text_agentic.py b/src/reducto/types/shared/text_agentic.py new file mode 100644 index 00000000..e2074786 --- /dev/null +++ b/src/reducto/types/shared/text_agentic.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["TextAgentic"] + + +class TextAgentic(BaseModel): + scope: Literal["text"] + + prompt: Optional[str] = None + """Custom instructions for agentic text. + + Note: This only applies to form regions (key-value). + """ diff --git a/src/reducto/types/shared/webhook_config_new.py b/src/reducto/types/shared/webhook_config_new.py new file mode 100644 index 00000000..e28c8b6d --- /dev/null +++ b/src/reducto/types/shared/webhook_config_new.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["WebhookConfigNew"] + + +class WebhookConfigNew(BaseModel): + channels: Optional[List[str]] = None + """ + A list of Svix channels the message will be delivered down, omit to send to all + channels. + """ + + metadata: Optional[object] = None + """JSON metadata included in webhook request body""" + + mode: Optional[Literal["disabled", "svix", "direct"]] = None + """The mode to use for webhook delivery. + + Defaults to 'disabled'. We recommend using 'svix' for production environments. + """ + + url: Optional[str] = None + """The URL to send the webhook to (if using direct webhoook).""" diff --git a/src/reducto/types/shared_params/__init__.py b/src/reducto/types/shared_params/__init__.py index e9bf9399..46da23a2 100644 --- a/src/reducto/types/shared_params/__init__.py +++ b/src/reducto/types/shared_params/__init__.py @@ -1,3 +1,11 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from .upload import Upload as Upload +from .chunking import Chunking as Chunking +from .text_agentic import TextAgentic as TextAgentic +from .table_agentic import TableAgentic as TableAgentic +from .figure_agentic import FigureAgentic as FigureAgentic +from .split_large_tables import SplitLargeTables as SplitLargeTables +from .webhook_config_new import WebhookConfigNew as WebhookConfigNew +from .svix_webhook_config import SvixWebhookConfig as SvixWebhookConfig +from .direct_webhook_config import DirectWebhookConfig as DirectWebhookConfig diff --git a/src/reducto/types/shared_params/chunking.py b/src/reducto/types/shared_params/chunking.py new file mode 100644 index 00000000..2d79eda7 --- /dev/null +++ b/src/reducto/types/shared_params/chunking.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["Chunking"] + + +class Chunking(TypedDict, total=False): + chunk_mode: Literal["variable", "section", "page", "disabled", "block", "page_sections"] + """Choose how to partition chunks. + + Variable mode chunks by character length and visual context. Section mode chunks + by section headers. Page mode chunks according to pages. Page sections mode + chunks first by page, then by sections within each page. Disabled returns one + single chunk. + """ + + chunk_overlap: int + """Number of characters of overlap to include from adjacent chunks. Defaults to 0.""" + + chunk_size: Optional[int] + """ + The approximate size of chunks (in characters) that the document will be split + into. Defaults to null, in which case the chunk size is variable between 250 - + 1500 characters. + """ diff --git a/src/reducto/types/shared_params/direct_webhook_config.py b/src/reducto/types/shared_params/direct_webhook_config.py new file mode 100644 index 00000000..841219f1 --- /dev/null +++ b/src/reducto/types/shared_params/direct_webhook_config.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["DirectWebhookConfig"] + + +class DirectWebhookConfig(TypedDict, total=False): + url: Required[str] + + mode: Literal["direct"] diff --git a/src/reducto/types/shared_params/figure_agentic.py b/src/reducto/types/shared_params/figure_agentic.py new file mode 100644 index 00000000..607034fb --- /dev/null +++ b/src/reducto/types/shared_params/figure_agentic.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["FigureAgentic"] + + +class FigureAgentic(TypedDict, total=False): + scope: Required[Literal["figure"]] + + advanced_chart_agent: bool + """If True, use the advanced chart agent. Defaults to False.""" + + prompt: Optional[str] + """Custom prompt for figure agentic.""" + + return_overlays: bool + """If True, return overlays for the figure. + + This is so you can use the overlays to double check the quality of the + extraction + """ diff --git a/src/reducto/types/shared_params/split_large_tables.py b/src/reducto/types/shared_params/split_large_tables.py new file mode 100644 index 00000000..82c1c45b --- /dev/null +++ b/src/reducto/types/shared_params/split_large_tables.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from typing_extensions import TypeAlias, TypedDict + +__all__ = ["SplitLargeTables", "Size", "SizeSplitLargeTableSizes"] + + +class SizeSplitLargeTableSizes(TypedDict, total=False): + column: Optional[int] + """The number of columns to include in each chunk when splitting large tables. + + Does not chunk columns if set to None. + """ + + row: Optional[int] + """The number of rows to include in each chunk when splitting large tables. + + Does not chunk rows if set to None. + """ + + +Size: TypeAlias = Union[int, SizeSplitLargeTableSizes] + + +class SplitLargeTables(TypedDict, total=False): + enabled: bool + """If True, split large tables into smaller tables. Defaults to True.""" + + size: Size + """The size of the tables to split into. + + Defaults to 50. Use 'row' and 'column' to independently specify the number of + rows and columns to include when splitting. If you only want to split by rows or + columns, set the other value to None. + """ diff --git a/src/reducto/types/shared_params/svix_webhook_config.py b/src/reducto/types/shared_params/svix_webhook_config.py new file mode 100644 index 00000000..cf571641 --- /dev/null +++ b/src/reducto/types/shared_params/svix_webhook_config.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["SvixWebhookConfig"] + + +class SvixWebhookConfig(TypedDict, total=False): + channels: SequenceNotStr[str] + """ + A list of Svix channels the message will be delivered down, omit to send to all + channels. + """ + + mode: Literal["svix"] diff --git a/src/reducto/types/shared_params/table_agentic.py b/src/reducto/types/shared_params/table_agentic.py new file mode 100644 index 00000000..30d3216d --- /dev/null +++ b/src/reducto/types/shared_params/table_agentic.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["TableAgentic"] + + +class TableAgentic(TypedDict, total=False): + scope: Required[Literal["table"]] + + prompt: Optional[str] + """Custom prompt for table agentic.""" diff --git a/src/reducto/types/shared_params/text_agentic.py b/src/reducto/types/shared_params/text_agentic.py new file mode 100644 index 00000000..be3ae8b2 --- /dev/null +++ b/src/reducto/types/shared_params/text_agentic.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["TextAgentic"] + + +class TextAgentic(TypedDict, total=False): + scope: Required[Literal["text"]] + + prompt: Optional[str] + """Custom instructions for agentic text. + + Note: This only applies to form regions (key-value). + """ diff --git a/src/reducto/types/shared_params/webhook_config_new.py b/src/reducto/types/shared_params/webhook_config_new.py new file mode 100644 index 00000000..0ebd8e3c --- /dev/null +++ b/src/reducto/types/shared_params/webhook_config_new.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["WebhookConfigNew"] + + +class WebhookConfigNew(TypedDict, total=False): + channels: SequenceNotStr[str] + """ + A list of Svix channels the message will be delivered down, omit to send to all + channels. + """ + + metadata: object + """JSON metadata included in webhook request body""" + + mode: Literal["disabled", "svix", "direct"] + """The mode to use for webhook delivery. + + Defaults to 'disabled'. We recommend using 'svix' for production environments. + """ + + url: str + """The URL to send the webhook to (if using direct webhoook).""" diff --git a/src/reducto/types/spreadsheet_param.py b/src/reducto/types/spreadsheet_param.py index 4a3e1a12..43dfcbd4 100644 --- a/src/reducto/types/spreadsheet_param.py +++ b/src/reducto/types/spreadsheet_param.py @@ -2,40 +2,12 @@ from __future__ import annotations -from typing import List, Union, Optional -from typing_extensions import Literal, TypeAlias, TypedDict +from typing import List +from typing_extensions import Literal, TypedDict -__all__ = ["SpreadsheetParam", "SplitLargeTables", "SplitLargeTablesSize", "SplitLargeTablesSizeSplitLargeTableSizes"] +from .shared_params.split_large_tables import SplitLargeTables - -class SplitLargeTablesSizeSplitLargeTableSizes(TypedDict, total=False): - column: Optional[int] - """The number of columns to include in each chunk when splitting large tables. - - Does not chunk columns if set to None. - """ - - row: Optional[int] - """The number of rows to include in each chunk when splitting large tables. - - Does not chunk rows if set to None. - """ - - -SplitLargeTablesSize: TypeAlias = Union[int, SplitLargeTablesSizeSplitLargeTableSizes] - - -class SplitLargeTables(TypedDict, total=False): - enabled: bool - """If True, split large tables into smaller tables. Defaults to True.""" - - size: SplitLargeTablesSize - """The size of the tables to split into. - - Defaults to 50. Use 'row' and 'column' to independently specify the number of - rows and columns to include when splitting. If you only want to split by rows or - columns, set the other value to None. - """ +__all__ = ["SpreadsheetParam"] class SpreadsheetParam(TypedDict, total=False): diff --git a/tests/api_resources/test_classify.py b/tests/api_resources/test_classify.py index 04bdff54..5a2a554f 100644 --- a/tests/api_resources/test_classify.py +++ b/tests/api_resources/test_classify.py @@ -9,7 +9,7 @@ from reducto import Reducto, AsyncReducto from tests.utils import assert_matches_type -from reducto.types import ClassifyResponse +from reducto.types.shared import ClassifyResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_edit.py b/tests/api_resources/test_edit.py index 426d8c2c..c47b3356 100644 --- a/tests/api_resources/test_edit.py +++ b/tests/api_resources/test_edit.py @@ -9,7 +9,7 @@ from reducto import Reducto, AsyncReducto from tests.utils import assert_matches_type -from reducto.types import EditResponse, EditRunJobResponse +from reducto.types.shared import EditResponse, AsyncEditResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -95,7 +95,7 @@ def test_method_run_job(self, client: Reducto) -> None: document_url="string", edit_instructions="edit_instructions", ) - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -135,7 +135,7 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: "url": "url", }, ) - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -148,7 +148,7 @@ def test_raw_response_run_job(self, client: Reducto) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" edit = response.parse() - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -161,7 +161,7 @@ def test_streaming_response_run_job(self, client: Reducto) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" edit = response.parse() - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) assert cast(Any, response.is_closed) is True @@ -249,7 +249,7 @@ async def test_method_run_job(self, async_client: AsyncReducto) -> None: document_url="string", edit_instructions="edit_instructions", ) - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -289,7 +289,7 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) "url": "url", }, ) - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -302,7 +302,7 @@ async def test_raw_response_run_job(self, async_client: AsyncReducto) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" edit = await response.parse() - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -315,6 +315,6 @@ async def test_streaming_response_run_job(self, async_client: AsyncReducto) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" edit = await response.parse() - assert_matches_type(EditRunJobResponse, edit, path=["response"]) + assert_matches_type(AsyncEditResponse, edit, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_extract.py b/tests/api_resources/test_extract.py index 8a3a4202..4be4d104 100644 --- a/tests/api_resources/test_extract.py +++ b/tests/api_resources/test_extract.py @@ -11,8 +11,8 @@ from tests.utils import assert_matches_type from reducto.types import ( ExtractRunResponse, - AsyncExtractResponse, ) +from reducto.types.shared import AsyncExtractResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_parse.py b/tests/api_resources/test_parse.py index 4590de2b..f74ce4e4 100644 --- a/tests/api_resources/test_parse.py +++ b/tests/api_resources/test_parse.py @@ -11,8 +11,8 @@ from tests.utils import assert_matches_type from reducto.types import ( ParseRunResponse, - AsyncParseResponse, ) +from reducto.types.shared import AsyncParseResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") diff --git a/tests/api_resources/test_pipeline.py b/tests/api_resources/test_pipeline.py index 9bbcdeb7..db9aa933 100644 --- a/tests/api_resources/test_pipeline.py +++ b/tests/api_resources/test_pipeline.py @@ -9,10 +9,7 @@ from reducto import Reducto, AsyncReducto from tests.utils import assert_matches_type -from reducto.types import ( - PipelineResponse, - PipelineRunJobResponse, -) +from reducto.types.shared import PipelineResponse, AsyncPipelineResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -74,7 +71,7 @@ def test_method_run_job(self, client: Reducto) -> None: input="string", pipeline_id="pipeline_id", ) - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -92,7 +89,7 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: }, settings={"document_password": "document_password"}, ) - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -105,7 +102,7 @@ def test_raw_response_run_job(self, client: Reducto) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" pipeline = response.parse() - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -118,7 +115,7 @@ def test_streaming_response_run_job(self, client: Reducto) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" pipeline = response.parse() - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) assert cast(Any, response.is_closed) is True @@ -182,7 +179,7 @@ async def test_method_run_job(self, async_client: AsyncReducto) -> None: input="string", pipeline_id="pipeline_id", ) - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -200,7 +197,7 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) }, settings={"document_password": "document_password"}, ) - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -213,7 +210,7 @@ async def test_raw_response_run_job(self, async_client: AsyncReducto) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" pipeline = await response.parse() - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -226,6 +223,6 @@ async def test_streaming_response_run_job(self, async_client: AsyncReducto) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" pipeline = await response.parse() - assert_matches_type(PipelineRunJobResponse, pipeline, path=["response"]) + assert_matches_type(AsyncPipelineResponse, pipeline, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_split.py b/tests/api_resources/test_split.py index 408699d5..2de2376f 100644 --- a/tests/api_resources/test_split.py +++ b/tests/api_resources/test_split.py @@ -9,10 +9,7 @@ from reducto import Reducto, AsyncReducto from tests.utils import assert_matches_type -from reducto.types import ( - SplitResponse, - SplitRunJobResponse, -) +from reducto.types.shared import SplitResponse, AsyncSplitResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -153,7 +150,7 @@ def test_method_run_job(self, client: Reducto) -> None: } ], ) - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -230,7 +227,7 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: settings={"table_cutoff": "truncate"}, split_rules="split_rules", ) - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -248,7 +245,7 @@ def test_raw_response_run_job(self, client: Reducto) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" split = response.parse() - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -266,7 +263,7 @@ def test_streaming_response_run_job(self, client: Reducto) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" split = response.parse() - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) assert cast(Any, response.is_closed) is True @@ -409,7 +406,7 @@ async def test_method_run_job(self, async_client: AsyncReducto) -> None: } ], ) - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -486,7 +483,7 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) settings={"table_cutoff": "truncate"}, split_rules="split_rules", ) - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -504,7 +501,7 @@ async def test_raw_response_run_job(self, async_client: AsyncReducto) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" split = await response.parse() - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize @@ -522,6 +519,6 @@ async def test_streaming_response_run_job(self, async_client: AsyncReducto) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" split = await response.parse() - assert_matches_type(SplitRunJobResponse, split, path=["response"]) + assert_matches_type(AsyncSplitResponse, split, path=["response"]) assert cast(Any, response.is_closed) is True From 64288190fc8a562f5f8c8c6238a2a43b38f119f9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:12:44 +0000 Subject: [PATCH 010/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4418a538..d3fa1108 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8b225e5605bed3927beaca916c03c525df08ba114ea1e2cd07765e6c3379bbc0.yml -openapi_spec_hash: 042770ee09fbd932ca8b80b9257cbaa1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fbbc6e51d3843a0c5cfe536cebd5a2b4d4355664e0a13bd41b9f5ef3928b8fac.yml +openapi_spec_hash: fc924505111b4b639ccb16e9d1d04c28 config_hash: d834b8e3612580690c900b6c3f6423dd From 3723b842591ac2f65ee1facb1efa6fa8029821dd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 00:30:05 +0000 Subject: [PATCH 011/174] feat(api): manual updates --- .stats.yml | 4 ++-- src/reducto/_client.py | 8 -------- src/reducto/types/shared/pipeline_response.py | 12 ++++++------ 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.stats.yml b/.stats.yml index d3fa1108..3f3c800e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fbbc6e51d3843a0c5cfe536cebd5a2b4d4355664e0a13bd41b9f5ef3928b8fac.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d9ba576df67b3485645463e410fc45736df4fe91343389ce968ad81556c6536d.yml openapi_spec_hash: fc924505111b4b639ccb16e9d1d04c28 -config_hash: d834b8e3612580690c900b6c3f6423dd +config_hash: f76bb083318051c2e524ba180e60f61b diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 03182941..901feebe 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -335,10 +335,6 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self.post( "/upload", body=maybe_transform({"file": file}, client_upload_params.ClientUploadParams), @@ -642,10 +638,6 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self.post( "/upload", body=await async_maybe_transform({"file": file}, client_upload_params.ClientUploadParams), diff --git a/src/reducto/types/shared/pipeline_response.py b/src/reducto/types/shared/pipeline_response.py index 5b9b44de..3e6d9c59 100644 --- a/src/reducto/types/shared/pipeline_response.py +++ b/src/reducto/types/shared/pipeline_response.py @@ -15,27 +15,27 @@ "PipelineResponse", "Result", "ResultExtract", - "ResultExtractExtractVariant0", - "ResultExtractExtractVariant0Result", + "ResultExtractUnionMember0", + "ResultExtractUnionMember0Result", "ResultParse", ] -ResultExtractExtractVariant0Result: TypeAlias = Union[ExtractResponse, V3Extract] +ResultExtractUnionMember0Result: TypeAlias = Union[ExtractResponse, V3Extract] -class ResultExtractExtractVariant0(BaseModel): +class ResultExtractUnionMember0(BaseModel): """This is the response format for Extract -> Split Pipelines""" page_range: List[int] - result: ResultExtractExtractVariant0Result + result: ResultExtractUnionMember0Result split_name: str partition: Optional[str] = None -ResultExtract: TypeAlias = Union[List[ResultExtractExtractVariant0], ExtractResponse, V3Extract, None] +ResultExtract: TypeAlias = Union[List[ResultExtractUnionMember0], ExtractResponse, V3Extract, None] ResultParse: TypeAlias = Union[ParseResponse, List[ParseResponse], None] From f2797e33461b14884c5579ab4a359c5efcca243c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 02:07:02 +0000 Subject: [PATCH 012/174] feat(internal): implement indices array format for query and form serialization --- src/reducto/_qs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reducto/_qs.py b/src/reducto/_qs.py index ada6fd3f..de8c99bc 100644 --- a/src/reducto/_qs.py +++ b/src/reducto/_qs.py @@ -101,7 +101,10 @@ def _stringify_item( items.extend(self._stringify_item(key, item, opts)) return items elif array_format == "indices": - raise NotImplementedError("The array indices format is not supported yet") + items = [] + for i, item in enumerate(value): + items.extend(self._stringify_item(f"{key}[{i}]", item, opts)) + return items elif array_format == "brackets": items = [] key = key + "[]" From 28addfc762614ad842c03b92dfaa7bce30148720 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 03:12:45 +0000 Subject: [PATCH 013/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3f3c800e..3fb3d21b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d9ba576df67b3485645463e410fc45736df4fe91343389ce968ad81556c6536d.yml -openapi_spec_hash: fc924505111b4b639ccb16e9d1d04c28 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-18ef41f68f14067ec238d4c7e6e5b59466854e5f080f72eaeeb3d1d88d26123f.yml +openapi_spec_hash: b203883525d78c439b26f139314ddb86 config_hash: f76bb083318051c2e524ba180e60f61b From c82f80be627c8fe0703d9174a5d601d5038c2835 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 06:12:33 +0000 Subject: [PATCH 014/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3fb3d21b..d787aa05 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-18ef41f68f14067ec238d4c7e6e5b59466854e5f080f72eaeeb3d1d88d26123f.yml -openapi_spec_hash: b203883525d78c439b26f139314ddb86 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c5d5987e2fc9ad601086745c1b6f1d9abefed3891565e427946c8e7849b1bec7.yml +openapi_spec_hash: 666167372dafb51e8a89108b559e1d39 config_hash: f76bb083318051c2e524ba180e60f61b From 667ec6b63c5da56c9d14851b656d46548fd54c80 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 19:53:23 +0000 Subject: [PATCH 015/174] feat(api): manual updates --- .stats.yml | 2 +- api.md | 7 +------ src/reducto/types/__init__.py | 2 +- src/reducto/types/classify_run_params.py | 4 ++-- src/reducto/types/settings_param.py | 4 ++-- src/reducto/types/shared/__init__.py | 1 + .../types/shared/advanced_processing_options.py | 2 +- src/reducto/types/shared/page_range.py | 15 +++++++++++++++ src/reducto/types/shared_params/__init__.py | 1 + .../page_range.py} | 4 ++-- 10 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 src/reducto/types/shared/page_range.py rename src/reducto/types/{page_range_param.py => shared_params/page_range.py} (83%) diff --git a/.stats.yml b/.stats.yml index d787aa05..2c36e8b9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c5d5987e2fc9ad601086745c1b6f1d9abefed3891565e427946c8e7849b1bec7.yml openapi_spec_hash: 666167372dafb51e8a89108b559e1d39 -config_hash: f76bb083318051c2e524ba180e60f61b +config_hash: e28e0679bfb60c6dd4ae34b9fc8b85c1 diff --git a/api.md b/api.md index 7f279889..2b7460ac 100644 --- a/api.md +++ b/api.md @@ -22,6 +22,7 @@ from reducto.types import ( FigureAgentic, FigureSummaryConfig, LargeTableChunkingConfig, + PageRange, ParseResponse, PipelineResponse, SplitLargeTables, @@ -132,12 +133,6 @@ Methods: # Classify -Types: - -```python -from reducto.types import PageRange -``` - Methods: - client.classify.run(\*\*params) -> ClassifyResponse diff --git a/src/reducto/types/__init__.py b/src/reducto/types/__init__.py index bf5908b3..c0641ad1 100644 --- a/src/reducto/types/__init__.py +++ b/src/reducto/types/__init__.py @@ -5,6 +5,7 @@ from .shared import ( Upload as Upload, Chunking as Chunking, + PageRange as PageRange, TextAgentic as TextAgentic, EditResponse as EditResponse, EnrichConfig as EnrichConfig, @@ -45,7 +46,6 @@ from .retrieval_param import RetrievalParam as RetrievalParam from .formatting_param import FormattingParam as FormattingParam from .job_get_response import JobGetResponse as JobGetResponse -from .page_range_param import PageRangeParam as PageRangeParam from .parse_run_params import ParseRunParams as ParseRunParams from .split_run_params import SplitRunParams as SplitRunParams from .edit_widget_param import EditWidgetParam as EditWidgetParam diff --git a/src/reducto/types/classify_run_params.py b/src/reducto/types/classify_run_params.py index 5c3db299..3bf70235 100644 --- a/src/reducto/types/classify_run_params.py +++ b/src/reducto/types/classify_run_params.py @@ -6,7 +6,7 @@ from typing_extensions import Required, TypeAlias, TypedDict from .._types import SequenceNotStr -from .page_range_param import PageRangeParam +from .shared_params import page_range from .shared_params.upload import Upload __all__ = ["ClassifyRunParams", "Input", "ClassificationSchema", "PageRange"] @@ -63,4 +63,4 @@ class ClassificationSchema(TypedDict, total=False): """ -PageRange: TypeAlias = Union[PageRangeParam, Iterable[PageRangeParam], Iterable[int]] +PageRange: TypeAlias = Union[page_range.PageRange, Iterable[page_range.PageRange], Iterable[int]] diff --git a/src/reducto/types/settings_param.py b/src/reducto/types/settings_param.py index efa50cad..93130646 100644 --- a/src/reducto/types/settings_param.py +++ b/src/reducto/types/settings_param.py @@ -6,11 +6,11 @@ from typing_extensions import Literal, TypeAlias, TypedDict from .._types import SequenceNotStr -from .page_range_param import PageRangeParam +from .shared_params import page_range __all__ = ["SettingsParam", "PageRange"] -PageRange: TypeAlias = Union[PageRangeParam, Iterable[PageRangeParam], Iterable[int], SequenceNotStr[str]] +PageRange: TypeAlias = Union[page_range.PageRange, Iterable[page_range.PageRange], Iterable[int], SequenceNotStr[str]] class SettingsParam(TypedDict, total=False): diff --git a/src/reducto/types/shared/__init__.py b/src/reducto/types/shared/__init__.py index 9bb36d61..bb73a836 100644 --- a/src/reducto/types/shared/__init__.py +++ b/src/reducto/types/shared/__init__.py @@ -2,6 +2,7 @@ from .upload import Upload as Upload from .chunking import Chunking as Chunking +from .page_range import PageRange as PageRange from .text_agentic import TextAgentic as TextAgentic from .edit_response import EditResponse as EditResponse from .enrich_config import EnrichConfig as EnrichConfig diff --git a/src/reducto/types/shared/advanced_processing_options.py b/src/reducto/types/shared/advanced_processing_options.py index 0fbb39d2..9b55e5bb 100644 --- a/src/reducto/types/shared/advanced_processing_options.py +++ b/src/reducto/types/shared/advanced_processing_options.py @@ -3,7 +3,7 @@ from typing import List, Union, Optional from typing_extensions import Literal, TypeAlias -from .. import page_range +from . import page_range from ..._models import BaseModel from .large_table_chunking_config import LargeTableChunkingConfig diff --git a/src/reducto/types/shared/page_range.py b/src/reducto/types/shared/page_range.py new file mode 100644 index 00000000..9ecdd60a --- /dev/null +++ b/src/reducto/types/shared/page_range.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["PageRange"] + + +class PageRange(BaseModel): + end: Optional[int] = None + """The page number to stop processing at (1-indexed).""" + + start: Optional[int] = None + """The page number to start processing from (1-indexed).""" diff --git a/src/reducto/types/shared_params/__init__.py b/src/reducto/types/shared_params/__init__.py index 46da23a2..4a4299d3 100644 --- a/src/reducto/types/shared_params/__init__.py +++ b/src/reducto/types/shared_params/__init__.py @@ -2,6 +2,7 @@ from .upload import Upload as Upload from .chunking import Chunking as Chunking +from .page_range import PageRange as PageRange from .text_agentic import TextAgentic as TextAgentic from .table_agentic import TableAgentic as TableAgentic from .figure_agentic import FigureAgentic as FigureAgentic diff --git a/src/reducto/types/page_range_param.py b/src/reducto/types/shared_params/page_range.py similarity index 83% rename from src/reducto/types/page_range_param.py rename to src/reducto/types/shared_params/page_range.py index d2b9e2a5..780830b7 100644 --- a/src/reducto/types/page_range_param.py +++ b/src/reducto/types/shared_params/page_range.py @@ -5,10 +5,10 @@ from typing import Optional from typing_extensions import TypedDict -__all__ = ["PageRangeParam"] +__all__ = ["PageRange"] -class PageRangeParam(TypedDict, total=False): +class PageRange(TypedDict, total=False): end: Optional[int] """The page number to stop processing at (1-indexed).""" From 5b02f1db6261db6be146df6775e5e3dc781f1e44 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:12:44 +0000 Subject: [PATCH 016/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2c36e8b9..7986aa13 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c5d5987e2fc9ad601086745c1b6f1d9abefed3891565e427946c8e7849b1bec7.yml -openapi_spec_hash: 666167372dafb51e8a89108b559e1d39 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-126ed9be51026d3941fd2cf4c40fddd078468b50d7b1eeb9d17bb6a1eb557c31.yml +openapi_spec_hash: 26a2d8b8a3379835f979376c1944e580 config_hash: e28e0679bfb60c6dd4ae34b9fc8b85c1 From 652c7fa77ac616c467a7d3ce18a8a51f4d9390ef Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:12:32 +0000 Subject: [PATCH 017/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7986aa13..4f043ee6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-126ed9be51026d3941fd2cf4c40fddd078468b50d7b1eeb9d17bb6a1eb557c31.yml -openapi_spec_hash: 26a2d8b8a3379835f979376c1944e580 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-3ff774ca90d4e60be4d3ed906c6429869bca25931768589f3eb92820409f27c5.yml +openapi_spec_hash: ba3f8545296cb2ce3be8b1fa229e3067 config_hash: e28e0679bfb60c6dd4ae34b9fc8b85c1 From e90de06a7fbf4400a6131f9fae2dda9f413f5ca4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:24:58 +0000 Subject: [PATCH 018/174] feat(api): manual updates --- .stats.yml | 4 ++-- src/reducto/_client.py | 20 ++++++++++++-------- src/reducto/types/client_upload_params.py | 8 +++++--- tests/api_resources/test_client.py | 8 ++++---- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4f043ee6..3c8da565 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-3ff774ca90d4e60be4d3ed906c6429869bca25931768589f3eb92820409f27c5.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a86cf34fabca37ac3989a521fc3854365234baefa68ccb2cb222a17215695996.yml openapi_spec_hash: ba3f8545296cb2ce3be8b1fa229e3067 -config_hash: e28e0679bfb60c6dd4ae34b9fc8b85c1 +config_hash: 7330dc57143a8dfc217966cfe5337cbe diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 901feebe..85492ef4 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -314,8 +314,8 @@ def api_version( def upload( self, *, - extension: Optional[str] | Omit = omit, - file: Optional[str] | Omit = omit, + query_extension: Optional[str] | Omit = omit, + body_extension: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -337,13 +337,13 @@ def upload( """ return self.post( "/upload", - body=maybe_transform({"file": file}, client_upload_params.ClientUploadParams), + body=maybe_transform({"body_extension": body_extension}, client_upload_params.ClientUploadParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"extension": extension}, client_upload_params.ClientUploadParams), + query=maybe_transform({"query_extension": query_extension}, client_upload_params.ClientUploadParams), ), cast_to=Upload, ) @@ -617,8 +617,8 @@ async def api_version( async def upload( self, *, - extension: Optional[str] | Omit = omit, - file: Optional[str] | Omit = omit, + query_extension: Optional[str] | Omit = omit, + body_extension: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -640,13 +640,17 @@ async def upload( """ return await self.post( "/upload", - body=await async_maybe_transform({"file": file}, client_upload_params.ClientUploadParams), + body=await async_maybe_transform( + {"body_extension": body_extension}, client_upload_params.ClientUploadParams + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"extension": extension}, client_upload_params.ClientUploadParams), + query=await async_maybe_transform( + {"query_extension": query_extension}, client_upload_params.ClientUploadParams + ), ), cast_to=Upload, ) diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index 60f0acc0..5b2d1a95 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -3,12 +3,14 @@ from __future__ import annotations from typing import Optional -from typing_extensions import TypedDict +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo __all__ = ["ClientUploadParams"] class ClientUploadParams(TypedDict, total=False): - extension: Optional[str] + query_extension: Annotated[Optional[str], PropertyInfo(alias="extension")] - file: Optional[str] + body_extension: Annotated[str, PropertyInfo(alias="extension")] diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py index 66d4adb5..2d766d22 100644 --- a/tests/api_resources/test_client.py +++ b/tests/api_resources/test_client.py @@ -55,8 +55,8 @@ def test_method_upload(self, client: Reducto) -> None: @parametrize def test_method_upload_with_all_params(self, client: Reducto) -> None: client_ = client.upload( - extension="extension", - file="file", + query_extension="extension", + body_extension="extension", ) assert_matches_type(Upload, client_, path=["response"]) @@ -126,8 +126,8 @@ async def test_method_upload(self, async_client: AsyncReducto) -> None: @parametrize async def test_method_upload_with_all_params(self, async_client: AsyncReducto) -> None: client = await async_client.upload( - extension="extension", - file="file", + query_extension="extension", + body_extension="extension", ) assert_matches_type(Upload, client, path=["response"]) From 7ce796447c358454b84727779f39a904821f61b6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:44:22 +0000 Subject: [PATCH 019/174] feat(api): manual updates --- .stats.yml | 4 ++-- src/reducto/_client.py | 16 ++++------------ src/reducto/types/client_upload_params.py | 8 ++------ tests/api_resources/test_client.py | 6 ++---- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3c8da565..c7da94a9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a86cf34fabca37ac3989a521fc3854365234baefa68ccb2cb222a17215695996.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a54f1f52c65d01920d2b162072f6ed6a24f9a1d7554ecaaacb3796c53a8cb3a5.yml openapi_spec_hash: ba3f8545296cb2ce3be8b1fa229e3067 -config_hash: 7330dc57143a8dfc217966cfe5337cbe +config_hash: 136e091062372a1bd8ec2650e62f3682 diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 85492ef4..693585e4 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -314,8 +314,7 @@ def api_version( def upload( self, *, - query_extension: Optional[str] | Omit = omit, - body_extension: str | Omit = omit, + extension: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -337,13 +336,12 @@ def upload( """ return self.post( "/upload", - body=maybe_transform({"body_extension": body_extension}, client_upload_params.ClientUploadParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"query_extension": query_extension}, client_upload_params.ClientUploadParams), + query=maybe_transform({"extension": extension}, client_upload_params.ClientUploadParams), ), cast_to=Upload, ) @@ -617,8 +615,7 @@ async def api_version( async def upload( self, *, - query_extension: Optional[str] | Omit = omit, - body_extension: str | Omit = omit, + extension: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -640,17 +637,12 @@ async def upload( """ return await self.post( "/upload", - body=await async_maybe_transform( - {"body_extension": body_extension}, client_upload_params.ClientUploadParams - ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( - {"query_extension": query_extension}, client_upload_params.ClientUploadParams - ), + query=await async_maybe_transform({"extension": extension}, client_upload_params.ClientUploadParams), ), cast_to=Upload, ) diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index 5b2d1a95..28604d73 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -3,14 +3,10 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Annotated, TypedDict - -from .._utils import PropertyInfo +from typing_extensions import TypedDict __all__ = ["ClientUploadParams"] class ClientUploadParams(TypedDict, total=False): - query_extension: Annotated[Optional[str], PropertyInfo(alias="extension")] - - body_extension: Annotated[str, PropertyInfo(alias="extension")] + extension: Optional[str] diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py index 2d766d22..ff83df25 100644 --- a/tests/api_resources/test_client.py +++ b/tests/api_resources/test_client.py @@ -55,8 +55,7 @@ def test_method_upload(self, client: Reducto) -> None: @parametrize def test_method_upload_with_all_params(self, client: Reducto) -> None: client_ = client.upload( - query_extension="extension", - body_extension="extension", + extension="extension", ) assert_matches_type(Upload, client_, path=["response"]) @@ -126,8 +125,7 @@ async def test_method_upload(self, async_client: AsyncReducto) -> None: @parametrize async def test_method_upload_with_all_params(self, async_client: AsyncReducto) -> None: client = await async_client.upload( - query_extension="extension", - body_extension="extension", + extension="extension", ) assert_matches_type(Upload, client, path=["response"]) From 161771813714c42fc4ff2a958df0d9a511bd7f20 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:12:52 +0000 Subject: [PATCH 020/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c7da94a9..4305c810 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a54f1f52c65d01920d2b162072f6ed6a24f9a1d7554ecaaacb3796c53a8cb3a5.yml -openapi_spec_hash: ba3f8545296cb2ce3be8b1fa229e3067 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-43b98d339d25aeb17ffb139b18e0791cf571ab28b07f16084cab1ff9dd317ec3.yml +openapi_spec_hash: ab5973b4c1ef10071533ae8c9930b57a config_hash: 136e091062372a1bd8ec2650e62f3682 From a909df10cf41a8c4b6607fb78c0d9bc74adf6976 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:23:21 +0000 Subject: [PATCH 021/174] feat(api): manual updates --- .stats.yml | 4 ++-- src/reducto/_client.py | 12 ++++++++++++ src/reducto/types/client_upload_params.py | 2 ++ tests/api_resources/test_client.py | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4305c810..dbe0f27e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-43b98d339d25aeb17ffb139b18e0791cf571ab28b07f16084cab1ff9dd317ec3.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d361c5bf5361d351ecc5f2e18061d369315c6e4d95d93ebe3085c8cbc08bbf36.yml openapi_spec_hash: ab5973b4c1ef10071533ae8c9930b57a -config_hash: 136e091062372a1bd8ec2650e62f3682 +config_hash: cccd14eaf46aa0acb1a1c4f71172eeb5 diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 693585e4..03182941 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -315,6 +315,7 @@ def upload( self, *, extension: Optional[str] | Omit = omit, + file: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -334,8 +335,13 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self.post( "/upload", + body=maybe_transform({"file": file}, client_upload_params.ClientUploadParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -616,6 +622,7 @@ async def upload( self, *, extension: Optional[str] | Omit = omit, + file: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -635,8 +642,13 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self.post( "/upload", + body=await async_maybe_transform({"file": file}, client_upload_params.ClientUploadParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index 28604d73..60f0acc0 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -10,3 +10,5 @@ class ClientUploadParams(TypedDict, total=False): extension: Optional[str] + + file: Optional[str] diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py index ff83df25..66d4adb5 100644 --- a/tests/api_resources/test_client.py +++ b/tests/api_resources/test_client.py @@ -56,6 +56,7 @@ def test_method_upload(self, client: Reducto) -> None: def test_method_upload_with_all_params(self, client: Reducto) -> None: client_ = client.upload( extension="extension", + file="file", ) assert_matches_type(Upload, client_, path=["response"]) @@ -126,6 +127,7 @@ async def test_method_upload(self, async_client: AsyncReducto) -> None: async def test_method_upload_with_all_params(self, async_client: AsyncReducto) -> None: client = await async_client.upload( extension="extension", + file="file", ) assert_matches_type(Upload, client, path=["response"]) From 052bec3b9ae88969f401a8fdea8bc67f126e5d1b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 22:52:08 +0000 Subject: [PATCH 022/174] feat(api): manual updates --- .stats.yml | 4 ++-- README.md | 17 +++++++++++++++++ src/reducto/_client.py | 17 +++++++++++++---- src/reducto/_files.py | 2 +- src/reducto/types/client_upload_params.py | 4 +++- tests/api_resources/test_client.py | 4 ++-- 6 files changed, 38 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index dbe0f27e..b3adb0db 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d361c5bf5361d351ecc5f2e18061d369315c6e4d95d93ebe3085c8cbc08bbf36.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-53cf8c795b3f5c8f8bf7a9e700054d5596ae12beeb10a63ab3935dba6b11dae5.yml openapi_spec_hash: ab5973b4c1ef10071533ae8c9930b57a -config_hash: cccd14eaf46aa0acb1a1c4f71172eeb5 +config_hash: 9ea0deb1ca46e81666fa608a786afad5 diff --git a/README.md b/README.md index 16329cb2..f0fc57fd 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,23 @@ response = client.parse.run( print(response.enhance) ``` +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from reducto import Reducto + +client = Reducto() + +client.upload( + file=Path("/path/to/file"), +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `reducto.APIConnectionError` is raised. diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 03182941..76385f5a 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -18,6 +18,7 @@ Headers, Timeout, NotGiven, + FileTypes, Transport, ProxiesTypes, RequestOptions, @@ -26,7 +27,9 @@ ) from ._utils import ( is_given, + extract_files, maybe_transform, + deepcopy_minimal, get_async_library, async_maybe_transform, ) @@ -315,7 +318,7 @@ def upload( self, *, extension: Optional[str] | Omit = omit, - file: Optional[str] | Omit = omit, + file: FileTypes | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -335,13 +338,16 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ + body = deepcopy_minimal({"file": file}) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self.post( "/upload", - body=maybe_transform({"file": file}, client_upload_params.ClientUploadParams), + body=maybe_transform(body, client_upload_params.ClientUploadParams), + files=files, options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -622,7 +628,7 @@ async def upload( self, *, extension: Optional[str] | Omit = omit, - file: Optional[str] | Omit = omit, + file: FileTypes | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -642,13 +648,16 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ + body = deepcopy_minimal({"file": file}) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self.post( "/upload", - body=await async_maybe_transform({"file": file}, client_upload_params.ClientUploadParams), + body=await async_maybe_transform(body, client_upload_params.ClientUploadParams), + files=files, options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, diff --git a/src/reducto/_files.py b/src/reducto/_files.py index cc14c14f..7f056768 100644 --- a/src/reducto/_files.py +++ b/src/reducto/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/reductoai/reducto-python-sdk/tree/main#file-uploads" ) from None diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index 60f0acc0..ee382e87 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -5,10 +5,12 @@ from typing import Optional from typing_extensions import TypedDict +from .._types import FileTypes + __all__ = ["ClientUploadParams"] class ClientUploadParams(TypedDict, total=False): extension: Optional[str] - file: Optional[str] + file: FileTypes diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py index 66d4adb5..4d11b754 100644 --- a/tests/api_resources/test_client.py +++ b/tests/api_resources/test_client.py @@ -56,7 +56,7 @@ def test_method_upload(self, client: Reducto) -> None: def test_method_upload_with_all_params(self, client: Reducto) -> None: client_ = client.upload( extension="extension", - file="file", + file=b"Example data", ) assert_matches_type(Upload, client_, path=["response"]) @@ -127,7 +127,7 @@ async def test_method_upload(self, async_client: AsyncReducto) -> None: async def test_method_upload_with_all_params(self, async_client: AsyncReducto) -> None: client = await async_client.upload( extension="extension", - file="file", + file=b"Example data", ) assert_matches_type(Upload, client, path=["response"]) From 605ccec35afb086a1228b8ffba9bcbf1ec31344d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:12:41 +0000 Subject: [PATCH 023/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b3adb0db..3f350c75 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-53cf8c795b3f5c8f8bf7a9e700054d5596ae12beeb10a63ab3935dba6b11dae5.yml -openapi_spec_hash: ab5973b4c1ef10071533ae8c9930b57a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9eee1ac5ec5dd13e71079d1b3e970b26f818454e1ea891200a5b6091d960aa93.yml +openapi_spec_hash: b29da1c900ba9e9ed8903ac196566e09 config_hash: 9ea0deb1ca46e81666fa608a786afad5 From 6f041cda38e43327c6363ee32055ca0a8881ae13 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:25:43 +0000 Subject: [PATCH 024/174] feat(api): manual updates --- .stats.yml | 4 ++-- README.md | 17 -------------- src/reducto/_client.py | 27 ++++++++++------------- src/reducto/_files.py | 2 +- src/reducto/types/client_upload_params.py | 4 ---- tests/api_resources/test_client.py | 2 -- 6 files changed, 15 insertions(+), 41 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3f350c75..ee309be3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9eee1ac5ec5dd13e71079d1b3e970b26f818454e1ea891200a5b6091d960aa93.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2e2e09bd6ce632bb9bc03ec212f13825bd001bc767a1e8a77a9d4644b9514f7e.yml openapi_spec_hash: b29da1c900ba9e9ed8903ac196566e09 -config_hash: 9ea0deb1ca46e81666fa608a786afad5 +config_hash: 6628388e7d43dd3e500cfd9453077431 diff --git a/README.md b/README.md index f0fc57fd..16329cb2 100644 --- a/README.md +++ b/README.md @@ -129,23 +129,6 @@ response = client.parse.run( print(response.enhance) ``` -## File uploads - -Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. - -```python -from pathlib import Path -from reducto import Reducto - -client = Reducto() - -client.upload( - file=Path("/path/to/file"), -) -``` - -The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. - ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `reducto.APIConnectionError` is raised. diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 76385f5a..a8d71ab0 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -18,7 +18,6 @@ Headers, Timeout, NotGiven, - FileTypes, Transport, ProxiesTypes, RequestOptions, @@ -318,7 +317,6 @@ def upload( self, *, extension: Optional[str] | Omit = omit, - file: FileTypes | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -338,15 +336,15 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal({"file": file}) + body = deepcopy_minimal({}) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + if files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self.post( "/upload", - body=maybe_transform(body, client_upload_params.ClientUploadParams), files=files, options=make_request_options( extra_headers=extra_headers, @@ -628,7 +626,6 @@ async def upload( self, *, extension: Optional[str] | Omit = omit, - file: FileTypes | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -648,15 +645,15 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal({"file": file}) + body = deepcopy_minimal({}) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + if files: + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self.post( "/upload", - body=await async_maybe_transform(body, client_upload_params.ClientUploadParams), files=files, options=make_request_options( extra_headers=extra_headers, diff --git a/src/reducto/_files.py b/src/reducto/_files.py index 7f056768..cc14c14f 100644 --- a/src/reducto/_files.py +++ b/src/reducto/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/reductoai/reducto-python-sdk/tree/main#file-uploads" + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." ) from None diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index ee382e87..28604d73 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -5,12 +5,8 @@ from typing import Optional from typing_extensions import TypedDict -from .._types import FileTypes - __all__ = ["ClientUploadParams"] class ClientUploadParams(TypedDict, total=False): extension: Optional[str] - - file: FileTypes diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py index 4d11b754..ff83df25 100644 --- a/tests/api_resources/test_client.py +++ b/tests/api_resources/test_client.py @@ -56,7 +56,6 @@ def test_method_upload(self, client: Reducto) -> None: def test_method_upload_with_all_params(self, client: Reducto) -> None: client_ = client.upload( extension="extension", - file=b"Example data", ) assert_matches_type(Upload, client_, path=["response"]) @@ -127,7 +126,6 @@ async def test_method_upload(self, async_client: AsyncReducto) -> None: async def test_method_upload_with_all_params(self, async_client: AsyncReducto) -> None: client = await async_client.upload( extension="extension", - file=b"Example data", ) assert_matches_type(Upload, client, path=["response"]) From b49d17b6d13ec8987de4a7fb78ea8b54511874ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:28:08 +0000 Subject: [PATCH 025/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ee309be3..ac28256b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2e2e09bd6ce632bb9bc03ec212f13825bd001bc767a1e8a77a9d4644b9514f7e.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ae504c29ef91c7052fc3895c37ec6eb1b13dee2052e3d226ba33a53ef44374e0.yml openapi_spec_hash: b29da1c900ba9e9ed8903ac196566e09 -config_hash: 6628388e7d43dd3e500cfd9453077431 +config_hash: fb70c4e3a4688f5ef2caf44642c5ef14 From 4abcc9e49ad3ab9de0af82ca726c8197f6189bdc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:12:42 +0000 Subject: [PATCH 026/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ac28256b..b9a8c452 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ae504c29ef91c7052fc3895c37ec6eb1b13dee2052e3d226ba33a53ef44374e0.yml -openapi_spec_hash: b29da1c900ba9e9ed8903ac196566e09 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a9ab24620fc15fee5636f8e32f9269ad1b20b6739e3703b51b6dd5e1ea232ad5.yml +openapi_spec_hash: 2fafc7e660943da5377fa589bfd4740f config_hash: fb70c4e3a4688f5ef2caf44642c5ef14 From b340e2960a09e361b55a8bfe659a781163cc530d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:30:58 +0000 Subject: [PATCH 027/174] feat(api): manual updates --- .stats.yml | 4 ++-- README.md | 17 +++++++++++++++++ src/reducto/_client.py | 9 +++++++-- src/reducto/_files.py | 2 +- src/reducto/types/client_upload_params.py | 4 ++++ tests/api_resources/test_client.py | 2 ++ 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index b9a8c452..cf84f435 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a9ab24620fc15fee5636f8e32f9269ad1b20b6739e3703b51b6dd5e1ea232ad5.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-bb90818383934ef29bf3083ddcc64fec8d5bfce1317a8d2256f67224e4a5ec3f.yml openapi_spec_hash: 2fafc7e660943da5377fa589bfd4740f -config_hash: fb70c4e3a4688f5ef2caf44642c5ef14 +config_hash: f9d9d0619bed0fc393a11804c0d278d8 diff --git a/README.md b/README.md index 16329cb2..f0fc57fd 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,23 @@ response = client.parse.run( print(response.enhance) ``` +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from reducto import Reducto + +client = Reducto() + +client.upload( + file=Path("/path/to/file"), +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `reducto.APIConnectionError` is raised. diff --git a/src/reducto/_client.py b/src/reducto/_client.py index a8d71ab0..df400032 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -18,6 +18,7 @@ Headers, Timeout, NotGiven, + FileTypes, Transport, ProxiesTypes, RequestOptions, @@ -317,6 +318,7 @@ def upload( self, *, extension: Optional[str] | Omit = omit, + file: Optional[FileTypes] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -336,7 +338,7 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal({}) + body = deepcopy_minimal({"file": file}) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) if files: # It should be noted that the actual Content-Type header that will be @@ -345,6 +347,7 @@ def upload( extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return self.post( "/upload", + body=maybe_transform(body, client_upload_params.ClientUploadParams), files=files, options=make_request_options( extra_headers=extra_headers, @@ -626,6 +629,7 @@ async def upload( self, *, extension: Optional[str] | Omit = omit, + file: Optional[FileTypes] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -645,7 +649,7 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal({}) + body = deepcopy_minimal({"file": file}) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) if files: # It should be noted that the actual Content-Type header that will be @@ -654,6 +658,7 @@ async def upload( extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} return await self.post( "/upload", + body=await async_maybe_transform(body, client_upload_params.ClientUploadParams), files=files, options=make_request_options( extra_headers=extra_headers, diff --git a/src/reducto/_files.py b/src/reducto/_files.py index cc14c14f..7f056768 100644 --- a/src/reducto/_files.py +++ b/src/reducto/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/reductoai/reducto-python-sdk/tree/main#file-uploads" ) from None diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index 28604d73..c8d7c223 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -5,8 +5,12 @@ from typing import Optional from typing_extensions import TypedDict +from .._types import FileTypes + __all__ = ["ClientUploadParams"] class ClientUploadParams(TypedDict, total=False): extension: Optional[str] + + file: Optional[FileTypes] diff --git a/tests/api_resources/test_client.py b/tests/api_resources/test_client.py index ff83df25..4d11b754 100644 --- a/tests/api_resources/test_client.py +++ b/tests/api_resources/test_client.py @@ -56,6 +56,7 @@ def test_method_upload(self, client: Reducto) -> None: def test_method_upload_with_all_params(self, client: Reducto) -> None: client_ = client.upload( extension="extension", + file=b"Example data", ) assert_matches_type(Upload, client_, path=["response"]) @@ -126,6 +127,7 @@ async def test_method_upload(self, async_client: AsyncReducto) -> None: async def test_method_upload_with_all_params(self, async_client: AsyncReducto) -> None: client = await async_client.upload( extension="extension", + file=b"Example data", ) assert_matches_type(Upload, client, path=["response"]) From 38e54a69a6d1802507221750f06c009151af67b3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:31:24 +0000 Subject: [PATCH 028/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index cf84f435..b9f5a547 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-bb90818383934ef29bf3083ddcc64fec8d5bfce1317a8d2256f67224e4a5ec3f.yml openapi_spec_hash: 2fafc7e660943da5377fa589bfd4740f -config_hash: f9d9d0619bed0fc393a11804c0d278d8 +config_hash: cad5f4ff48d645fed82e0b5a3ce372f9 From 39eab345631075e26a84b2241b97a627913d48bd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:53:24 +0000 Subject: [PATCH 029/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b9f5a547..09a2a5e1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-bb90818383934ef29bf3083ddcc64fec8d5bfce1317a8d2256f67224e4a5ec3f.yml -openapi_spec_hash: 2fafc7e660943da5377fa589bfd4740f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-992916d94b91432de757046f564548f45d8b6d4d5aea01203bf7d27e21942124.yml +openapi_spec_hash: 24893057bf77963f2faa7826da8ab613 config_hash: cad5f4ff48d645fed82e0b5a3ce372f9 From b5f97b6532cd2c14b8b2a3452b3b3888bd102287 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:57:41 +0000 Subject: [PATCH 030/174] feat(api): remove transforms --- .stats.yml | 4 ++-- src/reducto/_client.py | 6 +++--- src/reducto/types/client_upload_params.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index 09a2a5e1..4b4beb57 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-992916d94b91432de757046f564548f45d8b6d4d5aea01203bf7d27e21942124.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-76bbc51e6600082a94730440989a91428a12b260a209e51c22555a4cc909832d.yml openapi_spec_hash: 24893057bf77963f2faa7826da8ab613 -config_hash: cad5f4ff48d645fed82e0b5a3ce372f9 +config_hash: 3b49f91449808ba96507803f82568644 diff --git a/src/reducto/_client.py b/src/reducto/_client.py index df400032..1d2fc769 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, Union, Mapping, Optional, cast from typing_extensions import Self, Literal, override import httpx @@ -318,7 +318,7 @@ def upload( self, *, extension: Optional[str] | Omit = omit, - file: Optional[FileTypes] | Omit = omit, + file: Union[FileTypes, str, None] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -629,7 +629,7 @@ async def upload( self, *, extension: Optional[str] | Omit = omit, - file: Optional[FileTypes] | Omit = omit, + file: Union[FileTypes, str, None] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index c8d7c223..42bf0e4f 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Union, Optional from typing_extensions import TypedDict from .._types import FileTypes @@ -13,4 +13,4 @@ class ClientUploadParams(TypedDict, total=False): extension: Optional[str] - file: Optional[FileTypes] + file: Union[FileTypes, str, None] From 077905adf829d70f959cfe948cb82c456657b6a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 03:12:43 +0000 Subject: [PATCH 031/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4b4beb57..d95c1948 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-76bbc51e6600082a94730440989a91428a12b260a209e51c22555a4cc909832d.yml -openapi_spec_hash: 24893057bf77963f2faa7826da8ab613 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-443f374b0c2a78740603ae2891fdee0cfe0a3bc5b408755407db96f45990703b.yml +openapi_spec_hash: d36bb7ea749be1666b0cf4f841d8eacc config_hash: 3b49f91449808ba96507803f82568644 From 51af8cd33ca846dc5c77bab77ddf2ad4d88592b6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Mar 2026 04:12:43 +0000 Subject: [PATCH 032/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d95c1948..f142c5d6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-443f374b0c2a78740603ae2891fdee0cfe0a3bc5b408755407db96f45990703b.yml -openapi_spec_hash: d36bb7ea749be1666b0cf4f841d8eacc +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-1d5e2e8d77f6b9c125732d572cc4c09848dfaad73eca06825cd7f69f3c34766b.yml +openapi_spec_hash: 10479661e623fc6063eafbba3f2b5dff config_hash: 3b49f91449808ba96507803f82568644 From 25bffaa2a63f28e3e7dae795d48a41910656d1a1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:53:05 +0000 Subject: [PATCH 033/174] feat(api): Add transform to fix file upload type for /upload endpoint FastAPI generates anyOf with both format:binary and plain string for the file field, causing Stainless to collapse it to Optional[str]. This transform removes the plain string variant so Stainless generates proper file upload types and uses conditional multipart/JSON encoding. Co-Authored-By: Claude Opus 4.6 (1M context) --- .stats.yml | 4 ++-- src/reducto/_client.py | 6 +++--- src/reducto/types/client_upload_params.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index f142c5d6..25e5fbc8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-1d5e2e8d77f6b9c125732d572cc4c09848dfaad73eca06825cd7f69f3c34766b.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9dcbb133ea8d4e419314a5fddc878258610a80acfe82604b328a7eb3cb4f8f5f.yml openapi_spec_hash: 10479661e623fc6063eafbba3f2b5dff -config_hash: 3b49f91449808ba96507803f82568644 +config_hash: 16d0e72cd4a11e40f1c7898b6473f504 diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 1d2fc769..df400032 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, Dict, Union, Mapping, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, cast from typing_extensions import Self, Literal, override import httpx @@ -318,7 +318,7 @@ def upload( self, *, extension: Optional[str] | Omit = omit, - file: Union[FileTypes, str, None] | Omit = omit, + file: Optional[FileTypes] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -629,7 +629,7 @@ async def upload( self, *, extension: Optional[str] | Omit = omit, - file: Union[FileTypes, str, None] | Omit = omit, + file: Optional[FileTypes] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, diff --git a/src/reducto/types/client_upload_params.py b/src/reducto/types/client_upload_params.py index 42bf0e4f..c8d7c223 100644 --- a/src/reducto/types/client_upload_params.py +++ b/src/reducto/types/client_upload_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Union, Optional +from typing import Optional from typing_extensions import TypedDict from .._types import FileTypes @@ -13,4 +13,4 @@ class ClientUploadParams(TypedDict, total=False): extension: Optional[str] - file: Union[FileTypes, str, None] + file: Optional[FileTypes] From 5d16330581ea6b48f9353dfe18bca9f56529f44d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:57:12 +0000 Subject: [PATCH 034/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 25e5fbc8..d6e52f7b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9dcbb133ea8d4e419314a5fddc878258610a80acfe82604b328a7eb3cb4f8f5f.yml openapi_spec_hash: 10479661e623fc6063eafbba3f2b5dff -config_hash: 16d0e72cd4a11e40f1c7898b6473f504 +config_hash: b300a48a82edd61f59fd18cb08d5ec54 From fb504e1443f9c3e6b7b41023cb29828e67144f89 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:01:23 +0000 Subject: [PATCH 035/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d6e52f7b..d5bade16 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9dcbb133ea8d4e419314a5fddc878258610a80acfe82604b328a7eb3cb4f8f5f.yml openapi_spec_hash: 10479661e623fc6063eafbba3f2b5dff -config_hash: b300a48a82edd61f59fd18cb08d5ec54 +config_hash: 14efe98ee80f1e66ef35443e52f02953 From 8eab40efb17b26dcfe10d4b47970ae2377060963 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:23:57 +0000 Subject: [PATCH 036/174] chore(internal): version bump --- .release-please-manifest.json | 2 +- pyproject.toml | 2 +- src/reducto/_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0c2ecec6..86b0e83d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.20.0" + ".": "0.21.0" } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 22d7bf0e..13381b81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "reductoai" -version = "0.20.0" +version = "0.21.0" description = "The official Python library for the reducto API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/reducto/_version.py b/src/reducto/_version.py index 497850e3..30639123 100644 --- a/src/reducto/_version.py +++ b/src/reducto/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "reducto" -__version__ = "0.20.0" # x-release-please-version +__version__ = "0.21.0" # x-release-please-version From c1903463c5461628bed27a50723ab95f6cccadf4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 07:12:40 +0000 Subject: [PATCH 037/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d5bade16..6c587a05 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9dcbb133ea8d4e419314a5fddc878258610a80acfe82604b328a7eb3cb4f8f5f.yml -openapi_spec_hash: 10479661e623fc6063eafbba3f2b5dff +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a84151f6a38bf7bf51ba18b5f89953041d0fa0d55e20e0963e7a6e4e879d7f92.yml +openapi_spec_hash: b7b27b00a0ba1db2bf4ead8d99e169c8 config_hash: 14efe98ee80f1e66ef35443e52f02953 From ccfb6ede2d2b6102f4043c74c8ed5c5458527643 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 22:12:45 +0000 Subject: [PATCH 038/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6c587a05..20caf862 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a84151f6a38bf7bf51ba18b5f89953041d0fa0d55e20e0963e7a6e4e879d7f92.yml -openapi_spec_hash: b7b27b00a0ba1db2bf4ead8d99e169c8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-5725f0597f674c6e948d7eeea4a7083a5961701f1ab41a4925ee8e7ec0c51e29.yml +openapi_spec_hash: bead409a858bc21c2f9d8c48747a6109 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 9774c00b388d7ab25129b59dfabb64c96ccafa7a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 18:12:53 +0000 Subject: [PATCH 039/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 20caf862..00e9a54d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-5725f0597f674c6e948d7eeea4a7083a5961701f1ab41a4925ee8e7ec0c51e29.yml -openapi_spec_hash: bead409a858bc21c2f9d8c48747a6109 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-aa7a5b73db1eaec68233c620991a8534ec0d1d2bfa2cac7c19c8c6afe45b4885.yml +openapi_spec_hash: 2278aecc02ea9ce18c0e4aac3a19b3d8 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 67af67698577b517aa7e0167b3c63b6a786caf38 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 19:12:52 +0000 Subject: [PATCH 040/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 00e9a54d..e9ee073d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-aa7a5b73db1eaec68233c620991a8534ec0d1d2bfa2cac7c19c8c6afe45b4885.yml -openapi_spec_hash: 2278aecc02ea9ce18c0e4aac3a19b3d8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9785c1c79da8b3aa66bbd477b412199aef228ba5d6c76e22c42c2b86486e99b0.yml +openapi_spec_hash: 225134420d5dc6505a3a3070b4630e7f config_hash: 14efe98ee80f1e66ef35443e52f02953 From bfc847b77ed22462433e941635cde0193d18942e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 20:12:55 +0000 Subject: [PATCH 041/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e9ee073d..04504ec3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9785c1c79da8b3aa66bbd477b412199aef228ba5d6c76e22c42c2b86486e99b0.yml -openapi_spec_hash: 225134420d5dc6505a3a3070b4630e7f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f6578cd4a0fb47032d22aef27e037ffccc5f867c389009734a2d664fadfa2edf.yml +openapi_spec_hash: f39d514ea25d9971f63ccba74c385055 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 0c01abd7eacb950871c8ca7403329f1f1709b877 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:12:51 +0000 Subject: [PATCH 042/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 04504ec3..c495bdb5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f6578cd4a0fb47032d22aef27e037ffccc5f867c389009734a2d664fadfa2edf.yml -openapi_spec_hash: f39d514ea25d9971f63ccba74c385055 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a013b4b2d7a191deb744e270c9e36671a56755aa1e4947aa1a6c47bb0bd74522.yml +openapi_spec_hash: 8ababe03f384d092c5087af336a04397 config_hash: 14efe98ee80f1e66ef35443e52f02953 From aaecd73cc617e2e4627d48175e7f4d0f6ade3e7f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 22:13:08 +0000 Subject: [PATCH 043/174] feat(api): api update --- .stats.yml | 4 ++-- .../types/split_table_options_param.py | 6 ++++++ tests/api_resources/test_split.py | 20 +++++++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.stats.yml b/.stats.yml index c495bdb5..ff2fb865 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-a013b4b2d7a191deb744e270c9e36671a56755aa1e4947aa1a6c47bb0bd74522.yml -openapi_spec_hash: 8ababe03f384d092c5087af336a04397 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8a67a4230c9d972fa26ce75b3202346c05c8ebbf8500d2f9cff1a9d3ae737139.yml +openapi_spec_hash: b10a686101cb072711c7ed18078d7061 config_hash: 14efe98ee80f1e66ef35443e52f02953 diff --git a/src/reducto/types/split_table_options_param.py b/src/reducto/types/split_table_options_param.py index 740e863c..a4c29707 100644 --- a/src/reducto/types/split_table_options_param.py +++ b/src/reducto/types/split_table_options_param.py @@ -8,6 +8,12 @@ class SplitTableOptionsParam(TypedDict, total=False): + allow_page_overlap: bool + """If True, a page can belong to multiple categories/partitions. + + If False, each page must belong to exactly one category. Defaults to True. + """ + table_cutoff: Literal["truncate", "preserve"] """ If tables should be truncated to the first few rows or if all content should be diff --git a/tests/api_resources/test_split.py b/tests/api_resources/test_split.py index 2de2376f..af41c588 100644 --- a/tests/api_resources/test_split.py +++ b/tests/api_resources/test_split.py @@ -95,7 +95,10 @@ def test_method_run_with_all_params(self, client: Reducto) -> None: }, }, }, - settings={"table_cutoff": "truncate"}, + settings={ + "allow_page_overlap": True, + "table_cutoff": "truncate", + }, split_rules="split_rules", ) assert_matches_type(SplitResponse, split, path=["response"]) @@ -224,7 +227,10 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: }, }, }, - settings={"table_cutoff": "truncate"}, + settings={ + "allow_page_overlap": True, + "table_cutoff": "truncate", + }, split_rules="split_rules", ) assert_matches_type(AsyncSplitResponse, split, path=["response"]) @@ -351,7 +357,10 @@ async def test_method_run_with_all_params(self, async_client: AsyncReducto) -> N }, }, }, - settings={"table_cutoff": "truncate"}, + settings={ + "allow_page_overlap": True, + "table_cutoff": "truncate", + }, split_rules="split_rules", ) assert_matches_type(SplitResponse, split, path=["response"]) @@ -480,7 +489,10 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) }, }, }, - settings={"table_cutoff": "truncate"}, + settings={ + "allow_page_overlap": True, + "table_cutoff": "truncate", + }, split_rules="split_rules", ) assert_matches_type(AsyncSplitResponse, split, path=["response"]) From 179df285bdfbf4b84ea3c00be4b9e563875e2894 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:12:58 +0000 Subject: [PATCH 044/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ff2fb865..551ff3a5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8a67a4230c9d972fa26ce75b3202346c05c8ebbf8500d2f9cff1a9d3ae737139.yml -openapi_spec_hash: b10a686101cb072711c7ed18078d7061 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-55e59e8321d55efeca390c786a15d7b5c15f3447a353e77a33692ff96ed3c7e5.yml +openapi_spec_hash: 3f0d45f30cbecb1af0376e6df4457ef6 config_hash: 14efe98ee80f1e66ef35443e52f02953 From a96d41cd8217eacf510c591271ff4357a28f18a4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 00:12:55 +0000 Subject: [PATCH 045/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/parse_usage.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 551ff3a5..6874716d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-55e59e8321d55efeca390c786a15d7b5c15f3447a353e77a33692ff96ed3c7e5.yml -openapi_spec_hash: 3f0d45f30cbecb1af0376e6df4457ef6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-6f920cbb7dfd371cb4c975f617ab7b24917ef45be5dbc2e188b84999ba99ae2a.yml +openapi_spec_hash: 7135c104dfc28ece87fa04705fe5634c config_hash: 14efe98ee80f1e66ef35443e52f02953 diff --git a/src/reducto/types/parse_usage.py b/src/reducto/types/parse_usage.py index 74abb42d..a67ffc45 100644 --- a/src/reducto/types/parse_usage.py +++ b/src/reducto/types/parse_usage.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Dict, List, Optional +from typing_extensions import Literal from .._models import BaseModel @@ -13,3 +14,19 @@ class ParseUsage(BaseModel): credit_breakdown: Optional[Dict[str, float]] = None credits: Optional[float] = None + + page_billing_breakdown: Optional[ + Dict[ + str, + List[ + Literal[ + "page", "html_page", "docx_native_page", "agentic", "complex", "chart_agent", "spreadsheet_cells" + ] + ], + ] + ] = None + """Per-page breakdown of features used. + + Maps 1-indexed page numbers (as strings) to the list of billing features applied + on that page (e.g. 'page', 'complex', 'chart_agent'). + """ From 4146c37a30aaec2fbc932c0a9805ceae6543aa7f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 17:12:46 +0000 Subject: [PATCH 046/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6874716d..a779492b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-6f920cbb7dfd371cb4c975f617ab7b24917ef45be5dbc2e188b84999ba99ae2a.yml -openapi_spec_hash: 7135c104dfc28ece87fa04705fe5634c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-72b0bcabddc0f5047bfe24ace69b39850c2bc33dde97db1879d2623c7b785158.yml +openapi_spec_hash: 974d8580e1e20098c8d1e78759b04161 config_hash: 14efe98ee80f1e66ef35443e52f02953 From fde52c2015d347bfaa2b0c2efe860bb5dfa28063 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 18:12:42 +0000 Subject: [PATCH 047/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a779492b..1a06425c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-72b0bcabddc0f5047bfe24ace69b39850c2bc33dde97db1879d2623c7b785158.yml -openapi_spec_hash: 974d8580e1e20098c8d1e78759b04161 +openapi_spec_hash: fb344cd27b1109b60ef6f07f2e66a3df config_hash: 14efe98ee80f1e66ef35443e52f02953 From 7eea3a322a5c0d66bfbbbb348341cefeef81de35 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:12:38 +0000 Subject: [PATCH 048/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1a06425c..19fc7470 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-72b0bcabddc0f5047bfe24ace69b39850c2bc33dde97db1879d2623c7b785158.yml -openapi_spec_hash: fb344cd27b1109b60ef6f07f2e66a3df +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7c9e2128f7b792b0fcb1388ff190536e566ff57bb4ecdc85e109c5b87afd7aad.yml +openapi_spec_hash: 790b761c5f52d643ad4d7319a27d1d78 config_hash: 14efe98ee80f1e66ef35443e52f02953 From fdb978057698e963cbc88d6af4b654309ac33d15 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 00:12:33 +0000 Subject: [PATCH 049/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 19fc7470..809e905a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7c9e2128f7b792b0fcb1388ff190536e566ff57bb4ecdc85e109c5b87afd7aad.yml -openapi_spec_hash: 790b761c5f52d643ad4d7319a27d1d78 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-eb0239565433f1be5464872d144a39c6fddfba871d37953e610ae4b3bcf84765.yml +openapi_spec_hash: 64160bb7ceb786d6b693520e3c6136ed config_hash: 14efe98ee80f1e66ef35443e52f02953 From 68b374ff6b8a4b8288d8dab472dbc0f6800afd18 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 01:12:33 +0000 Subject: [PATCH 050/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 809e905a..a1453de6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-eb0239565433f1be5464872d144a39c6fddfba871d37953e610ae4b3bcf84765.yml -openapi_spec_hash: 64160bb7ceb786d6b693520e3c6136ed +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f4850dffc0de034cdf31637d73868dcb6902178d9bc2d65637881203f0916a9b.yml +openapi_spec_hash: 1ec001354c7be267d736ea5bf78d252e config_hash: 14efe98ee80f1e66ef35443e52f02953 From e7d4d97beb7a23f918fff333f99efaf0f3fbbd45 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 02:12:30 +0000 Subject: [PATCH 051/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a1453de6..d540a74f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f4850dffc0de034cdf31637d73868dcb6902178d9bc2d65637881203f0916a9b.yml -openapi_spec_hash: 1ec001354c7be267d736ea5bf78d252e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7767033068b42cda48fd3e40e12aba82d51de6ce344c2e872674a1398603ea9c.yml +openapi_spec_hash: 61c87e5f6031ee998935fe8d5f984090 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 4bf4f633ab629417ec622285c426e55d6b81a226 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:12:39 +0000 Subject: [PATCH 052/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d540a74f..93ee2e21 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7767033068b42cda48fd3e40e12aba82d51de6ce344c2e872674a1398603ea9c.yml -openapi_spec_hash: 61c87e5f6031ee998935fe8d5f984090 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-291a8e226cee138c003d0803686f3f4a801be4d75d11708b8daf4f4cf2320522.yml +openapi_spec_hash: 6d642d25fe63e0e652b55111cdfc3971 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 58ca0e8b5493acbf1f56fa979b1812fbcacced2b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 06:12:40 +0000 Subject: [PATCH 053/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 93ee2e21..42949bd7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-291a8e226cee138c003d0803686f3f4a801be4d75d11708b8daf4f4cf2320522.yml -openapi_spec_hash: 6d642d25fe63e0e652b55111cdfc3971 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-795b6cf3510ca1a816f22b3bc37a74fdbb14d0278aa9fd3a5a11c70f6c51e8bd.yml +openapi_spec_hash: f6372c128d75f8ab00e345ed79eb6695 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 03f6ae96a41bffce4bd95874bf542c2a72d6f689 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:12:33 +0000 Subject: [PATCH 054/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 42949bd7..b78233ae 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-795b6cf3510ca1a816f22b3bc37a74fdbb14d0278aa9fd3a5a11c70f6c51e8bd.yml -openapi_spec_hash: f6372c128d75f8ab00e345ed79eb6695 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-37a0b358a1cdedb9e9a70fae2fc0eca28eb26abeab2ac8d58d6a845934b0a82d.yml +openapi_spec_hash: 62cdb8d681367c86cc3e01d2622468a7 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 5f4357eeec46259b85ab3e426a08f885b4dd0e88 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:12:43 +0000 Subject: [PATCH 055/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b78233ae..4065a839 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-37a0b358a1cdedb9e9a70fae2fc0eca28eb26abeab2ac8d58d6a845934b0a82d.yml -openapi_spec_hash: 62cdb8d681367c86cc3e01d2622468a7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9dcf831bd3aa27c3150266a1ada9ea0a048d9f7abfccb7112e975066a49d2efd.yml +openapi_spec_hash: ac7d7b08600bce9ba341654a6d19ceb8 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 10f7af2ea1d3fff41c0363cd5d77947acc162ac6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 21:12:38 +0000 Subject: [PATCH 056/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4065a839..4535e1cf 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9dcf831bd3aa27c3150266a1ada9ea0a048d9f7abfccb7112e975066a49d2efd.yml -openapi_spec_hash: ac7d7b08600bce9ba341654a6d19ceb8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ad80ccea986db3c204b07467500db29d32b665029f1a420bc7bcbbe5d494e8b9.yml +openapi_spec_hash: 68352a6b9b020bdb9e1f0ead92bffb3d config_hash: 14efe98ee80f1e66ef35443e52f02953 From 726ac55e036e4b1d21294fab50d6ee5f760f81a8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:12:34 +0000 Subject: [PATCH 057/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4535e1cf..81f11e4b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ad80ccea986db3c204b07467500db29d32b665029f1a420bc7bcbbe5d494e8b9.yml -openapi_spec_hash: 68352a6b9b020bdb9e1f0ead92bffb3d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-1cc5b0f12365973a1a8b6b1891180c844cf80c60516643be09cf6680a76186c4.yml +openapi_spec_hash: 33199663f44acd70776fa2449945d930 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 9b105d3b70c7ccc3741d21dd8926891d082257ca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 03:12:31 +0000 Subject: [PATCH 058/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/parse_usage.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 81f11e4b..551eaac4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-1cc5b0f12365973a1a8b6b1891180c844cf80c60516643be09cf6680a76186c4.yml -openapi_spec_hash: 33199663f44acd70776fa2449945d930 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-e2cdf83446e5a5681f9d8ab7fc7be09e2fb817249b89024500c9824d89bf62d3.yml +openapi_spec_hash: cfa97fc221b56c4a2fa1e41ab74ef856 config_hash: 14efe98ee80f1e66ef35443e52f02953 diff --git a/src/reducto/types/parse_usage.py b/src/reducto/types/parse_usage.py index a67ffc45..17b20133 100644 --- a/src/reducto/types/parse_usage.py +++ b/src/reducto/types/parse_usage.py @@ -20,7 +20,14 @@ class ParseUsage(BaseModel): str, List[ Literal[ - "page", "html_page", "docx_native_page", "agentic", "complex", "chart_agent", "spreadsheet_cells" + "page", + "html_page", + "docx_native_page", + "agentic", + "complex", + "chart_agent", + "spreadsheet_cells", + "billable_spreadsheet_pages", ] ], ] From c4c57020f83f2c138c312b8687e387a9831a7f75 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 16:12:38 +0000 Subject: [PATCH 059/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 551eaac4..968358c7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-e2cdf83446e5a5681f9d8ab7fc7be09e2fb817249b89024500c9824d89bf62d3.yml -openapi_spec_hash: cfa97fc221b56c4a2fa1e41ab74ef856 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f0165cc85ea814378d85886f8f4ddf68320aee2a3aa51d0fdd6d091ae18b250a.yml +openapi_spec_hash: a045e701dae4fbd90dfcfe0a4ad50f9a config_hash: 14efe98ee80f1e66ef35443e52f02953 From c9a2cbe3f55884af0de572f6d45bab3633574297 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 19:12:48 +0000 Subject: [PATCH 060/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 968358c7..4f1e8bf3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-f0165cc85ea814378d85886f8f4ddf68320aee2a3aa51d0fdd6d091ae18b250a.yml -openapi_spec_hash: a045e701dae4fbd90dfcfe0a4ad50f9a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c5c5783a3605356c2ff578e37a0493f25b4fee6c089f0c334f2732f2d44847af.yml +openapi_spec_hash: 7c38f13abeceda217fefbffd57b2ef17 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 165326711e4450f6bc826f7c1ec2a1167369effb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:12:33 +0000 Subject: [PATCH 061/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4f1e8bf3..f911c6fe 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c5c5783a3605356c2ff578e37a0493f25b4fee6c089f0c334f2732f2d44847af.yml -openapi_spec_hash: 7c38f13abeceda217fefbffd57b2ef17 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-96d58e98621d6df2c489729df5fa38f206d83c8f424ccec5b6d27ff375ae3a53.yml +openapi_spec_hash: 8a18f6a4b9e62d6822dd4896af1ba4b5 config_hash: 14efe98ee80f1e66ef35443e52f02953 From dcbedf0a1dfd776c69434dd41331fb3e413d9e94 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:12:32 +0000 Subject: [PATCH 062/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f911c6fe..6efca889 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-96d58e98621d6df2c489729df5fa38f206d83c8f424ccec5b6d27ff375ae3a53.yml -openapi_spec_hash: 8a18f6a4b9e62d6822dd4896af1ba4b5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fcb9575685c61204f6fd0380384057e425fbd50f0c6ac5ca3f9172140093cd4f.yml +openapi_spec_hash: 7e4178eb5f23b9653f797f21accc3f50 config_hash: 14efe98ee80f1e66ef35443e52f02953 From ab9c9c7f85fe83977a1d0819a3c3421f697eadaa Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 00:12:38 +0000 Subject: [PATCH 063/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6efca889..0c1752e7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fcb9575685c61204f6fd0380384057e425fbd50f0c6ac5ca3f9172140093cd4f.yml -openapi_spec_hash: 7e4178eb5f23b9653f797f21accc3f50 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-cd060f7f0fa6ddbdd5ee1fe96f0a8b99d1fa5e2c7f161cc0894fa060cfff4cbe.yml +openapi_spec_hash: 0ad486eb5857c7025b2c3d50f08c0fff config_hash: 14efe98ee80f1e66ef35443e52f02953 From fa5e981f40a7a3990cb004ec60b405a8a3b651e2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:12:41 +0000 Subject: [PATCH 064/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0c1752e7..9aa41462 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-cd060f7f0fa6ddbdd5ee1fe96f0a8b99d1fa5e2c7f161cc0894fa060cfff4cbe.yml -openapi_spec_hash: 0ad486eb5857c7025b2c3d50f08c0fff +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ea359bf0e80dd8ae215cb2842fc973f27427d836e4fd4e445a5c2579ef55238b.yml +openapi_spec_hash: fdb4a30b3b2e0291c49b8afe842a708c config_hash: 14efe98ee80f1e66ef35443e52f02953 From bf50f32de645b413e8b58d77e89c8190f0936d5b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 20:12:32 +0000 Subject: [PATCH 065/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9aa41462..50ae468b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ea359bf0e80dd8ae215cb2842fc973f27427d836e4fd4e445a5c2579ef55238b.yml -openapi_spec_hash: fdb4a30b3b2e0291c49b8afe842a708c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d2e33e7e9c06e2964894d501d1697545bd363664f457bb54aac567e2d9c04fd5.yml +openapi_spec_hash: 774e8e086308bda721c7a923a2adee7e config_hash: 14efe98ee80f1e66ef35443e52f02953 From fe524d004cf497c51e27cf30087c05d865ced6b6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 21:12:33 +0000 Subject: [PATCH 066/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 50ae468b..56b40b5c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d2e33e7e9c06e2964894d501d1697545bd363664f457bb54aac567e2d9c04fd5.yml -openapi_spec_hash: 774e8e086308bda721c7a923a2adee7e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-97829cbf4403d0ccd099bf9a5e5e8be6006bca8afa602de02a2523db580e5f8b.yml +openapi_spec_hash: bb373a25261bbe17512f0887e920f75b config_hash: 14efe98ee80f1e66ef35443e52f02953 From 1bd9adbe8f02df2ee09db863433ea94e8c238c80 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 22:12:31 +0000 Subject: [PATCH 067/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 56b40b5c..ab035b21 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-97829cbf4403d0ccd099bf9a5e5e8be6006bca8afa602de02a2523db580e5f8b.yml -openapi_spec_hash: bb373a25261bbe17512f0887e920f75b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-47096a2028691753abd34906de25dd2fe82cfa92c01e312a83a39add624a1eb8.yml +openapi_spec_hash: d23db7e8b2cab2fa8fa7261e81df683f config_hash: 14efe98ee80f1e66ef35443e52f02953 From f3ad30a135555b71a4530f634d56ce77e57b8acc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:12:30 +0000 Subject: [PATCH 068/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ab035b21..e9f96426 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-47096a2028691753abd34906de25dd2fe82cfa92c01e312a83a39add624a1eb8.yml -openapi_spec_hash: d23db7e8b2cab2fa8fa7261e81df683f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-80908169ab6840ad4486b00af46b14d2928e5b15e9b1decee167d9903f155b67.yml +openapi_spec_hash: 98535b9fdf88f1a983f610b0e5b63e67 config_hash: 14efe98ee80f1e66ef35443e52f02953 From c8ac16077908f9409c8ba05c5677d0ec7faa4c51 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:12:32 +0000 Subject: [PATCH 069/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e9f96426..d6e3d041 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-80908169ab6840ad4486b00af46b14d2928e5b15e9b1decee167d9903f155b67.yml -openapi_spec_hash: 98535b9fdf88f1a983f610b0e5b63e67 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d5027be46fb8d1a7ad62983642862fb2d131058ba1a874d1defe4ecad0d7a556.yml +openapi_spec_hash: add5b3c740887d70a85bb09999d49e28 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 4f43481bbdaa9e0f2624905ec56c4e95f9e046ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:12:33 +0000 Subject: [PATCH 070/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d6e3d041..2c90e2bd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d5027be46fb8d1a7ad62983642862fb2d131058ba1a874d1defe4ecad0d7a556.yml -openapi_spec_hash: add5b3c740887d70a85bb09999d49e28 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-e7ba7639173bb705eeb83622c6311fca0409131ac7d2d5aee1ee3d409fcc6ac1.yml +openapi_spec_hash: 42dd6959f8443e6def3dd260d713bc0e config_hash: 14efe98ee80f1e66ef35443e52f02953 From edabac8d8f547fcfd107add006a9b1bf2904b68c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 20:12:35 +0000 Subject: [PATCH 071/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2c90e2bd..a5b06f76 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-e7ba7639173bb705eeb83622c6311fca0409131ac7d2d5aee1ee3d409fcc6ac1.yml -openapi_spec_hash: 42dd6959f8443e6def3dd260d713bc0e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-b30a7a55fea0136bbe9eb0c7d870e6028aeee0a5efc9d759a2beea9607e7e706.yml +openapi_spec_hash: 2590388059734b0f921eea37b986ab1a config_hash: 14efe98ee80f1e66ef35443e52f02953 From b75d76a131a2df44d4acbdabba9f6ceea1fa33c6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:12:35 +0000 Subject: [PATCH 072/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a5b06f76..4c07ea7a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-b30a7a55fea0136bbe9eb0c7d870e6028aeee0a5efc9d759a2beea9607e7e706.yml -openapi_spec_hash: 2590388059734b0f921eea37b986ab1a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-72d69e27a3f79d04afad20e779cf142051a4d981ac69105c6591b4c4ceebf12c.yml +openapi_spec_hash: 1cd45cf29c741bb38041252cb0f71b04 config_hash: 14efe98ee80f1e66ef35443e52f02953 From 7a986ffd3da297e48dd81d79aa8dd1fb15330c42 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 01:12:38 +0000 Subject: [PATCH 073/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4c07ea7a..6272f155 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-72d69e27a3f79d04afad20e779cf142051a4d981ac69105c6591b4c4ceebf12c.yml -openapi_spec_hash: 1cd45cf29c741bb38041252cb0f71b04 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-909015e871edecb3ae245c7bcebf6ac51030151866722c240e49c75e4d7a9bfe.yml +openapi_spec_hash: f3de44db5aa996b24d01eaac32b95f21 config_hash: 14efe98ee80f1e66ef35443e52f02953 From bbf66b3dbe5b67eb51df0dca753064dce880fa56 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:14:53 +0000 Subject: [PATCH 074/174] chore: configure new SDK language --- .stats.yml | 2 +- README.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 6272f155..e4c43fdc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-909015e871edecb3ae245c7bcebf6ac51030151866722c240e49c75e4d7a9bfe.yml openapi_spec_hash: f3de44db5aa996b24d01eaac32b95f21 -config_hash: 14efe98ee80f1e66ef35443e52f02953 +config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/README.md b/README.md index f0fc57fd..055265b1 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,15 @@ and offers both synchronous and asynchronous clients powered by [httpx](https:// It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Reducto MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=reductoai-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInJlZHVjdG9haS1tY3AiXSwiZW52Ijp7IlJFRFVDVE9fQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22reductoai-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22reductoai-mcp%22%5D%2C%22env%22%3A%7B%22REDUCTO_API_KEY%22%3A%22My%20API%20Key%22%7D%7D) + +> Note: You may need to set environment variables in your MCP client. + ## Documentation The REST API documentation can be found on [docs.reductoai.com](https://docs.reductoai.com). The full API of this library can be found in [api.md](api.md). From 1b6469396e2c44a84eda599527969291586ba35a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 02:07:55 +0000 Subject: [PATCH 075/174] fix(client): preserve hardcoded query params when merging with user params --- src/reducto/_base_client.py | 4 ++++ tests/test_client.py | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/reducto/_base_client.py b/src/reducto/_base_client.py index 1e31cb4d..ee1f4314 100644 --- a/src/reducto/_base_client.py +++ b/src/reducto/_base_client.py @@ -558,6 +558,10 @@ def _build_request( files = cast(HttpxRequestFiles, ForceMultipartDict()) prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) if "_" in prepared_url.host: # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} diff --git a/tests/test_client.py b/tests/test_client.py index 157a4300..95bc33e2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -427,6 +427,30 @@ def test_default_query_option(self) -> None: client.close() + def test_hardcoded_query_params_in_url(self, client: Reducto) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_request_extra_json(self, client: Reducto) -> None: request = client._build_request( FinalRequestOptions( @@ -1328,6 +1352,30 @@ async def test_default_query_option(self) -> None: await client.close() + async def test_hardcoded_query_params_in_url(self, async_client: AsyncReducto) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_request_extra_json(self, client: Reducto) -> None: request = client._build_request( FinalRequestOptions( From 3f94c0531197216e3c13c1a4348cfb9a59cfd9f6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:12:43 +0000 Subject: [PATCH 076/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e4c43fdc..957945df 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-909015e871edecb3ae245c7bcebf6ac51030151866722c240e49c75e4d7a9bfe.yml -openapi_spec_hash: f3de44db5aa996b24d01eaac32b95f21 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-6027f973a6f6048c223a46f1eb6e202034fd879b5dc66457ba53383340de206d.yml +openapi_spec_hash: 15ba253c0b9229186e50c20f43a4c6a4 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 03884d14ac791a985717aa7dc18d507dbbbc4c78 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 04:12:41 +0000 Subject: [PATCH 077/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 957945df..e4c43fdc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-6027f973a6f6048c223a46f1eb6e202034fd879b5dc66457ba53383340de206d.yml -openapi_spec_hash: 15ba253c0b9229186e50c20f43a4c6a4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-909015e871edecb3ae245c7bcebf6ac51030151866722c240e49c75e4d7a9bfe.yml +openapi_spec_hash: f3de44db5aa996b24d01eaac32b95f21 config_hash: 9fa10baf03f994be027bf73b29ac8572 From f97904a8a09af10d81582a74cb354e13e507b390 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 21:12:33 +0000 Subject: [PATCH 078/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e4c43fdc..e57a6c7e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-909015e871edecb3ae245c7bcebf6ac51030151866722c240e49c75e4d7a9bfe.yml -openapi_spec_hash: f3de44db5aa996b24d01eaac32b95f21 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7f583b0abe8f0d1373ba420e60d7dce079a34e4011cd6563391d55cfea38904e.yml +openapi_spec_hash: 1f85c7e745c2d4cd06d8941b7b698224 config_hash: 9fa10baf03f994be027bf73b29ac8572 From ed6766fbfec606fb6c0b6b118c2a72a41aa717dc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 23:12:36 +0000 Subject: [PATCH 079/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e57a6c7e..92e7dde3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7f583b0abe8f0d1373ba420e60d7dce079a34e4011cd6563391d55cfea38904e.yml -openapi_spec_hash: 1f85c7e745c2d4cd06d8941b7b698224 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac7b0ea2f39a481f0ddc8db9061bf9cb47d5ed1016aece28e33e49e1a24c752c.yml +openapi_spec_hash: 485d53a2036d10457aa78e5aa2c45028 config_hash: 9fa10baf03f994be027bf73b29ac8572 From d315000fc2dbe41ee526887879b380a1e545312f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 01:12:33 +0000 Subject: [PATCH 080/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 92e7dde3..a37a6991 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac7b0ea2f39a481f0ddc8db9061bf9cb47d5ed1016aece28e33e49e1a24c752c.yml -openapi_spec_hash: 485d53a2036d10457aa78e5aa2c45028 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-44cd0e03741ed12f4a77a1c52e1c223d204c9db59524088cf8f3fdafd05b9a4b.yml +openapi_spec_hash: d5df075740321207fe2347f95de0683a config_hash: 9fa10baf03f994be027bf73b29ac8572 From e49587bb10ef47a066b41cc41efbbc00fbcc694f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 21:12:33 +0000 Subject: [PATCH 081/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a37a6991..36a9ba39 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-44cd0e03741ed12f4a77a1c52e1c223d204c9db59524088cf8f3fdafd05b9a4b.yml -openapi_spec_hash: d5df075740321207fe2347f95de0683a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-00fd6801264dbae6636cbff4adf786219dd5b8f6eeb393199a459145971e9731.yml +openapi_spec_hash: 7383c3202c3118c5c0b792ec759d9a35 config_hash: 9fa10baf03f994be027bf73b29ac8572 From abbe2203dd944e43df98f7c0f5241c613fd90e5c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 22:12:37 +0000 Subject: [PATCH 082/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 36a9ba39..0575f04f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-00fd6801264dbae6636cbff4adf786219dd5b8f6eeb393199a459145971e9731.yml -openapi_spec_hash: 7383c3202c3118c5c0b792ec759d9a35 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fd81681679afcd2c4b67e17acf6e1931bddce71a040433a55656e5aedd9c3ca5.yml +openapi_spec_hash: 9e418826f20acb29130fc3fd97e7eb16 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 1b4981279daac989308163b9c653701c8bac42fa Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 03:12:33 +0000 Subject: [PATCH 083/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0575f04f..71b0a926 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fd81681679afcd2c4b67e17acf6e1931bddce71a040433a55656e5aedd9c3ca5.yml -openapi_spec_hash: 9e418826f20acb29130fc3fd97e7eb16 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-25b8ffdcb5543ff9e3306645405651c2d538547f534a2df25bc378313bac2160.yml +openapi_spec_hash: f87c3a6f387c20df1543853a64f0a51a config_hash: 9fa10baf03f994be027bf73b29ac8572 From 8d925790e731ac20a502de609fe629e5b722fa0e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 07:12:37 +0000 Subject: [PATCH 084/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 71b0a926..a0e08c66 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-25b8ffdcb5543ff9e3306645405651c2d538547f534a2df25bc378313bac2160.yml -openapi_spec_hash: f87c3a6f387c20df1543853a64f0a51a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-89b07255ef1c62b80012bdc17421939d61faa8d66ffb49266787aa5aee74d532.yml +openapi_spec_hash: 0f4a9dc896b86f7717a19a9264c73457 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 1ce77d1984c2f34e9b73587c2ba0d7f031e0cb3b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 01:12:39 +0000 Subject: [PATCH 085/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a0e08c66..a34d9817 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-89b07255ef1c62b80012bdc17421939d61faa8d66ffb49266787aa5aee74d532.yml -openapi_spec_hash: 0f4a9dc896b86f7717a19a9264c73457 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c405eb2d0f0ef8bac877fd956606d60feb1bf4e0e38ad99e3df00dc6b92657ca.yml +openapi_spec_hash: 95f4ae86e756e1c48345dc0d0d4a8bc8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 96643965fc133d8a4b81626782e3b43f35c30715 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 02:08:35 +0000 Subject: [PATCH 086/174] fix: ensure file data are only sent as 1 parameter --- src/reducto/_utils/_utils.py | 5 +++-- tests/test_extract_files.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/reducto/_utils/_utils.py b/src/reducto/_utils/_utils.py index eec7f4a1..63b8cd60 100644 --- a/src/reducto/_utils/_utils.py +++ b/src/reducto/_utils/_utils.py @@ -86,8 +86,9 @@ def _extract_items( index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index a4c8a66f..9e158a96 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -35,6 +35,15 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [ + ("files[]", b"file one"), + ("files[]", b"file two"), + ] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ From 7366a611c60be0c59de2d1201ed3baaab51b595f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 04:12:34 +0000 Subject: [PATCH 087/174] feat(api): api update --- .stats.yml | 4 +-- src/reducto/types/__init__.py | 1 - src/reducto/types/extract_usage.py | 18 ------------- src/reducto/types/shared/extract_response.py | 25 +++---------------- src/reducto/types/shared/pipeline_response.py | 17 +++---------- src/reducto/types/v3_extract.py | 22 +++------------- 6 files changed, 11 insertions(+), 76 deletions(-) delete mode 100644 src/reducto/types/extract_usage.py diff --git a/.stats.yml b/.stats.yml index a34d9817..ba63085e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c405eb2d0f0ef8bac877fd956606d60feb1bf4e0e38ad99e3df00dc6b92657ca.yml -openapi_spec_hash: 95f4ae86e756e1c48345dc0d0d4a8bc8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2714c6a71511a557b25a6e48ff64cc317d8b5df4f055a6e1968419ca7c0e6550.yml +openapi_spec_hash: 01a53f60cbb0e94dd307a914c46eb8d1 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/__init__.py b/src/reducto/types/__init__.py index c0641ad1..3f217832 100644 --- a/src/reducto/types/__init__.py +++ b/src/reducto/types/__init__.py @@ -40,7 +40,6 @@ from .parse_usage import ParseUsage as ParseUsage from .bounding_box import BoundingBox as BoundingBox from .enhance_param import EnhanceParam as EnhanceParam -from .extract_usage import ExtractUsage as ExtractUsage from .settings_param import SettingsParam as SettingsParam from .edit_run_params import EditRunParams as EditRunParams from .retrieval_param import RetrievalParam as RetrievalParam diff --git a/src/reducto/types/extract_usage.py b/src/reducto/types/extract_usage.py deleted file mode 100644 index 2d6a0a30..00000000 --- a/src/reducto/types/extract_usage.py +++ /dev/null @@ -1,18 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["ExtractUsage"] - - -class ExtractUsage(BaseModel): - num_fields: int - - num_pages: int - - credits: Optional[float] = None - - extract_mode: Optional[Literal["super_agent", "extract", "spreadsheet_agent"]] = None diff --git a/src/reducto/types/shared/extract_response.py b/src/reducto/types/shared/extract_response.py index 1744516d..b071e9fc 100644 --- a/src/reducto/types/shared/extract_response.py +++ b/src/reducto/types/shared/extract_response.py @@ -1,27 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional - -from ..._models import BaseModel -from ..extract_usage import ExtractUsage +from typing import Dict +from typing_extensions import TypeAlias __all__ = ["ExtractResponse"] - -class ExtractResponse(BaseModel): - citations: Optional[List[object]] = None - """The citations corresponding to the extracted response.""" - - result: List[object] - """The extracted response in your provided schema. - - This is a list of dictionaries. If disable_chunking is True (default), then it - will be a list of length one. - """ - - usage: ExtractUsage - - job_id: Optional[str] = None - - studio_link: Optional[str] = None - """The link to the studio pipeline for the document.""" +ExtractResponse: TypeAlias = Dict[str, object] diff --git a/src/reducto/types/shared/pipeline_response.py b/src/reducto/types/shared/pipeline_response.py index 3e6d9c59..1c5359dd 100644 --- a/src/reducto/types/shared/pipeline_response.py +++ b/src/reducto/types/shared/pipeline_response.py @@ -11,16 +11,7 @@ from .split_response import SplitResponse from .extract_response import ExtractResponse -__all__ = [ - "PipelineResponse", - "Result", - "ResultExtract", - "ResultExtractUnionMember0", - "ResultExtractUnionMember0Result", - "ResultParse", -] - -ResultExtractUnionMember0Result: TypeAlias = Union[ExtractResponse, V3Extract] +__all__ = ["PipelineResponse", "Result", "ResultExtractUnionMember0", "ResultParse"] class ResultExtractUnionMember0(BaseModel): @@ -28,20 +19,18 @@ class ResultExtractUnionMember0(BaseModel): page_range: List[int] - result: ResultExtractUnionMember0Result + result: Union[ExtractResponse, V3Extract] split_name: str partition: Optional[str] = None -ResultExtract: TypeAlias = Union[List[ResultExtractUnionMember0], ExtractResponse, V3Extract, None] - ResultParse: TypeAlias = Union[ParseResponse, List[ParseResponse], None] class Result(BaseModel): - extract: Optional[ResultExtract] = None + extract: Union[List[ResultExtractUnionMember0], ExtractResponse, V3Extract, None] = None parse: Optional[ResultParse] = None diff --git a/src/reducto/types/v3_extract.py b/src/reducto/types/v3_extract.py index e2b61eb0..2f197e6f 100644 --- a/src/reducto/types/v3_extract.py +++ b/src/reducto/types/v3_extract.py @@ -1,24 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Union, Optional - -from .._models import BaseModel -from .extract_usage import ExtractUsage +from typing import Dict +from typing_extensions import TypeAlias __all__ = ["V3Extract"] - -class V3Extract(BaseModel): - result: Union[List[object], object] - """The extracted response in your provided schema. - - This is a list of dictionaries. If disable_chunking is True (default), then it - will be a list of length one. - """ - - usage: ExtractUsage - - job_id: Optional[str] = None - - studio_link: Optional[str] = None - """The link to the studio pipeline for the document.""" +V3Extract: TypeAlias = Dict[str, object] From 339e73c28fa9890ded2b66221470f2dcf7265b30 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:12:36 +0000 Subject: [PATCH 088/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ba63085e..1326b5ce 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2714c6a71511a557b25a6e48ff64cc317d8b5df4f055a6e1968419ca7c0e6550.yml -openapi_spec_hash: 01a53f60cbb0e94dd307a914c46eb8d1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-0f32cd43f48d7950c980686efe301bf48ed6e02031376d8060b99fc8235a67d1.yml +openapi_spec_hash: 4a197feccb27145d2ac4da356cbdcb07 config_hash: 9fa10baf03f994be027bf73b29ac8572 From a82f97f816a9079f5b0d22636d1b994ed8627a79 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:12:40 +0000 Subject: [PATCH 089/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1326b5ce..c4e2dcb9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-0f32cd43f48d7950c980686efe301bf48ed6e02031376d8060b99fc8235a67d1.yml -openapi_spec_hash: 4a197feccb27145d2ac4da356cbdcb07 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-3c897e9af4fb27c2531bde46c076775d7e012039cada998576d07a241f485a87.yml +openapi_spec_hash: 1caa2e7294bcc07cfa89b1cf39c2e362 config_hash: 9fa10baf03f994be027bf73b29ac8572 From b7c03bc59e94a1e7ae80bea72ac873980e6b81e0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:12:41 +0000 Subject: [PATCH 090/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c4e2dcb9..478555b8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-3c897e9af4fb27c2531bde46c076775d7e012039cada998576d07a241f485a87.yml -openapi_spec_hash: 1caa2e7294bcc07cfa89b1cf39c2e362 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-190fd6622115407de2871b2c2b0aac45bbd7a3f9c83f3c44412fa933d1511c5f.yml +openapi_spec_hash: fcf713955ffe437a447b1790a37bb82a config_hash: 9fa10baf03f994be027bf73b29ac8572 From 5b88ae92f5ce799a05aeced34dbfb2554747c475 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 00:12:38 +0000 Subject: [PATCH 091/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 478555b8..2d3c1371 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-190fd6622115407de2871b2c2b0aac45bbd7a3f9c83f3c44412fa933d1511c5f.yml -openapi_spec_hash: fcf713955ffe437a447b1790a37bb82a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-53c9f34fa73f745c6cd62494a216bc078bb935f4f4ae2f08482ee481231d5850.yml +openapi_spec_hash: 4ed5b6f007e2084b2093dfb1fb8bf558 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 31e60c1fa6c6f8bfb5f89c1942f326e720327ed6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 01:12:45 +0000 Subject: [PATCH 092/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2d3c1371..af551da4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-53c9f34fa73f745c6cd62494a216bc078bb935f4f4ae2f08482ee481231d5850.yml -openapi_spec_hash: 4ed5b6f007e2084b2093dfb1fb8bf558 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ef603a65b5d3c2e5186a7625ebae2ff30260025456061331345cdf85dde55086.yml +openapi_spec_hash: cd29c0540293a911dbeecb258fe32e63 config_hash: 9fa10baf03f994be027bf73b29ac8572 From a3412f53dfb82d0575631f9ec7e426802deacb73 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 03:12:36 +0000 Subject: [PATCH 093/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index af551da4..451f14d5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ef603a65b5d3c2e5186a7625ebae2ff30260025456061331345cdf85dde55086.yml -openapi_spec_hash: cd29c0540293a911dbeecb258fe32e63 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7b9df39d70396663501e3bae8eceef3432569a8ba0ce12050d043820145e26f6.yml +openapi_spec_hash: 7ad05621fd208b1cfac9ee7592407f66 config_hash: 9fa10baf03f994be027bf73b29ac8572 From f138e67f17ac84f4aee72df3d7a33c075e56e4f5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:12:51 +0000 Subject: [PATCH 094/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 451f14d5..983ecd5f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-7b9df39d70396663501e3bae8eceef3432569a8ba0ce12050d043820145e26f6.yml -openapi_spec_hash: 7ad05621fd208b1cfac9ee7592407f66 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac994ccab6e4e151b0d433d08642cab90d939c9e2b44d28087bebad13f7ec3e4.yml +openapi_spec_hash: 81159a4e0d5a29e738439decc3379465 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 512180673047047768414c7aaaca33f2b5b4f604 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 20:12:56 +0000 Subject: [PATCH 095/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 983ecd5f..ad9d3057 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac994ccab6e4e151b0d433d08642cab90d939c9e2b44d28087bebad13f7ec3e4.yml -openapi_spec_hash: 81159a4e0d5a29e738439decc3379465 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-190bbabd8d3d7bfef42399a6acb6f2d9a0e965bbc7d03ee0397c3e7df5b547fe.yml +openapi_spec_hash: d08f20d455f275f8b05da4b835eb7a27 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 290996385738b0aeeb21928e0d3a259eb67dfd54 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:12:50 +0000 Subject: [PATCH 096/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ad9d3057..1dcc6d68 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-190bbabd8d3d7bfef42399a6acb6f2d9a0e965bbc7d03ee0397c3e7df5b547fe.yml -openapi_spec_hash: d08f20d455f275f8b05da4b835eb7a27 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c2b7bb5dd7c39b937f800cec69c4316bac82ae36ff180bc14c000e79830ac463.yml +openapi_spec_hash: 95827dd2170cb25e2e4feeb3cae1abbf config_hash: 9fa10baf03f994be027bf73b29ac8572 From 66f509e3067a908a06c5cff076be0642044a6acd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:12:51 +0000 Subject: [PATCH 097/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1dcc6d68..86f7f9b5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c2b7bb5dd7c39b937f800cec69c4316bac82ae36ff180bc14c000e79830ac463.yml -openapi_spec_hash: 95827dd2170cb25e2e4feeb3cae1abbf +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-57ffebe4495e07de3cb29354b4bc69a1a4c69daef1134079c5953fddca6b31aa.yml +openapi_spec_hash: 4a2b08397f9ef7ff6dfc09e0695233a1 config_hash: 9fa10baf03f994be027bf73b29ac8572 From dbf7566e81408c6584eebf2d5c6eefb9a58cb9e1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 05:12:41 +0000 Subject: [PATCH 098/174] feat(api): api update --- .stats.yml | 4 ++-- .../types/shared/experimental_processing_options.py | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 86f7f9b5..e920eadb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-57ffebe4495e07de3cb29354b4bc69a1a4c69daef1134079c5953fddca6b31aa.yml -openapi_spec_hash: 4a2b08397f9ef7ff6dfc09e0695233a1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac5d9fa4fc671803ac11db02ee3200edd7bba978ddce2780305fafc75aaf07e3.yml +openapi_spec_hash: 7e4e965e7d07f2f8bc371d1a9ee5e74f config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/shared/experimental_processing_options.py b/src/reducto/types/shared/experimental_processing_options.py index 1f4d9ac2..90c9a136 100644 --- a/src/reducto/types/shared/experimental_processing_options.py +++ b/src/reducto/types/shared/experimental_processing_options.py @@ -75,7 +75,15 @@ class ExperimentalProcessingOptions(BaseModel): layout_model: Optional[ Literal[ - "default", "beta", "rfdetr", "rfdetr0302", "rfdetr0303", "rfdetrbase0218", "rfdetr0304", "qwen35_27b_0317" + "default", + "beta", + "dfine", + "rfdetr", + "rfdetr0302", + "rfdetr0303", + "rfdetrbase0218", + "rfdetr0304", + "qwen35_27b_0317", ] ] = None """The layout model to use for the document. From 0f8b30464c1683f452795c3a47aedef8400c5682 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 23:12:50 +0000 Subject: [PATCH 099/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e920eadb..ff33082c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac5d9fa4fc671803ac11db02ee3200edd7bba978ddce2780305fafc75aaf07e3.yml -openapi_spec_hash: 7e4e965e7d07f2f8bc371d1a9ee5e74f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-62084f3425c2ffac3857d6270c6f136c842c6d72039573aa4d8e2d5206d825c8.yml +openapi_spec_hash: 05d764ad3fc51207de437dbe1ee50b61 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 40c7ca8259034a5ce9a7e046e1f9f90c2a486096 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 01:12:39 +0000 Subject: [PATCH 100/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ff33082c..48226d10 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-62084f3425c2ffac3857d6270c6f136c842c6d72039573aa4d8e2d5206d825c8.yml -openapi_spec_hash: 05d764ad3fc51207de437dbe1ee50b61 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-4c5c9cc4b785b9e1ffeff8c0c587cdb54812cd07e63dd4f58ca9b9411185af14.yml +openapi_spec_hash: 63ecc9bd072d72b4d83ab3d20aaf61d0 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 943ec5d9a400effe72a8a496cee5773b058b715f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 02:12:53 +0000 Subject: [PATCH 101/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 48226d10..78f88644 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-4c5c9cc4b785b9e1ffeff8c0c587cdb54812cd07e63dd4f58ca9b9411185af14.yml -openapi_spec_hash: 63ecc9bd072d72b4d83ab3d20aaf61d0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-420204f046b750d0665ec5899470d733d2556e264b5358ba9e4458bbdf6ff2a2.yml +openapi_spec_hash: 8d72761f7a9e83d2ed7ffe1b85f6eb61 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 31d6c0f7452f58bb34ba8511e4bf3ff6dfa0c17c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 04:12:43 +0000 Subject: [PATCH 102/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 78f88644..68e2476f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-420204f046b750d0665ec5899470d733d2556e264b5358ba9e4458bbdf6ff2a2.yml -openapi_spec_hash: 8d72761f7a9e83d2ed7ffe1b85f6eb61 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9664ddf167d04b13da552bc6761d998a85076d8c8c466d01b5c01aeedeead226.yml +openapi_spec_hash: 90fd9b742d614335509f0d0b9873d5c1 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 7a82a3680e031c244e3ebfae7042978093d7e873 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:12:40 +0000 Subject: [PATCH 103/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 68e2476f..c7807254 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9664ddf167d04b13da552bc6761d998a85076d8c8c466d01b5c01aeedeead226.yml -openapi_spec_hash: 90fd9b742d614335509f0d0b9873d5c1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-dcd29a317223209fd2db066c914a2e58ddf0e53d398cad78a209f9ff279dd123.yml +openapi_spec_hash: 7d36288ac54bf244fffee1c775ebff6b config_hash: 9fa10baf03f994be027bf73b29ac8572 From a398aafd4c80f0c5230dedf484fa1cea514b068c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:12:40 +0000 Subject: [PATCH 104/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c7807254..fb86aa75 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-dcd29a317223209fd2db066c914a2e58ddf0e53d398cad78a209f9ff279dd123.yml -openapi_spec_hash: 7d36288ac54bf244fffee1c775ebff6b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-0de6c58d0748d56e2c2bd5d0bc1bbcb8b291a13929c10edd1b5586b134791e88.yml +openapi_spec_hash: 6fba69b7e0f3f16f231f3ec42d714283 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 1259659610554aefc4dff3f09546b69c057fed17 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:13:07 +0000 Subject: [PATCH 105/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index fb86aa75..72689081 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-0de6c58d0748d56e2c2bd5d0bc1bbcb8b291a13929c10edd1b5586b134791e88.yml -openapi_spec_hash: 6fba69b7e0f3f16f231f3ec42d714283 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-787866cad3a80d7f704d5b099a706bf433146952fe9ea187a165c1d8b23ee039.yml +openapi_spec_hash: 4852284fd1c2b3f953142afeeb3c97aa config_hash: 9fa10baf03f994be027bf73b29ac8572 From 586a40a7ae020b37b9de9035e2a3603a175bd56b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 02:14:54 +0000 Subject: [PATCH 106/174] perf(client): optimize file structure copying in multipart requests --- src/reducto/_client.py | 6 +-- src/reducto/_files.py | 56 +++++++++++++++++-- src/reducto/_utils/__init__.py | 1 - src/reducto/_utils/_utils.py | 15 ------ tests/test_deepcopy.py | 58 -------------------- tests/test_files.py | 99 +++++++++++++++++++++++++++++++++- 6 files changed, 154 insertions(+), 81 deletions(-) delete mode 100644 tests/test_deepcopy.py diff --git a/src/reducto/_client.py b/src/reducto/_client.py index df400032..81ebe7a3 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -11,6 +11,7 @@ from . import _exceptions from ._qs import Querystring from .types import client_upload_params +from ._files import deepcopy_with_paths from ._types import ( Body, Omit, @@ -29,7 +30,6 @@ is_given, extract_files, maybe_transform, - deepcopy_minimal, get_async_library, async_maybe_transform, ) @@ -338,7 +338,7 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal({"file": file}) + body = deepcopy_with_paths({"file": file}, [["file"]]) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) if files: # It should be noted that the actual Content-Type header that will be @@ -649,7 +649,7 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal({"file": file}) + body = deepcopy_with_paths({"file": file}, [["file"]]) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) if files: # It should be noted that the actual Content-Type header that will be diff --git a/src/reducto/_files.py b/src/reducto/_files.py index 7f056768..a4b9a7b1 100644 --- a/src/reducto/_files.py +++ b/src/reducto/_files.py @@ -3,8 +3,8 @@ import io import os import pathlib -from typing import overload -from typing_extensions import TypeGuard +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard import anyio @@ -17,7 +17,9 @@ HttpxFileContent, HttpxRequestFiles, ) -from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: @@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent: return await anyio.Path(file).read_bytes() return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/reducto/_utils/__init__.py b/src/reducto/_utils/__init__.py index 10cb66d2..1c090e51 100644 --- a/src/reducto/_utils/__init__.py +++ b/src/reducto/_utils/__init__.py @@ -24,7 +24,6 @@ coerce_integer as coerce_integer, file_from_path as file_from_path, strip_not_given as strip_not_given, - deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, maybe_coerce_float as maybe_coerce_float, get_required_header as get_required_header, diff --git a/src/reducto/_utils/_utils.py b/src/reducto/_utils/_utils.py index 63b8cd60..771859f5 100644 --- a/src/reducto/_utils/_utils.py +++ b/src/reducto/_utils/_utils.py @@ -177,21 +177,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: return isinstance(obj, Iterable) -def deepcopy_minimal(item: _T) -> _T: - """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: - - - mappings, e.g. `dict` - - list - - This is done for performance reasons. - """ - if is_mapping(item): - return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) - if is_list(item): - return cast(_T, [deepcopy_minimal(entry) for entry in item]) - return item - - # copied from https://github.com/Rapptz/RoboDanny def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: size = len(seq) diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py deleted file mode 100644 index f2e61051..00000000 --- a/tests/test_deepcopy.py +++ /dev/null @@ -1,58 +0,0 @@ -from reducto._utils import deepcopy_minimal - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert id(obj1) != id(obj2) - - -def test_simple_dict() -> None: - obj1 = {"foo": "bar"} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_dict() -> None: - obj1 = {"foo": {"bar": True}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - - -def test_complex_nested_dict() -> None: - obj1 = {"foo": {"bar": [{"hello": "world"}]}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) - assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) - - -def test_simple_list() -> None: - obj1 = ["a", "b", "c"] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_list() -> None: - obj1 = ["a", [1, 2, 3]] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1[1], obj2[1]) - - -class MyObject: ... - - -def test_ignores_other_types() -> None: - # custom classes - my_obj = MyObject() - obj1 = {"foo": my_obj} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert obj1["foo"] is my_obj - - # tuples - obj3 = ("a", "b") - obj4 = deepcopy_minimal(obj3) - assert obj3 is obj4 diff --git a/tests/test_files.py b/tests/test_files.py index b5e73903..b76db4fd 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,7 +4,8 @@ import pytest from dirty_equals import IsDict, IsList, IsBytes, IsTuple -from reducto._files import to_httpx_files, async_to_httpx_files +from reducto._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files +from reducto._utils import extract_files readme_path = Path(__file__).parent.parent.joinpath("README.md") @@ -49,3 +50,99 @@ def test_string_not_allowed() -> None: "file": "foo", # type: ignore } ) + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert obj1 is not obj2 + + +class TestDeepcopyWithPaths: + def test_copies_top_level_dict(self) -> None: + original = {"file": b"data", "other": "value"} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + + def test_file_value_is_same_reference(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + assert result["file"] is file_bytes + + def test_list_popped_wholesale(self) -> None: + files = [b"f1", b"f2"] + original = {"files": files, "title": "t"} + result = deepcopy_with_paths(original, [["files", ""]]) + assert_different_identities(result, original) + result_files = result["files"] + assert isinstance(result_files, list) + assert_different_identities(result_files, files) + + def test_nested_array_path_copies_list_and_elements(self) -> None: + elem1 = {"file": b"f1", "extra": 1} + elem2 = {"file": b"f2", "extra": 2} + original = {"items": [elem1, elem2]} + result = deepcopy_with_paths(original, [["items", "", "file"]]) + assert_different_identities(result, original) + result_items = result["items"] + assert isinstance(result_items, list) + assert_different_identities(result_items, original["items"]) + assert_different_identities(result_items[0], elem1) + assert_different_identities(result_items[1], elem2) + + def test_empty_paths_returns_same_object(self) -> None: + original = {"foo": "bar"} + result = deepcopy_with_paths(original, []) + assert result is original + + def test_multiple_paths(self) -> None: + f1 = b"file1" + f2 = b"file2" + original = {"a": f1, "b": f2, "c": "unchanged"} + result = deepcopy_with_paths(original, [["a"], ["b"]]) + assert_different_identities(result, original) + assert result["a"] is f1 + assert result["b"] is f2 + assert result["c"] is original["c"] + + def test_extract_files_does_not_mutate_original_top_level(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes, "other": "value"} + + copied = deepcopy_with_paths(original, [["file"]]) + extracted = extract_files(copied, paths=[["file"]]) + + assert extracted == [("file", file_bytes)] + assert original == {"file": file_bytes, "other": "value"} + assert copied == {"other": "value"} + + def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: + file1 = b"f1" + file2 = b"f2" + original = { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + + copied = deepcopy_with_paths(original, [["items", "", "file"]]) + extracted = extract_files(copied, paths=[["items", "", "file"]]) + + assert extracted == [("items[][file]", file1), ("items[][file]", file2)] + assert original == { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + assert copied == { + "items": [ + {"extra": 1}, + {"extra": 2}, + ], + "title": "example", + } From 14b95fd4fb7abf86f19c06d1d7586a4efbfa92e2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 05:12:48 +0000 Subject: [PATCH 107/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 72689081..bfaa277b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-787866cad3a80d7f704d5b099a706bf433146952fe9ea187a165c1d8b23ee039.yml -openapi_spec_hash: 4852284fd1c2b3f953142afeeb3c97aa +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-4523e8ace99fb0cf3fb7663a3df504eac6b4dee034afd260a2b17eb44c29667d.yml +openapi_spec_hash: 3047550dd065bf08b6e4ed2106393dbf config_hash: 9fa10baf03f994be027bf73b29ac8572 From cdc928d37e0b980c265ce7a0703e97c6023d43b8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:12:41 +0000 Subject: [PATCH 108/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/shared/experimental_processing_options.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bfaa277b..ffb5dd1a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-4523e8ace99fb0cf3fb7663a3df504eac6b4dee034afd260a2b17eb44c29667d.yml -openapi_spec_hash: 3047550dd065bf08b6e4ed2106393dbf +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-10e369182c416df8a9e82db083a02c64adf5017aa5efe78625d338034a033d1c.yml +openapi_spec_hash: 08b4777c472cf32d686e44648ad444a1 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/shared/experimental_processing_options.py b/src/reducto/types/shared/experimental_processing_options.py index 90c9a136..6533dd4f 100644 --- a/src/reducto/types/shared/experimental_processing_options.py +++ b/src/reducto/types/shared/experimental_processing_options.py @@ -83,6 +83,7 @@ class ExperimentalProcessingOptions(BaseModel): "rfdetr0303", "rfdetrbase0218", "rfdetr0304", + "rfdetr0306", "qwen35_27b_0317", ] ] = None From b0683870bed76265a9ccca41de6ba808f04c17e3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 00:12:42 +0000 Subject: [PATCH 109/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/shared/advanced_processing_options.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index ffb5dd1a..9736aa26 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-10e369182c416df8a9e82db083a02c64adf5017aa5efe78625d338034a033d1c.yml -openapi_spec_hash: 08b4777c472cf32d686e44648ad444a1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d562f8146638d91d6e6515ed836ce3464863d4833657b004c2da2f46beee581a.yml +openapi_spec_hash: 70d1778962ebacfcbce7dabb4b2bc058 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/shared/advanced_processing_options.py b/src/reducto/types/shared/advanced_processing_options.py index 9b55e5bb..8fb4c1fe 100644 --- a/src/reducto/types/shared/advanced_processing_options.py +++ b/src/reducto/types/shared/advanced_processing_options.py @@ -90,7 +90,9 @@ class AdvancedProcessingOptions(BaseModel): be merged across breaks and spaces. """ - ocr_system: Optional[Literal["highres", "multilingual", "combined", "reducto", "legacy", "reducto-v2"]] = None + ocr_system: Optional[ + Literal["highres", "multilingual", "combined", "reducto", "legacy", "reducto-v2", "reducto-v3"] + ] = None """The OCR system to use. Highres is recommended for documents with English characters. Legacy uses an From f94a5eb4a1f88cab5bc2721df80456fc61d90468 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:12:42 +0000 Subject: [PATCH 110/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9736aa26..16d8c67c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-d562f8146638d91d6e6515ed836ce3464863d4833657b004c2da2f46beee581a.yml -openapi_spec_hash: 70d1778962ebacfcbce7dabb4b2bc058 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fe2a4a7b11a7b969bf7fdd333f473bd8a6a071928af685954909796433a72cee.yml +openapi_spec_hash: 6f2a47ef326f3f33e0f153430cfa5f54 config_hash: 9fa10baf03f994be027bf73b29ac8572 From a3d7655aa8c9eff90f7e8f51b27814b5bc6855ae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 21:12:48 +0000 Subject: [PATCH 111/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 16d8c67c..8444c58d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-fe2a4a7b11a7b969bf7fdd333f473bd8a6a071928af685954909796433a72cee.yml -openapi_spec_hash: 6f2a47ef326f3f33e0f153430cfa5f54 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-64b36779c305f1acbdce5ac61579e29d6be5ebfeb997e402f62ef049d9fa3fda.yml +openapi_spec_hash: 0e6016512d5bed90ff1ba9fa7a718e15 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 48895d742663e0b24639c1988de1c524e8fa2e4b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 02:12:40 +0000 Subject: [PATCH 112/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8444c58d..bab3e21d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-64b36779c305f1acbdce5ac61579e29d6be5ebfeb997e402f62ef049d9fa3fda.yml -openapi_spec_hash: 0e6016512d5bed90ff1ba9fa7a718e15 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8a900f24897b15af3363b28fc2ad792f120f17e1929123de62940ff8456e5563.yml +openapi_spec_hash: 1b14545ee6985aa316b7f6bc29330101 config_hash: 9fa10baf03f994be027bf73b29ac8572 From ef63a146d60bcf3a06bc3a7649e79414cd0e0716 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:13:01 +0000 Subject: [PATCH 113/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bab3e21d..a32d08ec 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-8a900f24897b15af3363b28fc2ad792f120f17e1929123de62940ff8456e5563.yml -openapi_spec_hash: 1b14545ee6985aa316b7f6bc29330101 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-bbeb92ab31eed64cacf907613a1c51cea96ef5f24fe821731b472ec8c1a4c703.yml +openapi_spec_hash: 641e931b6746f47336426e3e6c9752df config_hash: 9fa10baf03f994be027bf73b29ac8572 From 9dbc6869edb9ee777a8c54b93c2a4bc64209d9cf Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:12:45 +0000 Subject: [PATCH 114/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a32d08ec..e5afa816 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-bbeb92ab31eed64cacf907613a1c51cea96ef5f24fe821731b472ec8c1a4c703.yml -openapi_spec_hash: 641e931b6746f47336426e3e6c9752df +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-af220ae9f0ded9509478b62f50e3b9f5345b90071c66974c9d24f49748e662b9.yml +openapi_spec_hash: f9a09a91f267528794561dd038d234c3 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 0ce89c2d23760a9e5a1b3e5f305d7d68f4812ff4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:06:08 +0000 Subject: [PATCH 115/174] chore(internal): more robust bootstrap script --- scripts/bootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index b430fee3..fe8451e4 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response From 50d3894bd19866640f6a265a8d93896ff555f41f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:12:28 +0000 Subject: [PATCH 116/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e5afa816..88a7ae80 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-af220ae9f0ded9509478b62f50e3b9f5345b90071c66974c9d24f49748e662b9.yml -openapi_spec_hash: f9a09a91f267528794561dd038d234c3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9b1104efaba2d34f2d690e00025461ab02d345ac0fbc29ca58c9eec3519bcc60.yml +openapi_spec_hash: 832c07ac993af2dc98319010daab7ca1 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 1c5e866e308c3a38f03dd06447c0720de47ea50a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 00:12:34 +0000 Subject: [PATCH 117/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 88a7ae80..b1e1bfeb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-9b1104efaba2d34f2d690e00025461ab02d345ac0fbc29ca58c9eec3519bcc60.yml -openapi_spec_hash: 832c07ac993af2dc98319010daab7ca1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c44446880673bec616aa963931fca97533b77951417446d5b7e7c77a12634725.yml +openapi_spec_hash: 07b02928e037afb8500345e85f91c1b3 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 2b818b557d8e09c52040564bb983226a09541528 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 02:12:32 +0000 Subject: [PATCH 118/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b1e1bfeb..dc0c7a1e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-c44446880673bec616aa963931fca97533b77951417446d5b7e7c77a12634725.yml -openapi_spec_hash: 07b02928e037afb8500345e85f91c1b3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-384de110cc6135bb14c494c6ab9971d216ff0b70849f66152770167996d4a6fe.yml +openapi_spec_hash: 8de0a58197a2859019c48a3577a6f4b8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From c0149d3a70896c45adf42f9369a65be1781a39b5 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:12:38 +0000 Subject: [PATCH 119/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index dc0c7a1e..c8f7f250 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-384de110cc6135bb14c494c6ab9971d216ff0b70849f66152770167996d4a6fe.yml -openapi_spec_hash: 8de0a58197a2859019c48a3577a6f4b8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-844854f668daedf94e2578f955ea9a06040dccd2f3ad0abe6718277bd775ba15.yml +openapi_spec_hash: 66dc5992e8d1d7c38df8c6176f7493a8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From a37c16c2baac63633041f546555808e78bbe949f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Apr 2026 01:12:36 +0000 Subject: [PATCH 120/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c8f7f250..14fdf3d3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-844854f668daedf94e2578f955ea9a06040dccd2f3ad0abe6718277bd775ba15.yml -openapi_spec_hash: 66dc5992e8d1d7c38df8c6176f7493a8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-3fd1114402fa3ea05e605729bcae2d40df0a2b3e2b81e8050e0ee14585110e07.yml +openapi_spec_hash: 3a61747234cc2cd5a6771ce34b9d94b4 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 14e9c5561bb049f0e47686e913bddb318d6508a3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:12:32 +0000 Subject: [PATCH 121/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 14fdf3d3..f2b8c0fc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-3fd1114402fa3ea05e605729bcae2d40df0a2b3e2b81e8050e0ee14585110e07.yml -openapi_spec_hash: 3a61747234cc2cd5a6771ce34b9d94b4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ad33dc55b36be5b7efa2653f52cea2f0a1078be309977ac065854d2462869738.yml +openapi_spec_hash: 836cdf17550f7c857bcfac7db04c9b1b config_hash: 9fa10baf03f994be027bf73b29ac8572 From 4ec76c3e1950bf831784b958a5c0ea9611b03238 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 26 Apr 2026 04:12:32 +0000 Subject: [PATCH 122/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f2b8c0fc..0d29b8c9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ad33dc55b36be5b7efa2653f52cea2f0a1078be309977ac065854d2462869738.yml -openapi_spec_hash: 836cdf17550f7c857bcfac7db04c9b1b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2f50309294626ee7c427ccd84a3749cf138b41ded635d70097e2be2a54d3c0be.yml +openapi_spec_hash: b6134b9e1388b6cf719c39865401d82a config_hash: 9fa10baf03f994be027bf73b29ac8572 From 9e206a0db91f083f8267c31aca937c5deb8367df Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:12:41 +0000 Subject: [PATCH 123/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 0d29b8c9..21fa6a0e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2f50309294626ee7c427ccd84a3749cf138b41ded635d70097e2be2a54d3c0be.yml -openapi_spec_hash: b6134b9e1388b6cf719c39865401d82a +openapi_spec_hash: 4da30ee28a326eb915b4019b9a23e96c config_hash: 9fa10baf03f994be027bf73b29ac8572 From c1d729d03a22a6c535ec0340e0203af46cf8e546 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:02:03 +0000 Subject: [PATCH 124/174] fix: use correct field name format for multipart file arrays --- src/reducto/_qs.py | 8 ++----- src/reducto/_types.py | 3 +++ src/reducto/_utils/_utils.py | 42 +++++++++++++++++++++++++++++------- tests/test_extract_files.py | 28 +++++++++++++++++++----- tests/test_files.py | 2 +- 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/reducto/_qs.py b/src/reducto/_qs.py index de8c99bc..4127c19c 100644 --- a/src/reducto/_qs.py +++ b/src/reducto/_qs.py @@ -2,17 +2,13 @@ from typing import Any, List, Tuple, Union, Mapping, TypeVar from urllib.parse import parse_qs, urlencode -from typing_extensions import Literal, get_args +from typing_extensions import get_args -from ._types import NotGiven, not_given +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given from ._utils import flatten _T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - PrimitiveData = Union[str, int, float, bool, None] # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] # https://github.com/microsoft/pyright/issues/3555 diff --git a/src/reducto/_types.py b/src/reducto/_types.py index 6c70e52c..1cd25278 100644 --- a/src/reducto/_types.py +++ b/src/reducto/_types.py @@ -47,6 +47,9 @@ ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) _T = TypeVar("_T") +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances diff --git a/src/reducto/_utils/_utils.py b/src/reducto/_utils/_utils.py index 771859f5..199cd231 100644 --- a/src/reducto/_utils/_utils.py +++ b/src/reducto/_utils/_utils.py @@ -17,11 +17,11 @@ ) from pathlib import Path from datetime import date, datetime -from typing_extensions import TypeGuard +from typing_extensions import TypeGuard, get_args import sniffio -from .._types import Omit, NotGiven, FileTypes, HeadersLike +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -40,25 +40,45 @@ def extract_files( query: Mapping[str, object], *, paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", ) -> list[tuple[str, FileTypes]]: """Recursively extract files from the given dictionary based on specified paths. A path may look like this ['foo', 'files', '', 'data']. + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + Note: this mutates the given dictionary. """ files: list[tuple[str, FileTypes]] = [] for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None)) + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) return files +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + def _extract_items( obj: object, path: Sequence[str], *, index: int, flattened_key: str | None, + array_format: ArrayFormat, ) -> list[tuple[str, FileTypes]]: try: key = path[index] @@ -75,9 +95,11 @@ def _extract_items( if is_list(obj): files: list[tuple[str, FileTypes]] = [] - for entry in obj: - assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") - files.append((flattened_key + "[]", cast(FileTypes, entry))) + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) return files assert_is_file_content(obj, key=flattened_key) @@ -106,6 +128,7 @@ def _extract_items( path, index=index, flattened_key=flattened_key, + array_format=array_format, ) elif is_list(obj): if key != "": @@ -117,9 +140,12 @@ def _extract_items( item, path, index=index, - flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, ) - for item in obj + for array_index, item in enumerate(obj) ] ) diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index 9e158a96..f3cccbea 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -4,7 +4,7 @@ import pytest -from reducto._types import FileTypes +from reducto._types import FileTypes, ArrayFormat from reducto._utils import extract_files @@ -37,10 +37,7 @@ def test_multiple_files() -> None: def test_top_level_file_array() -> None: query = {"files": [b"file one", b"file two"], "title": "hello"} - assert extract_files(query, paths=[["files", ""]]) == [ - ("files[]", b"file one"), - ("files[]", b"file two"), - ] + assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] assert query == {"title": "hello"} @@ -71,3 +68,24 @@ def test_ignores_incorrect_paths( expected: list[tuple[str, FileTypes]], ) -> None: assert extract_files(query, paths=paths) == expected + + +@pytest.mark.parametrize( + "array_format,expected_top_level,expected_nested", + [ + ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), + ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), + ], +) +def test_array_format_controls_file_field_names( + array_format: ArrayFormat, + expected_top_level: list[tuple[str, FileTypes]], + expected_nested: list[tuple[str, FileTypes]], +) -> None: + top_level = {"files": [b"a", b"b"]} + assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level + + nested = {"items": [{"file": b"a"}, {"file": b"b"}]} + assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py index b76db4fd..23535a30 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -131,7 +131,7 @@ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: copied = deepcopy_with_paths(original, [["items", "", "file"]]) extracted = extract_files(copied, paths=[["items", "", "file"]]) - assert extracted == [("items[][file]", file1), ("items[][file]", file2)] + assert [entry for _, entry in extracted] == [file1, file2] assert original == { "items": [ {"file": file1, "extra": 1}, From b47f82eae3018ff8a9b055f4a9fea0dda6f04a45 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:03:10 +0000 Subject: [PATCH 125/174] feat: support setting headers via env --- src/reducto/_client.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/reducto/_client.py b/src/reducto/_client.py index 81ebe7a3..81b298b8 100644 --- a/src/reducto/_client.py +++ b/src/reducto/_client.py @@ -28,6 +28,7 @@ ) from ._utils import ( is_given, + is_mapping_t, extract_files, maybe_transform, get_async_library, @@ -150,6 +151,15 @@ def __init__( except KeyError as exc: raise ValueError(f"Unknown environment: {environment}") from exc + custom_headers_env = os.environ.get("REDUCTO_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -461,6 +471,15 @@ def __init__( except KeyError as exc: raise ValueError(f"Unknown environment: {environment}") from exc + custom_headers_env = os.environ.get("REDUCTO_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, From 43ba55168b863d3dda4313d9cccb6684b7db3b05 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:12:40 +0000 Subject: [PATCH 126/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 21fa6a0e..6fb27bef 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2f50309294626ee7c427ccd84a3749cf138b41ded635d70097e2be2a54d3c0be.yml -openapi_spec_hash: 4da30ee28a326eb915b4019b9a23e96c +openapi_spec_hash: dea140425522d3d264eb6b9fd5c9c03b config_hash: 9fa10baf03f994be027bf73b29ac8572 From b70ad2c24c758d085e6daae422852926bfc38838 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 17:12:41 +0000 Subject: [PATCH 127/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 6fb27bef..d7901383 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2f50309294626ee7c427ccd84a3749cf138b41ded635d70097e2be2a54d3c0be.yml -openapi_spec_hash: dea140425522d3d264eb6b9fd5c9c03b +openapi_spec_hash: 02e9045815c1c1ee4bc9374fab7ab55e config_hash: 9fa10baf03f994be027bf73b29ac8572 From 2ea6ae52f950909cbc6e69decd9bd763a92069b9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 22:12:37 +0000 Subject: [PATCH 128/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d7901383..2e369549 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-2f50309294626ee7c427ccd84a3749cf138b41ded635d70097e2be2a54d3c0be.yml -openapi_spec_hash: 02e9045815c1c1ee4bc9374fab7ab55e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-de71b9bc8500496762352188fc464d575a7d94ff41cf3baf8423e64c3a1c0aaf.yml +openapi_spec_hash: 1841b17f88715c4c91476af71f0abc90 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 4fddf5d25b0efa4237cc1ccaab03dcc1ac467ff8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 02:17:06 +0000 Subject: [PATCH 129/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/shared/parse_response.py | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2e369549..dbe49ed9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-de71b9bc8500496762352188fc464d575a7d94ff41cf3baf8423e64c3a1c0aaf.yml -openapi_spec_hash: 1841b17f88715c4c91476af71f0abc90 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac81c16ff47ec23203e959d323f6b4816087bf340dc4eb2dcabb15ed48337e84.yml +openapi_spec_hash: 7df17d6c72fc7843eab0e5fb23e8b7e3 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/shared/parse_response.py b/src/reducto/types/shared/parse_response.py index 8b54180f..a12ff7dc 100644 --- a/src/reducto/types/shared/parse_response.py +++ b/src/reducto/types/shared/parse_response.py @@ -177,6 +177,15 @@ class ParseResponse(BaseModel): usage: ParseUsage + parse_mode: Optional[Literal["base", "lite"]] = None + """Which pipeline produced this response. + + `lite` means Reducto Flash Lite served the request; `base` is the standard + pipeline. Optional / nullable for forward compatibility — older API instances or + persisted responses written before this field existed will leave it `None`; + treat `None` as `base`. + """ + pdf_url: Optional[str] = None """The storage URL of the converted PDF file.""" From 1bcccb21fc543f9a6c7d99b7877f8bdbe79b2780 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 21:12:34 +0000 Subject: [PATCH 130/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index dbe49ed9..8f406842 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac81c16ff47ec23203e959d323f6b4816087bf340dc4eb2dcabb15ed48337e84.yml -openapi_spec_hash: 7df17d6c72fc7843eab0e5fb23e8b7e3 +openapi_spec_hash: 0d5277ada3330061c022e90145b01a59 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 8f8e64e20c0a0309b259b221994966654cebf9b1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 22:12:36 +0000 Subject: [PATCH 131/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 8f406842..d773cf5d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac81c16ff47ec23203e959d323f6b4816087bf340dc4eb2dcabb15ed48337e84.yml -openapi_spec_hash: 0d5277ada3330061c022e90145b01a59 +openapi_spec_hash: 5868e8c1bc6bf0d926ef4d0bb7383a7f config_hash: 9fa10baf03f994be027bf73b29ac8572 From cd200eab12acdae6410002ad40c5e1cf13987077 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:12:35 +0000 Subject: [PATCH 132/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d773cf5d..2f26e8d9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac81c16ff47ec23203e959d323f6b4816087bf340dc4eb2dcabb15ed48337e84.yml -openapi_spec_hash: 5868e8c1bc6bf0d926ef4d0bb7383a7f +openapi_spec_hash: 92205884bdd964090fb5984f86f0fde1 config_hash: 9fa10baf03f994be027bf73b29ac8572 From f0db752aa323a676459d270ddb57784dca9e1ff7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 00:12:45 +0000 Subject: [PATCH 133/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 2f26e8d9..1a4e2e32 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac81c16ff47ec23203e959d323f6b4816087bf340dc4eb2dcabb15ed48337e84.yml -openapi_spec_hash: 92205884bdd964090fb5984f86f0fde1 +openapi_spec_hash: 3d8223815107cc9681a64808f44cb6dd config_hash: 9fa10baf03f994be027bf73b29ac8572 From 32405472700f46e7dffddf62dca7f4799696d924 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 02:11:03 +0000 Subject: [PATCH 134/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 1a4e2e32..d73e84a1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto%2Freductoai-ac81c16ff47ec23203e959d323f6b4816087bf340dc4eb2dcabb15ed48337e84.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-cc75e7708b252477b11026d1ecd1b346e3b922341f36fbc0615795ad7a40c455.yml openapi_spec_hash: 3d8223815107cc9681a64808f44cb6dd config_hash: 9fa10baf03f994be027bf73b29ac8572 From 69a43cc9abfbca4e3bc27467a72567c14ab99440 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:14:05 +0000 Subject: [PATCH 135/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d73e84a1..b0f26b0a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-cc75e7708b252477b11026d1ecd1b346e3b922341f36fbc0615795ad7a40c455.yml -openapi_spec_hash: 3d8223815107cc9681a64808f44cb6dd +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-24ca58230430b95c3defa4eb74d7e867ab8958a2dbb5e69ffbfac7433beeb6d0.yml +openapi_spec_hash: d991660e4d427830db01842024d6bf51 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 4d2d58e2dd78868ebd1f3023fa3da46881713c8b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 22:12:44 +0000 Subject: [PATCH 136/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b0f26b0a..efadcfc2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-24ca58230430b95c3defa4eb74d7e867ab8958a2dbb5e69ffbfac7433beeb6d0.yml -openapi_spec_hash: d991660e4d427830db01842024d6bf51 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-55041772e015cc21bdc1b2984e1775c32c2b2f8b9c5aae461819cebd99a6b1c6.yml +openapi_spec_hash: d26f16d90d429371d7e86266bf475584 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 0f8298fc2adcd0e874e1251b9b9c0cda8d3fd8da Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 01:12:45 +0000 Subject: [PATCH 137/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index efadcfc2..296b0f7f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-55041772e015cc21bdc1b2984e1775c32c2b2f8b9c5aae461819cebd99a6b1c6.yml -openapi_spec_hash: d26f16d90d429371d7e86266bf475584 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-e7db3577af4195155f5f382652d5bb5589b150d0f6e51be3a17394f684bdf217.yml +openapi_spec_hash: 06f95c19ecff0d30451aa1dbe6fb7c6e config_hash: 9fa10baf03f994be027bf73b29ac8572 From ffd8b1fff4c769979872f6f599aaf94de512da73 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:04:55 +0000 Subject: [PATCH 138/174] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 296b0f7f..a6410018 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-e7db3577af4195155f5f382652d5bb5589b150d0f6e51be3a17394f684bdf217.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-d32017996d4322082da97c33ea8a288f509d91a032960f3e00026c2c3688c188.yml openapi_spec_hash: 06f95c19ecff0d30451aa1dbe6fb7c6e config_hash: 9fa10baf03f994be027bf73b29ac8572 From b730f7615d96c360ef667a136033d0322a285900 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:07:19 +0000 Subject: [PATCH 139/174] chore(internal): reformat pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 13381b81..354ab96e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -168,7 +168,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/reducto/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ["src/reducto/_files.py", "_dev/.*.py", "tests/.*"] strict_equality = true implicit_reexport = true From 6adeab66c3575eb77c300a1ad3a48bb699f663e9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 02:13:10 +0000 Subject: [PATCH 140/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a6410018..e16dd9c1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-d32017996d4322082da97c33ea8a288f509d91a032960f3e00026c2c3688c188.yml -openapi_spec_hash: 06f95c19ecff0d30451aa1dbe6fb7c6e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-5f9d094a1be943503b3e198ba6ba6f68fc5131617c97dc9a4400ff266e3a294b.yml +openapi_spec_hash: 06f1edd64bc50cdfba6915360def3aa7 config_hash: 9fa10baf03f994be027bf73b29ac8572 From f892447dac4510e5800de4de9c8ff34c9ea7af4f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 03:17:36 +0000 Subject: [PATCH 141/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e16dd9c1..ca9138f8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-5f9d094a1be943503b3e198ba6ba6f68fc5131617c97dc9a4400ff266e3a294b.yml -openapi_spec_hash: 06f1edd64bc50cdfba6915360def3aa7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-2d8bff57f81adb28fc4b04c8b267dc132f40afe2c38d218de1b52427afb1ea36.yml +openapi_spec_hash: db771c4602742c83bf692cc58abf2a6c config_hash: 9fa10baf03f994be027bf73b29ac8572 From 5243eb8fc08fdc3272255071cc2998b1ae47bbdb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 19:13:06 +0000 Subject: [PATCH 142/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ca9138f8..863f4fc4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-2d8bff57f81adb28fc4b04c8b267dc132f40afe2c38d218de1b52427afb1ea36.yml -openapi_spec_hash: db771c4602742c83bf692cc58abf2a6c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-f7219d0c1c376e5639245e7ca225326b38f09c534f6ef908e7447802d1e29a03.yml +openapi_spec_hash: faf3d341cc6adb3a9ede472213b464c2 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 6c6e68a70141fa04a3c3f847cecebdf0ab601d22 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 21:12:51 +0000 Subject: [PATCH 143/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 863f4fc4..353397bc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-f7219d0c1c376e5639245e7ca225326b38f09c534f6ef908e7447802d1e29a03.yml -openapi_spec_hash: faf3d341cc6adb3a9ede472213b464c2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-032a2806824d99b07aafd1328089da23564c46c2bce491302238cf55594cf0ce.yml +openapi_spec_hash: d51c8036f87c2157abdcdbfc5ba8e10a config_hash: 9fa10baf03f994be027bf73b29ac8572 From 9198f6169579182adbb51f412d553aade0d05945 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 23:12:55 +0000 Subject: [PATCH 144/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 353397bc..5ced4276 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-032a2806824d99b07aafd1328089da23564c46c2bce491302238cf55594cf0ce.yml -openapi_spec_hash: d51c8036f87c2157abdcdbfc5ba8e10a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-3975a84ba81c606b5115c07bfec5008108f0971c0e3dd5e5ac8c8e1ab06239f1.yml +openapi_spec_hash: b9ddca019ee811f80db741521af72531 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 086a1dc8321cde962754fc275e8035e317d8b200 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 00:13:42 +0000 Subject: [PATCH 145/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5ced4276..24b01c94 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-3975a84ba81c606b5115c07bfec5008108f0971c0e3dd5e5ac8c8e1ab06239f1.yml -openapi_spec_hash: b9ddca019ee811f80db741521af72531 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-55c3ee1ecd2b29d48e89d03f2cbbf36f4bf3ed2947d7147061fc048003a74dd1.yml +openapi_spec_hash: 94d86c6dd3fc991a6fa2ef6c6e637a3f config_hash: 9fa10baf03f994be027bf73b29ac8572 From 11c1e2682e2e1603472d29c9078ec85d51283ac8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 02:12:49 +0000 Subject: [PATCH 146/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 24b01c94..19a4af4c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-55c3ee1ecd2b29d48e89d03f2cbbf36f4bf3ed2947d7147061fc048003a74dd1.yml -openapi_spec_hash: 94d86c6dd3fc991a6fa2ef6c6e637a3f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-4bd905dfa4008d43f6a2efbef55f9b9c63447ab1dad14c81c2989c493ce189d7.yml +openapi_spec_hash: ae195d8743cdd4bb67c38536a539b37a config_hash: 9fa10baf03f994be027bf73b29ac8572 From cb3bec2104de2b340b976dc133e8b748ee54a2d8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 17:12:57 +0000 Subject: [PATCH 147/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 19a4af4c..c86e05c2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-4bd905dfa4008d43f6a2efbef55f9b9c63447ab1dad14c81c2989c493ce189d7.yml -openapi_spec_hash: ae195d8743cdd4bb67c38536a539b37a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-8ed2b1d52422c70aeb79135f043f2b6d5788d5a7b087c2e5ddca37fdb354cc9e.yml +openapi_spec_hash: 9061a4ae713a2dfca76c8ac18fbbb925 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 1b0c1093372483ba323f5838a378b9803f771742 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 23:12:47 +0000 Subject: [PATCH 148/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c86e05c2..65cf630f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-8ed2b1d52422c70aeb79135f043f2b6d5788d5a7b087c2e5ddca37fdb354cc9e.yml -openapi_spec_hash: 9061a4ae713a2dfca76c8ac18fbbb925 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-25b7449bd2147f410926e6f4350002995b565a8407a3423dedb0bdac8531bcb2.yml +openapi_spec_hash: ebd0039c56a269699a6194ff5c3ef9a8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From ac39f40865648a4397cb525523a38a35e5382099 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 01:12:49 +0000 Subject: [PATCH 149/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 65cf630f..66bb7251 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-25b7449bd2147f410926e6f4350002995b565a8407a3423dedb0bdac8531bcb2.yml -openapi_spec_hash: ebd0039c56a269699a6194ff5c3ef9a8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-cb3cf66b5d7bce2611293d625ff261d65ecaf5eabe9f80be498f3a7e1ef7a9c1.yml +openapi_spec_hash: 7c32855cd63d165a4db9bca10cb9f326 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 61cc8c5f1a41a81404abbb90d5b6c8d33998bf7d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 02:13:46 +0000 Subject: [PATCH 150/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 66bb7251..689d076f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-cb3cf66b5d7bce2611293d625ff261d65ecaf5eabe9f80be498f3a7e1ef7a9c1.yml -openapi_spec_hash: 7c32855cd63d165a4db9bca10cb9f326 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-2befb3125d6493ec025731d2ae10f1a21775bf42bad2a5e4311fe5e1d17a9587.yml +openapi_spec_hash: 64bdaab1873556c9a33325612161cbf8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 76000bbb530e033c4881f2cab20afc33c36750c1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 16:12:49 +0000 Subject: [PATCH 151/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/shared/table_agentic.py | 6 ++++++ src/reducto/types/shared_params/table_agentic.py | 6 ++++++ tests/api_resources/test_extract.py | 6 ++++++ tests/api_resources/test_parse.py | 6 ++++++ tests/api_resources/test_split.py | 4 ++++ 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 689d076f..31992001 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-2befb3125d6493ec025731d2ae10f1a21775bf42bad2a5e4311fe5e1d17a9587.yml -openapi_spec_hash: 64bdaab1873556c9a33325612161cbf8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-1f1711667699b7bbbad32bc686d4502d14770786864465b6fbaa4f1fbef961dd.yml +openapi_spec_hash: 8b0f3e19958a0d2ca670af5f74698157 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/shared/table_agentic.py b/src/reducto/types/shared/table_agentic.py index 9ff59a81..3cd206e6 100644 --- a/src/reducto/types/shared/table_agentic.py +++ b/src/reducto/types/shared/table_agentic.py @@ -11,5 +11,11 @@ class TableAgentic(BaseModel): scope: Literal["table"] + mode: Optional[Literal["default", "auto"]] = None + """ + Routing mode for table agentic: 'default' runs enrichment on all tables, 'auto' + uses the router to skip tables where enrichment is unlikely to help. + """ + prompt: Optional[str] = None """Custom prompt for table agentic.""" diff --git a/src/reducto/types/shared_params/table_agentic.py b/src/reducto/types/shared_params/table_agentic.py index 30d3216d..ff586b84 100644 --- a/src/reducto/types/shared_params/table_agentic.py +++ b/src/reducto/types/shared_params/table_agentic.py @@ -11,5 +11,11 @@ class TableAgentic(TypedDict, total=False): scope: Required[Literal["table"]] + mode: Literal["default", "auto"] + """ + Routing mode for table agentic: 'default' runs enrichment on all tables, 'auto' + uses the router to skip tables where enrichment is unlikely to help. + """ + prompt: Optional[str] """Custom prompt for table agentic.""" diff --git a/tests/api_resources/test_extract.py b/tests/api_resources/test_extract.py index 4be4d104..a6f018df 100644 --- a/tests/api_resources/test_extract.py +++ b/tests/api_resources/test_extract.py @@ -42,6 +42,7 @@ def test_method_run_with_all_params_overload_1(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -158,6 +159,7 @@ def test_method_run_with_all_params_overload_2(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -274,6 +276,7 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -388,6 +391,7 @@ async def test_method_run_with_all_params_overload_1(self, async_client: AsyncRe "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -504,6 +508,7 @@ async def test_method_run_with_all_params_overload_2(self, async_client: AsyncRe "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -620,6 +625,7 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], diff --git a/tests/api_resources/test_parse.py b/tests/api_resources/test_parse.py index f74ce4e4..7159e156 100644 --- a/tests/api_resources/test_parse.py +++ b/tests/api_resources/test_parse.py @@ -37,6 +37,7 @@ def test_method_run_with_all_params_overload_1(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -137,6 +138,7 @@ def test_method_run_with_all_params_overload_2(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -238,6 +240,7 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -337,6 +340,7 @@ async def test_method_run_with_all_params_overload_1(self, async_client: AsyncRe "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -437,6 +441,7 @@ async def test_method_run_with_all_params_overload_2(self, async_client: AsyncRe "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -538,6 +543,7 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], diff --git a/tests/api_resources/test_split.py b/tests/api_resources/test_split.py index af41c588..ba85395a 100644 --- a/tests/api_resources/test_split.py +++ b/tests/api_resources/test_split.py @@ -48,6 +48,7 @@ def test_method_run_with_all_params(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -180,6 +181,7 @@ def test_method_run_job_with_all_params(self, client: Reducto) -> None: "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -310,6 +312,7 @@ async def test_method_run_with_all_params(self, async_client: AsyncReducto) -> N "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], @@ -442,6 +445,7 @@ async def test_method_run_job_with_all_params(self, async_client: AsyncReducto) "agentic": [ { "scope": "table", + "mode": "default", "prompt": "prompt", } ], From c8d8a6c2894f3aa1ded4cf9db3fc1a37bb685323 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 20:12:45 +0000 Subject: [PATCH 152/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 31992001..2037376a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-1f1711667699b7bbbad32bc686d4502d14770786864465b6fbaa4f1fbef961dd.yml -openapi_spec_hash: 8b0f3e19958a0d2ca670af5f74698157 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-51604d091aa95ed636bb049ea253cac756944f21dcfdfd2433b42dc64916432a.yml +openapi_spec_hash: 29cffd278cd4234667808c5590621053 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 01aa4ede827b9d32d26bcda6bf8a36f47500e54c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 22:12:41 +0000 Subject: [PATCH 153/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2037376a..35caafbb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-51604d091aa95ed636bb049ea253cac756944f21dcfdfd2433b42dc64916432a.yml -openapi_spec_hash: 29cffd278cd4234667808c5590621053 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-f52f656f312d9f4894b9f59ed6f7807fc6e46f2dc07479ecc629c5ae8f04af60.yml +openapi_spec_hash: 888fcfbd2b9a6035f4bce6e5346ffbac config_hash: 9fa10baf03f994be027bf73b29ac8572 From 03bdea10376e703973fdb2c11cfd55e348352e3f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 23:12:41 +0000 Subject: [PATCH 154/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 35caafbb..6a97487b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-f52f656f312d9f4894b9f59ed6f7807fc6e46f2dc07479ecc629c5ae8f04af60.yml -openapi_spec_hash: 888fcfbd2b9a6035f4bce6e5346ffbac +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-707d5849ae071d21679f8246a67d34ee944c8a25707ad0c9bf655cd4888902d9.yml +openapi_spec_hash: 561a585f5145ff7811f58a7410aa3fa6 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 7ba904905225d7dc411696f6b6511929effea24a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 02:13:44 +0000 Subject: [PATCH 155/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/shared/classify_response.py | 2 ++ src/reducto/types/shared/edit_response.py | 3 +++ src/reducto/types/shared/parse_response.py | 2 ++ src/reducto/types/shared/pipeline_response.py | 4 +++- src/reducto/types/shared/split_response.py | 2 ++ 6 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6a97487b..88892903 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-707d5849ae071d21679f8246a67d34ee944c8a25707ad0c9bf655cd4888902d9.yml -openapi_spec_hash: 561a585f5145ff7811f58a7410aa3fa6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-e5a5ecf28bab07b58f3fbf30b719b2efe5931f954d6cd1db690d5608bc30c9e9.yml +openapi_spec_hash: 23d4a9c7de8df5831f90762de9977f78 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/shared/classify_response.py b/src/reducto/types/shared/classify_response.py index 6b26edbf..1a2e07cc 100644 --- a/src/reducto/types/shared/classify_response.py +++ b/src/reducto/types/shared/classify_response.py @@ -54,3 +54,5 @@ class ClassifyResponse(BaseModel): response_confidence: Optional[ResponseConfidence] = None """Overall confidence breakdown for classification response.""" + + response_type: Optional[Literal["classify"]] = None diff --git a/src/reducto/types/shared/edit_response.py b/src/reducto/types/shared/edit_response.py index 3eac5af4..b8a4cf01 100644 --- a/src/reducto/types/shared/edit_response.py +++ b/src/reducto/types/shared/edit_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from typing_extensions import Literal from ..._models import BaseModel from ..edit_widget import EditWidget @@ -19,6 +20,8 @@ class EditResponse(BaseModel): List of widgets with their types, descriptions, and bounding boxes. """ + response_type: Optional[Literal["edit"]] = None + usage: Optional[ParseUsage] = None """ Usage information for the edit operation, including number of pages and credits diff --git a/src/reducto/types/shared/parse_response.py b/src/reducto/types/shared/parse_response.py index a12ff7dc..cafaeaf8 100644 --- a/src/reducto/types/shared/parse_response.py +++ b/src/reducto/types/shared/parse_response.py @@ -189,5 +189,7 @@ class ParseResponse(BaseModel): pdf_url: Optional[str] = None """The storage URL of the converted PDF file.""" + response_type: Optional[Literal["parse"]] = None + studio_link: Optional[str] = None """The link to the studio pipeline for the document.""" diff --git a/src/reducto/types/shared/pipeline_response.py b/src/reducto/types/shared/pipeline_response.py index 1c5359dd..c2e2deda 100644 --- a/src/reducto/types/shared/pipeline_response.py +++ b/src/reducto/types/shared/pipeline_response.py @@ -1,7 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Union, Optional -from typing_extensions import TypeAlias +from typing_extensions import Literal, TypeAlias from ..._models import BaseModel from ..v3_extract import V3Extract @@ -45,3 +45,5 @@ class PipelineResponse(BaseModel): result: Result usage: ParseUsage + + response_type: Optional[Literal["pipeline"]] = None diff --git a/src/reducto/types/shared/split_response.py b/src/reducto/types/shared/split_response.py index 18cae9ef..d6e74073 100644 --- a/src/reducto/types/shared/split_response.py +++ b/src/reducto/types/shared/split_response.py @@ -69,3 +69,5 @@ class SplitResponse(BaseModel): """The split result.""" usage: ParseUsage + + response_type: Optional[Literal["split"]] = None From 19797bf936e3b4f3a0a6488fef0c5dc711b0caa1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 04:12:46 +0000 Subject: [PATCH 156/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 88892903..d54ab8fe 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-e5a5ecf28bab07b58f3fbf30b719b2efe5931f954d6cd1db690d5608bc30c9e9.yml -openapi_spec_hash: 23d4a9c7de8df5831f90762de9977f78 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-cc5de7fba7d276b859c11aecdc2b64a8ca503364c774de987faa0879dfc68623.yml +openapi_spec_hash: f8ea70f4809d3a48e8cc6de9f4e906aa config_hash: 9fa10baf03f994be027bf73b29ac8572 From 88b838e8fcaa86cc86153051e7c032314c28d17e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 20:12:44 +0000 Subject: [PATCH 157/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index d54ab8fe..39135cd0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-cc5de7fba7d276b859c11aecdc2b64a8ca503364c774de987faa0879dfc68623.yml -openapi_spec_hash: f8ea70f4809d3a48e8cc6de9f4e906aa +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-e35fbe47ce261efb57712871dabec5dfffe713dfedb5781584966f8f1e8a1ebb.yml +openapi_spec_hash: 10a96ebc64cd6bf5999db5bc01f4fed1 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 264984f01fba913b7de40f992eb56a7b342ef502 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 23:12:39 +0000 Subject: [PATCH 158/174] feat(api): api update --- .stats.yml | 4 ++-- src/reducto/types/parse_usage.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 39135cd0..1b9d4a0e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-e35fbe47ce261efb57712871dabec5dfffe713dfedb5781584966f8f1e8a1ebb.yml -openapi_spec_hash: 10a96ebc64cd6bf5999db5bc01f4fed1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-61a5a39a4ad5d6a7f2935d44446e31b09a8f52d3a046cfd806023cf9f321d738.yml +openapi_spec_hash: 0a2da47121171b20c13bdcb43b554525 config_hash: 9fa10baf03f994be027bf73b29ac8572 diff --git a/src/reducto/types/parse_usage.py b/src/reducto/types/parse_usage.py index 17b20133..fe1e375d 100644 --- a/src/reducto/types/parse_usage.py +++ b/src/reducto/types/parse_usage.py @@ -28,6 +28,12 @@ class ParseUsage(BaseModel): "chart_agent", "spreadsheet_cells", "billable_spreadsheet_pages", + "enrich_table", + "figure_summary", + "table_summary", + "key_value", + "agentic_text", + "promptable_agentic_text", ] ], ] From 19ee21861340ef759937d27b36e9875e4f1156af Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 00:12:39 +0000 Subject: [PATCH 159/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 1b9d4a0e..228e1ad5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-61a5a39a4ad5d6a7f2935d44446e31b09a8f52d3a046cfd806023cf9f321d738.yml -openapi_spec_hash: 0a2da47121171b20c13bdcb43b554525 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-40704d41f1bcc436c6ad661094a5b1c123f82979561347e50e80ea7b3230e025.yml +openapi_spec_hash: fa3b0155b26080239d1a5eb93d501bd8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 14e74867b8cb5114e587bb1172c0ea2d376f3f51 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 19:12:51 +0000 Subject: [PATCH 160/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 228e1ad5..3e12ec30 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-40704d41f1bcc436c6ad661094a5b1c123f82979561347e50e80ea7b3230e025.yml -openapi_spec_hash: fa3b0155b26080239d1a5eb93d501bd8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-5bebe0b3fe50126772280fc52113517d5797e299db523126aa382f82661e66af.yml +openapi_spec_hash: 56eeaf7bef51ab7c26a673cf193007d1 config_hash: 9fa10baf03f994be027bf73b29ac8572 From e96f18fb5447f275d15b8612cf6d842205482385 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 02:12:54 +0000 Subject: [PATCH 161/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3e12ec30..217347e5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-5bebe0b3fe50126772280fc52113517d5797e299db523126aa382f82661e66af.yml -openapi_spec_hash: 56eeaf7bef51ab7c26a673cf193007d1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-aee5aae4f6683a58dd063d691df34808094386c5212cef9397cc04e6a9fa2a93.yml +openapi_spec_hash: 3c7ce51fccaec185552327cf989fcb56 config_hash: 9fa10baf03f994be027bf73b29ac8572 From db43b5e559be351912f89cda973761c157eaf670 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 17:12:45 +0000 Subject: [PATCH 162/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 217347e5..616ccc6c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-aee5aae4f6683a58dd063d691df34808094386c5212cef9397cc04e6a9fa2a93.yml -openapi_spec_hash: 3c7ce51fccaec185552327cf989fcb56 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-2bd6b70520c73f62ab6e293ade35d1194c253e8061684444bd591028bbb30405.yml +openapi_spec_hash: ad5aadf2b2d5cff36e6026ac3faf77f6 config_hash: 9fa10baf03f994be027bf73b29ac8572 From c0944a628df440b6614e268391c51025dfe59e72 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 18:13:22 +0000 Subject: [PATCH 163/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 616ccc6c..e6a4cb5e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-2bd6b70520c73f62ab6e293ade35d1194c253e8061684444bd591028bbb30405.yml -openapi_spec_hash: ad5aadf2b2d5cff36e6026ac3faf77f6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-ffc428647555495393b9b33017557705394caacce5f52e85f3ce934cc6f9d634.yml +openapi_spec_hash: 24fd69f6b60e9458b2f30475eb9e4916 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 671a63b1508f2264fac1c8b31ee951a36f96f096 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 19:17:17 +0000 Subject: [PATCH 164/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e6a4cb5e..c8682599 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-ffc428647555495393b9b33017557705394caacce5f52e85f3ce934cc6f9d634.yml -openapi_spec_hash: 24fd69f6b60e9458b2f30475eb9e4916 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-29ce2b42f4979ab1591d21f8c8153c030b17c59fda48bc2625f5d35d53775445.yml +openapi_spec_hash: e3888237bb0bb3d0bae09eda183605e5 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 18f023992301065b906f01e8f050bdb1ed8fee5b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 21:12:42 +0000 Subject: [PATCH 165/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c8682599..ecaa7935 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-29ce2b42f4979ab1591d21f8c8153c030b17c59fda48bc2625f5d35d53775445.yml -openapi_spec_hash: e3888237bb0bb3d0bae09eda183605e5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-6e1df8c36f28dca0c3a1f16d69314f49c0807c900b1179cdc2b8c1e978a5289b.yml +openapi_spec_hash: 2afce59ef90598bbe083ebf5c3b3aed8 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 073ae81f83373806f2aa138d9e70f398d2256666 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 23:12:41 +0000 Subject: [PATCH 166/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index ecaa7935..012ecb53 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-6e1df8c36f28dca0c3a1f16d69314f49c0807c900b1179cdc2b8c1e978a5289b.yml -openapi_spec_hash: 2afce59ef90598bbe083ebf5c3b3aed8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-23c5d121beac3d34b2ec37736b22130c1cd85bbabeae4761cfed978a9bd5e19f.yml +openapi_spec_hash: b790daf812587ef9437c0328e12fec9f config_hash: 9fa10baf03f994be027bf73b29ac8572 From a1ff433083fe90d02587da08f1b9ef79f5786e38 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 01:12:40 +0000 Subject: [PATCH 167/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 012ecb53..e4f38eca 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-23c5d121beac3d34b2ec37736b22130c1cd85bbabeae4761cfed978a9bd5e19f.yml -openapi_spec_hash: b790daf812587ef9437c0328e12fec9f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-aaad11c298d6d1476cdfc170bc999fde671b99d1bc28f6e93994bf33a00170e7.yml +openapi_spec_hash: 214abd9080401d79ee767884eab88fef config_hash: 9fa10baf03f994be027bf73b29ac8572 From f46575b18ced817fa61a12ef313195613a2abbeb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 02:06:18 +0000 Subject: [PATCH 168/174] fix(client): add missing f-string prefix in file type error message --- src/reducto/_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reducto/_files.py b/src/reducto/_files.py index a4b9a7b1..76c6988d 100644 --- a/src/reducto/_files.py +++ b/src/reducto/_files.py @@ -99,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles elif is_sequence_t(files): files = [(key, await _async_transform_file(file)) for key, file in files] else: - raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence") + raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence") return files From fb19781f06e216325310482228dc5c294ce0d620 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 23:12:39 +0000 Subject: [PATCH 169/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e4f38eca..4a5c2847 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-aaad11c298d6d1476cdfc170bc999fde671b99d1bc28f6e93994bf33a00170e7.yml -openapi_spec_hash: 214abd9080401d79ee767884eab88fef +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-b2200b5b43872f84047a8ff360e45627b038649b71f0979eb431bbaa404ca668.yml +openapi_spec_hash: 74d4045b8574743d5f51b94327111fb9 config_hash: 9fa10baf03f994be027bf73b29ac8572 From b3afa61544629b98e8bed6f5d1e6c739ff980677 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 14:13:06 +0000 Subject: [PATCH 170/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4a5c2847..7a52ab8f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-b2200b5b43872f84047a8ff360e45627b038649b71f0979eb431bbaa404ca668.yml -openapi_spec_hash: 74d4045b8574743d5f51b94327111fb9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-a9d511b055ee436485839ad21f47e9fe7c21ec7a95b744d036af147da1d481b6.yml +openapi_spec_hash: 16bf83ee9b278ecf7373fdbccf17c405 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 56bbbc1cc568541a6473253d499899e75381a037 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 18:12:50 +0000 Subject: [PATCH 171/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7a52ab8f..882d7572 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-a9d511b055ee436485839ad21f47e9fe7c21ec7a95b744d036af147da1d481b6.yml -openapi_spec_hash: 16bf83ee9b278ecf7373fdbccf17c405 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-925c56877ac66b34e5ba8696095e2188d52d8e88715e9c6fe05230f318f1e8df.yml +openapi_spec_hash: 64012c0b4ade8208f1c5d825762386f4 config_hash: 9fa10baf03f994be027bf73b29ac8572 From 618a68d2ca6077d9a4afa009385e8f9a4256264b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 22:12:56 +0000 Subject: [PATCH 172/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 882d7572..35fea27a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-925c56877ac66b34e5ba8696095e2188d52d8e88715e9c6fe05230f318f1e8df.yml -openapi_spec_hash: 64012c0b4ade8208f1c5d825762386f4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-ff291368aa2e84ac2e15b41e6090af20b6d444c95bf3573981373aecd1caa9bc.yml +openapi_spec_hash: b6dc7d2be9005d023e52a8850f776b8d config_hash: 9fa10baf03f994be027bf73b29ac8572 From e5eb30c5150c5c598180febb64b1c1927c609202 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 00:12:49 +0000 Subject: [PATCH 173/174] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 35fea27a..0196ef48 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 17 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-ff291368aa2e84ac2e15b41e6090af20b6d444c95bf3573981373aecd1caa9bc.yml -openapi_spec_hash: b6dc7d2be9005d023e52a8850f776b8d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/reducto/reductoai-b36868a5180fdbbf68e1ec04645b9f3cafae6e8149be90e2b57a249fc8e5f070.yml +openapi_spec_hash: 22a35f1691ac8540ee6141d7c800119a config_hash: 9fa10baf03f994be027bf73b29ac8572 From abde2eae6caf228e37756c2173dd495aba05ad4d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 02:05:34 +0000 Subject: [PATCH 174/174] feat(internal/types): support eagerly validating pydantic iterators --- src/reducto/_models.py | 80 ++++++++++++++++++++++++++++++++++++++++++ tests/test_models.py | 60 +++++++++++++++++++++++++++++-- 2 files changed, 137 insertions(+), 3 deletions(-) diff --git a/src/reducto/_models.py b/src/reducto/_models.py index 1819e148..0ec36119 100644 --- a/src/reducto/_models.py +++ b/src/reducto/_models.py @@ -25,7 +25,9 @@ ClassVar, Protocol, Required, + Annotated, ParamSpec, + TypeAlias, TypedDict, TypeGuard, final, @@ -79,7 +81,15 @@ from ._constants import RAW_RESPONSE_HEADER if TYPE_CHECKING: + from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler + from pydantic_core import CoreSchema, core_schema from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema +else: + try: + from pydantic_core import CoreSchema, core_schema + except ImportError: + CoreSchema = None + core_schema = None __all__ = ["BaseModel", "GenericModel"] @@ -396,6 +406,76 @@ def model_dump_json( ) +class _EagerIterable(list[_T], Generic[_T]): + """ + Accepts any Iterable[T] input (including generators), consumes it + eagerly, and validates all items upfront. + + Validation preserves the original container type where possible + (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON) + always emits a list — round-tripping through model_dump() will not + restore the original container type. + """ + + @classmethod + def __get_pydantic_core_schema__( + cls, + source_type: Any, + handler: GetCoreSchemaHandler, + ) -> CoreSchema: + (item_type,) = get_args(source_type) or (Any,) + item_schema: CoreSchema = handler.generate_schema(item_type) + list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema) + + return core_schema.no_info_wrap_validator_function( + cls._validate, + list_of_items_schema, + serialization=core_schema.plain_serializer_function_ser_schema( + cls._serialize, + info_arg=False, + ), + ) + + @staticmethod + def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any: + original_type: type[Any] = type(v) + + # Normalize to list so list_schema can validate each item + if isinstance(v, list): + items: list[_T] = v + else: + try: + items = list(v) + except TypeError as e: + raise TypeError("Value is not iterable") from e + + # Validate items against the inner schema + validated: list[_T] = handler(items) + + # Reconstruct original container type + if original_type is list: + return validated + # str(list) produces the list's repr, not a string built from items, + # so skip reconstruction for str and its subclasses. + if issubclass(original_type, str): + return validated + try: + return original_type(validated) + except (TypeError, ValueError): + # If the type cannot be reconstructed, just return the validated list + return validated + + @staticmethod + def _serialize(v: Iterable[_T]) -> list[_T]: + """Always serialize as a list so Pydantic's JSON encoder is happy.""" + if isinstance(v, list): + return v + return list(v) + + +EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable] + + def _construct_field(value: object, field: FieldInfo, key: str) -> object: if value is None: return field_get_default(field) diff --git a/tests/test_models.py b/tests/test_models.py index d14b760a..3e0e297f 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,8 @@ import json -from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast +from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast from datetime import datetime, timezone -from typing_extensions import Literal, Annotated, TypeAliasType +from collections import deque +from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType import pytest import pydantic @@ -9,7 +10,7 @@ from reducto._utils import PropertyInfo from reducto._compat import PYDANTIC_V1, parse_obj, model_dump, model_json -from reducto._models import DISCRIMINATOR_CACHE, BaseModel, construct_type +from reducto._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type class BasicModel(BaseModel): @@ -961,3 +962,56 @@ def __getattr__(self, attr: str) -> Item: ... assert model.a.prop == 1 assert isinstance(model.a, Item) assert model.other == "foo" + + +# NOTE: Workaround for Pydantic Iterable behavior. +# Iterable fields are replaced with a ValidatorIterator and may be consumed +# during serialization, which can cause subsequent dumps to return empty data. +# See: https://github.com/pydantic/pydantic/issues/9541 +@pytest.mark.parametrize( + "data, expected_validated", + [ + ([1, 2, 3], [1, 2, 3]), + ((1, 2, 3), (1, 2, 3)), + (set([1, 2, 3]), set([1, 2, 3])), + (iter([1, 2, 3]), [1, 2, 3]), + ([], []), + ((x for x in [1, 2, 3]), [1, 2, 3]), + (map(lambda x: x, [1, 2, 3]), [1, 2, 3]), + (frozenset([1, 2, 3]), frozenset([1, 2, 3])), + (deque([1, 2, 3]), deque([1, 2, 3])), + ], + ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"], +) +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None: + class TypeWithIterable(TypedDict): + items: EagerIterable[int] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": data}}) + assert m.data["items"] == expected_validated + + # Verify repeated dumps don't lose data (the original bug) + assert m.model_dump()["data"]["items"] == list(expected_validated) + assert m.model_dump()["data"]["items"] == list(expected_validated) + + +@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2") +def test_iterable_construction_str_falls_back_to_list() -> None: + # str is iterable (over chars), but str(list_of_chars) produces the list's repr + # rather than reconstructing a string from items. We special-case str to fall + # back to list instead of attempting reconstruction. + class TypeWithIterable(TypedDict): + items: EagerIterable[str] + + class Model(BaseModel): + data: TypeWithIterable + + m = Model.model_validate({"data": {"items": "hello"}}) + + # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"]) + assert m.data["items"] == ["h", "e", "l", "l", "o"] + assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"]