You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add client.parse() for the Data Extraction API (/extraction/parse) (#47)
* feat: add client.parse() for the Data Extraction API (/extraction/parse)
Adds first-class support for the Data Extraction API on NutrientClient.
Covers all four processing modes (text, structure, understand, agentic)
and both output shapes (spatial elements and whole-document Markdown).
The response surface is a fully typed ParseResponse TypedDict with a
discriminated union of element variants (paragraph, table, formula,
picture, keyValueRegion, handwriting) so callers can narrow on `type`.
The Data Extraction API is billed against extraction credits, which are
a separate billing bucket from the processor API credits consumed by the
other endpoints used by this client (Build, sign, OCR, watermarking,
etc.). Docstrings, README, and changelog make that distinction explicit
so callers do not conflate the two buckets.
Verification:
- 16 new unit tests in tests/unit/test_parse.py (request shape per mode,
response parsing, error propagation for 401 / 400 / 402 / 500).
- mypy strict and ruff clean on src/.
Endpoint surface (httpx-multipart): POST /extraction/parse with a
'file' part and an optional 'instructions' part carrying the JSON
{mode, output:{format}} body. Extends the existing send_request infra
(RequestConfig + TypeGuard + overload) without churn to existing
endpoint paths.
* refactor(types): extract ExtractionCredits to dedicated module
The extraction-credits accounting shape (cost + remainingCredits) will
surface on every future endpoint billed against the extraction-credits
bucket, not just /extraction/parse. Factor it out of types/parse.py into
its own module so other endpoints can import it without pulling in the
whole parse type tree.
Also clarify ParseBounds: document that (x, y) is the top-left corner
and that bounds share a coordinate space with the page dimensions in
ParsePageRef.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(client): align parse() style with the rest of the file
Three small style nits surfaced in code review against the patterns set
by sign() and the other raw-send_request methods (get_account_info,
create_token, delete_token):
- Drop the redundant inner cast("ParseOutput", {"format": output_format}).
ParseOutput is a single-key TypedDict with total=False; the literal
already satisfies it structurally via the surrounding ParseInstructions
annotation. No other call site in client.py casts an inner literal
this way.
- Replace the RequestConfig(...) constructor call with an inline dict
literal at the send_request boundary, matching sign / create_token /
delete_token / get_account_info. RequestConfig is a generic TypedDict;
the constructor form is the outlier.
- Broaden the file parameter docstring to call out that the endpoint
accepts PDFs, Office documents, and images. Unlike sign(), parsing is
not PDF-only, and the previous docstring implicitly invited readers
to transplant sign()'s PDF-only mental model.
No behavior change.
format) combinations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: explain what client.parse() is good for
The README's Data Extraction section previously described WHAT parse()
does (modes, output formats, billing) without explaining WHY a user
would reach for it over the existing extract_* helpers. Rework so the
positioning leads:
- New "designed for" bullets up top — RAG ingestion, search indexing,
content migration, form/invoice extraction, layout-aware document
understanding.
- New output-format selector table mapping each format to its primary
use case (markdown → RAG/search; spatial → form/layout).
- Modes table reworded so each row says when to pick it, not just what
it technically does (text = born-digital only; structure = OCR for
scanned input; understand = AI-augmented for complex layouts; agentic
= + VLM for image-heavy content).
- Two worked recipes: RAG ingestion (PDF → markdown → embed) and form
extraction (PDF → spatial elements → structured dict).
Also adds a parse() entry to docs/METHODS.md (it was missing entirely)
and a "Designed for" preamble to the parse() docstring so the method's
positioning is visible in IDE hover popups.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(client): route parse() via DWS Extract key and reject text+spatial
DWS Extract is a separate product from DWS Processor with its own API key
and credit pool. Calling /extraction/parse with the Processor key returns
403. Add an optional extract_api_key constructor parameter (str or async
callable) that parse() prefers over api_key when set; non-parse methods
keep using api_key. Falling back to api_key keeps a single-key setup
working once tenants get global DWS keys.
Also reject mode='text' + output_format='spatial' before the request goes
out — the text mode only produces markdown, so the combination would 502
on the server side. Surface it as a ValidationError with guidance.
Addresses PR #47 review feedback from HungKNguyen.
* fix(types): align ParsePageRef TypedDict shape with its docstring
The docstring promises pageIndex/width/height are always populated and
only pageNumber may be absent, but the class was declared `total=False`,
which contradicts that and forces type-strict callers to guard every
subscript access on guaranteed-present fields. Switch to the default
(`total=True`) shape with pageNumber explicitly `NotRequired`, matching
the precedent set by ParseBounds in the same module.
No runtime impact — the wire already populates these fields.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|`markdown`| RAG, search indexing, content migration — anywhere structured text beats spatial data | One whole-document Markdown string at `response['output']['markdown']`|
124
+
|`spatial` (default) | Form/invoice extraction, layout reconstruction, flows that need per-element confidence | Flat list of typed elements at `response['output']['elements']`|
125
+
126
+
Spatial output requires an OCR-capable mode (`structure`, `understand`, or
127
+
`agentic`); `mode='text'` is markdown-only and the client rejects the
128
+
`text` + `spatial` combination before the request goes out.
129
+
130
+
### Quick start
131
+
132
+
```python
133
+
import asyncio
134
+
from nutrient_dws import NutrientClient
135
+
136
+
asyncdefmain():
137
+
client = NutrientClient(
138
+
api_key='your_processor_key',
139
+
extract_api_key='your_extract_key',
140
+
)
141
+
142
+
# Spatial elements (default) — paragraphs, tables, formulas, pictures, etc.
|`text`| 1 | Born-digital documents only. No OCR, no AI. Fastest and cheapest path to Markdown. |
162
+
|`structure`| 1.5 | OCR-based segmentation with bounding boxes. Handles scanned documents, images, and any input requiring OCR. |
163
+
|`understand`| 9 | Full pipeline with AI augmentation on top of OCR. Most accurate for documents with tables, multi-column layouts, formulas, and form fields. |
164
+
|`agentic`| 18 | Builds on `understand` and adds a vision-language model. Best for image descriptions, complex visual layouts, and deeper semantic understanding. |
0 commit comments