From f2d6868a152465ae879cc323a075489cc5723cf2 Mon Sep 17 00:00:00 2001 From: Chandrasekharan M Date: Fri, 18 Sep 2026 15:21:08 +0530 Subject: [PATCH 1/3] chore: sync the spec to Zipstack/unstract 3851a5866 and regenerate `ExecuteRequest` loses `use_file_history`, withdrawn from the published spec as an internal-only option (Zipstack/unstract#2290). The listing's `APIDeploymentSummary` gains `owner_emails`, added upstream since the last sync. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014f9oEEYspPH4fmPULTnLkJ --- specs/docstudio-oss.json | 13 +++++++++---- .../models/api_deployment_summary.py | 8 ++++++++ .../_sdk_docstudio/models/execute_request.py | 17 ----------------- tools/gen_sdk.sh | 4 ++-- 4 files changed, 19 insertions(+), 23 deletions(-) diff --git a/specs/docstudio-oss.json b/specs/docstudio-oss.json index 7bd536c..ea8039b 100644 --- a/specs/docstudio-oss.json +++ b/specs/docstudio-oss.json @@ -65,6 +65,14 @@ "readOnly": true, "type": "string" }, + "owner_emails": { + "description": "Email of each owner, earliest first. Empty if none is a person.", + "items": { + "type": "string" + }, + "readOnly": true, + "type": "array" + }, "run_count": { "readOnly": true, "type": "integer" @@ -91,6 +99,7 @@ "is_owner", "last_5_run_statuses", "last_run_time", + "owner_emails", "run_count", "workflow", "workflow_name" @@ -230,10 +239,6 @@ "maximum": 300, "minimum": -1, "type": "integer" - }, - "use_file_history": { - "default": false, - "type": "boolean" } }, "type": "object" diff --git a/src/unstract/api_deployments/_sdk_docstudio/models/api_deployment_summary.py b/src/unstract/api_deployments/_sdk_docstudio/models/api_deployment_summary.py index 643af06..81d488a 100644 --- a/src/unstract/api_deployments/_sdk_docstudio/models/api_deployment_summary.py +++ b/src/unstract/api_deployments/_sdk_docstudio/models/api_deployment_summary.py @@ -38,6 +38,7 @@ class APIDeploymentSummary: last_5_run_statuses (list[APIDeploymentSummaryLast5RunStatusesItem]): Fetch the last 5 execution statuses with timestamps for this API deployment. last_run_time (None | str): + owner_emails (list[str]): Email of each owner, earliest first. Empty if none is a person. run_count (int): workflow (UUID): workflow_name (str): @@ -55,6 +56,7 @@ class APIDeploymentSummary: is_owner: bool last_5_run_statuses: list[APIDeploymentSummaryLast5RunStatusesItem] last_run_time: None | str + owner_emails: list[str] run_count: int workflow: UUID workflow_name: str @@ -91,6 +93,8 @@ def to_dict(self) -> dict[str, Any]: last_run_time: None | str last_run_time = self.last_run_time + owner_emails = self.owner_emails + run_count = self.run_count workflow = str(self.workflow) @@ -113,6 +117,7 @@ def to_dict(self) -> dict[str, Any]: "is_owner": is_owner, "last_5_run_statuses": last_5_run_statuses, "last_run_time": last_run_time, + "owner_emails": owner_emails, "run_count": run_count, "workflow": workflow, "workflow_name": workflow_name, @@ -176,6 +181,8 @@ def _parse_last_run_time(data: object) -> None | str: last_run_time = _parse_last_run_time(d.pop("last_run_time")) + owner_emails = cast(list[str], d.pop("owner_emails")) + run_count = d.pop("run_count") workflow = UUID(d.pop("workflow")) @@ -195,6 +202,7 @@ def _parse_last_run_time(data: object) -> None | str: is_owner=is_owner, last_5_run_statuses=last_5_run_statuses, last_run_time=last_run_time, + owner_emails=owner_emails, run_count=run_count, workflow=workflow, workflow_name=workflow_name, diff --git a/src/unstract/api_deployments/_sdk_docstudio/models/execute_request.py b/src/unstract/api_deployments/_sdk_docstudio/models/execute_request.py index db0e7f9..a6c5332 100644 --- a/src/unstract/api_deployments/_sdk_docstudio/models/execute_request.py +++ b/src/unstract/api_deployments/_sdk_docstudio/models/execute_request.py @@ -34,7 +34,6 @@ class ExecuteRequest: presigned_urls (list[str] | Unset): tags (str | Unset): Comma-separated list of tag names (EX:'tag1,tag2-name,tag3_name') Default: ''. timeout (int | Unset): Default: -1. - use_file_history (bool | Unset): Default: False. """ custom_data: Any | Unset = UNSET @@ -48,7 +47,6 @@ class ExecuteRequest: presigned_urls: list[str] | Unset = UNSET tags: str | Unset = "" timeout: int | Unset = -1 - use_file_history: bool | Unset = False additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -94,8 +92,6 @@ def to_dict(self) -> dict[str, Any]: timeout = self.timeout - use_file_history = self.use_file_history - field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) @@ -121,8 +117,6 @@ def to_dict(self) -> dict[str, Any]: field_dict["tags"] = tags if timeout is not UNSET: field_dict["timeout"] = timeout - if use_file_history is not UNSET: - field_dict["use_file_history"] = use_file_history return field_dict @@ -225,14 +219,6 @@ def to_multipart(self) -> types.RequestFiles: if not isinstance(self.timeout, Unset): files.append(("timeout", (None, str(self.timeout).encode(), "text/plain"))) - if not isinstance(self.use_file_history, Unset): - files.append( - ( - "use_file_history", - (None, str(self.use_file_history).encode(), "text/plain"), - ) - ) - for prop_name, prop in self.additional_properties.items(): files.append((prop_name, (None, str(prop).encode(), "text/plain"))) @@ -291,8 +277,6 @@ def _parse_llm_profile_id(data: object) -> None | str | Unset: timeout = d.pop("timeout", UNSET) - use_file_history = d.pop("use_file_history", UNSET) - execute_request = cls( custom_data=custom_data, files=files, @@ -305,7 +289,6 @@ def _parse_llm_profile_id(data: object) -> None | str | Unset: presigned_urls=presigned_urls, tags=tags, timeout=timeout, - use_file_history=use_file_history, ) execute_request.additional_properties = d diff --git a/tools/gen_sdk.sh b/tools/gen_sdk.sh index 6af0723..0aba390 100755 --- a/tools/gen_sdk.sh +++ b/tools/gen_sdk.sh @@ -21,8 +21,8 @@ # both this script and the drift gate report clean either way. # # SPEC_SOURCE: Zipstack/unstract specs/docstudio-oss.json -# @ 520b98d7acf5a6d138b24d9787bd788b89150b76 -# sha256 68a31eaf72e54daf8173ae3ef42e4174b257c93b1afc608114582a81be135bd1 +# @ 3851a5866e92c2607b4a7323f5618a6a21c7ae8b +# sha256 c0de3a14254600f436483561ebb737feac4023fdef2fd46d6abf15dcd2182ad9 set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" From 8a445056b39be867776ae551ef1480d00cf5fb8c Mon Sep 17 00:00:00 2001 From: Chandrasekharan M Date: Fri, 18 Sep 2026 15:23:38 +0530 Subject: [PATCH 2/3] fix: drop the use_file_history keyword from structure_file The generated request no longer carries the field, so the facade could only have raised on it at the call; refusing it at the signature says so up front. The listing fixture gains the owner_emails the spec now requires. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014f9oEEYspPH4fmPULTnLkJ --- src/unstract/api_deployments/client.py | 3 --- tests/test_compat.py | 20 ++++++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/unstract/api_deployments/client.py b/src/unstract/api_deployments/client.py index df57093..a246a87 100644 --- a/src/unstract/api_deployments/client.py +++ b/src/unstract/api_deployments/client.py @@ -616,7 +616,6 @@ def structure_file( include_metadata: bool | Unset = UNSET, include_metrics: bool | Unset = UNSET, include_extracted_text: bool | Unset = UNSET, - use_file_history: bool | Unset = UNSET, tags: str | Unset = UNSET, llm_profile_id: str | None | Unset = UNSET, hitl_queue_name: str | None | Unset = UNSET, @@ -638,7 +637,6 @@ def structure_file( include_metadata (bool): Include metadata in the result. include_metrics (bool): Include metrics in the result. include_extracted_text (bool): Include the extracted text. - use_file_history (bool): Reuse a previous result for the same file. tags (str): Comma-separated tag names. llm_profile_id (str): LLM profile to override the deployment's. hitl_queue_name (str): Human-in-the-loop queue to route the file to. @@ -662,7 +660,6 @@ def structure_file( "include_metadata": include_metadata, "include_metrics": include_metrics, "include_extracted_text": include_extracted_text, - "use_file_history": use_file_history, "tags": tags, "llm_profile_id": llm_profile_id, "hitl_queue_name": hitl_queue_name, diff --git a/tests/test_compat.py b/tests/test_compat.py index 8fe3bf4..98ceef4 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -36,6 +36,7 @@ from unittest.mock import MagicMock, patch from urllib.parse import parse_qs, urlparse +import attrs import httpx import pytest import requests @@ -58,6 +59,7 @@ WhoAmIResponse, ) from unstract.api_deployments._sdk_docstudio import AuthenticatedClient +from unstract.api_deployments._sdk_docstudio.models import ExecuteRequest from unstract.api_deployments._sdk_docstudio.types import UNSET from unstract.api_deployments.client import ( _EXECUTE_SEND_ONLY, @@ -469,11 +471,24 @@ def test_a_requested_parameter_is_sent(sample_file): sample_file, tags="a,b", llm_profile_id="profile-1", - use_file_history=True, ) assert parts["tags"][1] == b"a,b" assert parts["llm_profile_id"][1] == b"profile-1" - assert parts["use_file_history"][1] == b"True" + + +def test_the_withdrawn_option_is_refused_not_forwarded(sample_file): + """`use_file_history` is internal-only and no longer in the published spec. + Accepting it here would send a field the contract does not declare.""" + with pytest.raises(TypeError): + _client().structure_file([sample_file], use_file_history=True) + + +def test_no_execute_part_is_the_withdrawn_option(sample_file): + """Neither the facade nor the generated request can put it on the wire.""" + parts = _execute_parts(_client(api_timeout=300), sample_file) + assert "use_file_history" not in parts + assert "use_file_history" not in _EXECUTE_SEND_ONLY + assert "use_file_history" not in attrs.fields_dict(ExecuteRequest) @pytest.mark.parametrize( @@ -1994,6 +2009,7 @@ def _deployment_page() -> dict: "created_by_email": "a@b.c", "co_owners_count": 0, "is_owner": True, + "owner_emails": ["a@b.c"], "last_run_time": None, "run_count": 0, "last_5_run_statuses": [], From 192d7b330dec8505d1cf47ec9f0ae9954535bb91 Mon Sep 17 00:00:00 2001 From: Chandrasekharan M Date: Fri, 18 Sep 2026 15:51:11 +0530 Subject: [PATCH 3/3] test: claim only what the facade and the model's declared fields guarantee Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014f9oEEYspPH4fmPULTnLkJ --- tests/test_compat.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_compat.py b/tests/test_compat.py index 98ceef4..3f8ef97 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -483,8 +483,12 @@ def test_the_withdrawn_option_is_refused_not_forwarded(sample_file): _client().structure_file([sample_file], use_file_history=True) -def test_no_execute_part_is_the_withdrawn_option(sample_file): - """Neither the facade nor the generated request can put it on the wire.""" +def test_the_facade_neither_sends_nor_the_model_declares_the_withdrawn_option( + sample_file, +): + """The generated model still forwards any unknown key a direct caller puts + in `additional_properties`; the claim here is only about the facade's + request and the model's declared fields.""" parts = _execute_parts(_client(api_timeout=300), sample_file) assert "use_file_history" not in parts assert "use_file_history" not in _EXECUTE_SEND_ONLY