Skip to content

feat: expose the API's declared error verdict as typed attributes - #360

Open
CyMule wants to merge 6 commits into
mainfrom
feat/typed-declared-error-fields
Open

CyMule wants to merge 6 commits into
mainfrom
feat/typed-declared-error-fields

Conversation

@CyMule

@CyMule CyMule commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What

HTTP errors from the client now carry the verdict the API declared, as typed attributes. Callers no longer have to re-parse err.body.

# Response: 503 {"detail": "OCR timed out", "retryable": false,
#                "error_type": "timeout", "error_reason": "ocr_timeout", "audience": "platform"}

# Before
err.retryable                              # AttributeError
json.loads(err.body).get("retryable")      # False, but every caller re-implements the parsing

# After
err.retryable     # False
err.error_type    # "timeout"
err.error_reason  # "ocr_timeout"
err.audience      # "platform"
Error body retryable / error_type / error_reason / audience
Declares all four fields the declared values
{"detail": "Internal server error"} (declares nothing) all None
A field with the wrong type, e.g. "retryable": "false" that field is None
Not JSON, or not a JSON object all None

The attributes are on UnstructuredClientError, so they work for every HTTP error class at any status (SDKError for 4xx, ServerError, HTTPValidationError). That includes the response a failed split-PDF chunk returns.

Why

The API publishes retryable, error_type, error_reason and audience next to detail on errors it raises deliberately. The client's error models only expose detail, so any caller that wants to act on the verdict has to parse the raw body itself. Each caller then re-implements the same type checks and tends to get the edge cases slightly different.

The retry loop already reads retryable to stop retrying a declared non-retryable error (0.46.4). The loop and the error attributes now share one parser, so they cannot disagree about what a body declares. Both decode the response bytes, so a UTF-8 BOM or a UTF-16 body is read the same way response.json() reads it.

Notes for consumers

  • Additive only. No existing attribute, message or retry decision changes.
  • A ResponseValidationError raised on a 2xx response always reads None, and its (possibly large) body is not parsed again.
  • An unread streamed response is never consumed to read the verdict.

Validation

  • Unit suite: 403 passed, 1 xfailed. make lint is clean (pylint 10/10, mypy).
  • New tests cover the parser's type rules, each error class with UTF-8, UTF-8-BOM and UTF-16 bodies, the unread-stream fallback, 2xx validation errors, and public partition / partition_async (400 and 503) through a mocked transport.
  • Four rounds of Codex review. Rounds 2 and 3 found that decoding text instead of bytes lost JSON encoding detection, in the retry check and then in the error attributes. Both are fixed with tests.

View guided diff

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Shadow auto-approve: would not auto-approve. This PR does not meet the repository auto-approval settings.

View guided diff | Re-trigger cubic

…rror-fields

# Conflicts:
#	CHANGELOG.md
#	src/unstructured_client/_version.py
Reconcile release notes and versions without changing authored behavior.
Retain main dependency updates and the exact certifi lock record.
All nonmetadata files equal the automatic merge of main.

@cragwolfe cragwolfe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head f056cc461a756a1ba2f72f5874f264d5e112857b with GPT Pro. No actionable defects found.

The shared parser preserves strict boolean retry decisions and byte-based JSON encoding detection. The new attributes preserve existing exception behavior, avoid consuming unread streams, and skip parsing for successful-response validation errors. The checked-in tests cover these boundaries and public synchronous/asynchronous failures. The reported encoding fixes are present in the current source.

All 20 reported checks on this head passed. No local tests were run.

(authored by codex)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants