structx-sdk 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- structx_sdk-0.2.0/.gitignore +20 -0
- structx_sdk-0.2.0/CHANGELOG.md +44 -0
- structx_sdk-0.2.0/LICENSE +21 -0
- structx_sdk-0.2.0/PKG-INFO +217 -0
- structx_sdk-0.2.0/README.md +181 -0
- structx_sdk-0.2.0/pyproject.toml +90 -0
- structx_sdk-0.2.0/src/structx_sdk/__init__.py +82 -0
- structx_sdk-0.2.0/src/structx_sdk/_client.py +526 -0
- structx_sdk-0.2.0/src/structx_sdk/_exceptions.py +177 -0
- structx_sdk-0.2.0/src/structx_sdk/_models.py +154 -0
- structx_sdk-0.2.0/src/structx_sdk/_version.py +1 -0
- structx_sdk-0.2.0/src/structx_sdk/py.typed +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `structx-sdk` Python SDK are documented here.
|
|
4
|
+
This project follows [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [0.2.0] — 2026-05-25
|
|
7
|
+
|
|
8
|
+
### BREAKING
|
|
9
|
+
- Renamed the package from `structx` to `structx-sdk` on PyPI. Pre-existing unrelated package at `structx` on PyPI made coexistence impossible.
|
|
10
|
+
- Renamed the Python module from `structx` to `structx_sdk`. Update imports: `from structx_sdk import StructX, AsyncStructX, ...`
|
|
11
|
+
- Renamed the public classes to use capital `X`: `Structx` → `StructX`, `AsyncStructx` → `AsyncStructX`, `StructxError` → `StructXError`. Brand-consistent with marketing's "Struct-X" spelling.
|
|
12
|
+
- Env variables (`STRUCTX_API_KEY`, `STRUCTX_BASE_URL`) unchanged.
|
|
13
|
+
- User-Agent header prefix updated to `structx-sdk/<version>`.
|
|
14
|
+
|
|
15
|
+
### Migration
|
|
16
|
+
Replace `from structx import Structx` with `from structx_sdk import StructX`. The class API is otherwise unchanged.
|
|
17
|
+
|
|
18
|
+
## [0.1.1] - 2026-05-25
|
|
19
|
+
|
|
20
|
+
### Security
|
|
21
|
+
- `ApiError.response_body` now has sensitive-key values redacted at exception-construction time (Phase 5.5 / rho). Closes a credential/PII leak that surfaced when customer code logged `repr(exc)` or routed exceptions to error trackers like Sentry. Redacted keys (case-insensitive): `x-api-key`, `authorization`, `apikey`, `api_key`, `password`, `token`, `secret`, `private_key`, `content`. Values become `<redacted N chars>` (length preserved for debugging).
|
|
22
|
+
- `ApiError.__repr__` now omits `response_body` entirely — second layer of defense against accidental `f"{exc!r}"` logging.
|
|
23
|
+
- Backward compatible: `.response_body` attribute is still accessible for programmatic inspection; only the values for sensitive keys are sentinelized.
|
|
24
|
+
|
|
25
|
+
## [0.1.0] — 2026-05-24
|
|
26
|
+
|
|
27
|
+
Initial release.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- `Structx` sync client + `AsyncStructx` async client (parallel surfaces).
|
|
31
|
+
- `extract(content, *, schema=None, template_slug=None, tier=..., options=...)`.
|
|
32
|
+
- `infer_schema(content, *, content_type=None, hints=None, ...)`.
|
|
33
|
+
- `list_templates()`, `list_models()`, `usage()`.
|
|
34
|
+
- Typed exception hierarchy: `StructxError → ApiError → {Authentication, PermissionDenied, NotFound, Validation, RateLimit, Server}Error`, plus `TransportError` for network failures.
|
|
35
|
+
- Typed Pydantic response models: `Extraction`, `FieldConfidence`, `TokenCounts`, `InferenceResult`, `InferredSchema`, `InferredField`, `Recommendation`, `Template`, `Model`, `Usage`. All accept extra fields silently for forward compatibility.
|
|
36
|
+
- `RetryPolicy` with read-vs-write retry differentiation — reads auto-retry on 5xx, writes only on transport errors (avoid double-billing).
|
|
37
|
+
- `STRUCTX_API_KEY` / `STRUCTX_BASE_URL` environment fallbacks, plus `Structx.from_env()`.
|
|
38
|
+
- Context-manager support (`with Structx(...)` closes the underlying httpx client).
|
|
39
|
+
- `py.typed` marker for full type-checker support.
|
|
40
|
+
|
|
41
|
+
### Compatible with
|
|
42
|
+
- Python 3.10, 3.11, 3.12, 3.13.
|
|
43
|
+
- struct-x API v1.x.
|
|
44
|
+
- httpx 0.27 / 0.28; pydantic 2.5+.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 struct-x
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: structx-sdk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Official Python SDK (structx-sdk) for struct-x — agent-native structured extraction.
|
|
5
|
+
Project-URL: Homepage, https://structx.ai
|
|
6
|
+
Project-URL: Documentation, https://docs.structx.ai
|
|
7
|
+
Project-URL: Repository, https://github.com/struct-x-ai/struct-x
|
|
8
|
+
Project-URL: Issues, https://github.com/struct-x-ai/struct-x/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/struct-x-ai/struct-x/blob/main/sdk/python/CHANGELOG.md
|
|
10
|
+
Author-email: struct-x <support@structx.ai>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agent,ai,extraction,json-schema,llm,mcp,structured-extraction
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: httpx<0.29,>=0.27
|
|
28
|
+
Requires-Dist: pydantic<3.0,>=2.5
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# structx-sdk — Python SDK for struct-x
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/structx-sdk/)
|
|
40
|
+
[](https://pypi.org/project/structx-sdk/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
|
|
43
|
+
Official Python client for **[struct-x](https://structx.ai)** — the agent-native structured-extraction API. Send raw content and a JSON Schema, get back validated, typed JSON with per-field confidence scores.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install structx-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quickstart
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from structx_sdk import StructX
|
|
55
|
+
|
|
56
|
+
client = StructX(api_key="sx_...")
|
|
57
|
+
|
|
58
|
+
result = client.extract(
|
|
59
|
+
content="<div><h1>Aeron Chair</h1><span>$1,795.00</span></div>",
|
|
60
|
+
schema={
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": ["title", "price_cents"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"title": {"type": "string"},
|
|
65
|
+
"price_cents": {"type": "integer"},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
print(result.data)
|
|
71
|
+
# {'title': 'Aeron Chair', 'price_cents': 179500}
|
|
72
|
+
|
|
73
|
+
print(result.field_confidences[0])
|
|
74
|
+
# FieldConfidence(field='title', confidence=0.96, source_snippet=None)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Use a catalog template instead of an inline schema
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
result = client.extract(
|
|
81
|
+
content=stripe_webhook_payload,
|
|
82
|
+
template_slug="logs.stripe.event", # latest published version
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Pin to a specific template version with `family_slug@version`:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
template_slug="logs.stripe.event@1.0.0"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Don't have a schema yet? Let the API infer one
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
inference = client.infer_schema(
|
|
96
|
+
content="<html>… some product page …</html>",
|
|
97
|
+
content_type="html",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print(inference.inferred.json_schema) # ready to pass back to extract()
|
|
101
|
+
for f in inference.inferred.fields:
|
|
102
|
+
print(f"{f.name} ({f.type}) — {f.rationale}")
|
|
103
|
+
|
|
104
|
+
# Plus template recommendations, if any matched:
|
|
105
|
+
for r in inference.recommendations:
|
|
106
|
+
print(f"{r.slug} (score={r.score:.2f})")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Async
|
|
110
|
+
|
|
111
|
+
Same surface, `await`-flavored:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import asyncio
|
|
115
|
+
from structx_sdk import AsyncStructX
|
|
116
|
+
|
|
117
|
+
async def main():
|
|
118
|
+
async with AsyncStructX(api_key="sx_...") as client:
|
|
119
|
+
result = await client.extract(content="…", schema={…})
|
|
120
|
+
print(result.data)
|
|
121
|
+
|
|
122
|
+
asyncio.run(main())
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Configuration
|
|
126
|
+
|
|
127
|
+
| Param | Default | Notes |
|
|
128
|
+
|----------------|-------------------------------|--------------------------------------------------------|
|
|
129
|
+
| `api_key` | `STRUCTX_API_KEY` env var | Required. |
|
|
130
|
+
| `base_url` | `STRUCTX_BASE_URL` env var, else `https://api.structx.ai` | Override for staging / self-hosted. |
|
|
131
|
+
| `timeout` | `30.0` seconds | Applied per request. |
|
|
132
|
+
| `retry` | `RetryPolicy(max_attempts=3, …)` | Tune via `RetryPolicy(...)`. |
|
|
133
|
+
| `default_headers` | `{}` | Merged into every request — e.g., for tracing IDs. |
|
|
134
|
+
|
|
135
|
+
Pick up credentials from the environment with `StructX.from_env()`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import os
|
|
139
|
+
os.environ["STRUCTX_API_KEY"] = "sx_..."
|
|
140
|
+
|
|
141
|
+
from structx_sdk import StructX
|
|
142
|
+
client = StructX.from_env()
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Errors
|
|
146
|
+
|
|
147
|
+
All exceptions inherit from `StructXError`. Catch the specific class you care about:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from structx_sdk import RateLimitError, ValidationError, ServerError
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
result = client.extract(content=…, schema=…)
|
|
154
|
+
except RateLimitError as e:
|
|
155
|
+
# 429 — back off; e.retry_after, e.credits_used, e.credits_remaining are populated
|
|
156
|
+
print(f"Sleep {e.retry_after}s. Used {e.credits_used}/{e.credits_used + e.credits_remaining}.")
|
|
157
|
+
except ValidationError as e:
|
|
158
|
+
# 400/422 — fix your input. e.code carries the machine-readable reason.
|
|
159
|
+
print(f"Bad input: {e.code} — {e.message}")
|
|
160
|
+
except ServerError as e:
|
|
161
|
+
# 5xx — retry or contact support; e.request_id is your handle.
|
|
162
|
+
print(f"Server error (request_id={e.request_id})")
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Full hierarchy:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
StructXError
|
|
169
|
+
├── TransportError # network failure — request never reached the server
|
|
170
|
+
└── ApiError # server responded with an error status
|
|
171
|
+
├── AuthenticationError # 401
|
|
172
|
+
├── PermissionDeniedError # 403
|
|
173
|
+
├── NotFoundError # 404
|
|
174
|
+
├── ValidationError # 400, 422
|
|
175
|
+
├── RateLimitError # 429 (carries retry_after, credits info)
|
|
176
|
+
└── ServerError # 5xx
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Retries
|
|
180
|
+
|
|
181
|
+
By default, **read** calls (`list_templates`, `list_models`, `usage`) auto-retry on transient 5xx and connection errors with exponential backoff.
|
|
182
|
+
|
|
183
|
+
**Write** calls (`extract`, `infer_schema`) retry ONLY on transport errors, never on 5xx — because a 5xx after a partial backend run may have already billed the call.
|
|
184
|
+
|
|
185
|
+
Customize via `RetryPolicy`:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from structx_sdk import StructX, RetryPolicy
|
|
189
|
+
|
|
190
|
+
client = StructX(
|
|
191
|
+
api_key="sx_...",
|
|
192
|
+
retry=RetryPolicy(
|
|
193
|
+
max_attempts=5,
|
|
194
|
+
initial_backoff=0.5,
|
|
195
|
+
max_backoff=60.0,
|
|
196
|
+
retry_on_5xx=True,
|
|
197
|
+
respect_retry_after=True,
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Forward compatibility
|
|
203
|
+
|
|
204
|
+
Response models accept extra fields silently. When the API adds a new field, old SDK versions don't break — they just don't surface it as a typed attribute. Reach it via `result.model_dump()` or `result.__pydantic_extra__`.
|
|
205
|
+
|
|
206
|
+
## Development
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
git clone https://github.com/struct-x-ai/struct-x
|
|
210
|
+
cd struct-x/sdk/python
|
|
211
|
+
pip install -e ".[dev]"
|
|
212
|
+
pytest -q
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# structx-sdk — Python SDK for struct-x
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/structx-sdk/)
|
|
4
|
+
[](https://pypi.org/project/structx-sdk/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Official Python client for **[struct-x](https://structx.ai)** — the agent-native structured-extraction API. Send raw content and a JSON Schema, get back validated, typed JSON with per-field confidence scores.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install structx-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from structx_sdk import StructX
|
|
19
|
+
|
|
20
|
+
client = StructX(api_key="sx_...")
|
|
21
|
+
|
|
22
|
+
result = client.extract(
|
|
23
|
+
content="<div><h1>Aeron Chair</h1><span>$1,795.00</span></div>",
|
|
24
|
+
schema={
|
|
25
|
+
"type": "object",
|
|
26
|
+
"required": ["title", "price_cents"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"title": {"type": "string"},
|
|
29
|
+
"price_cents": {"type": "integer"},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
print(result.data)
|
|
35
|
+
# {'title': 'Aeron Chair', 'price_cents': 179500}
|
|
36
|
+
|
|
37
|
+
print(result.field_confidences[0])
|
|
38
|
+
# FieldConfidence(field='title', confidence=0.96, source_snippet=None)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use a catalog template instead of an inline schema
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
result = client.extract(
|
|
45
|
+
content=stripe_webhook_payload,
|
|
46
|
+
template_slug="logs.stripe.event", # latest published version
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Pin to a specific template version with `family_slug@version`:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
template_slug="logs.stripe.event@1.0.0"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Don't have a schema yet? Let the API infer one
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
inference = client.infer_schema(
|
|
60
|
+
content="<html>… some product page …</html>",
|
|
61
|
+
content_type="html",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
print(inference.inferred.json_schema) # ready to pass back to extract()
|
|
65
|
+
for f in inference.inferred.fields:
|
|
66
|
+
print(f"{f.name} ({f.type}) — {f.rationale}")
|
|
67
|
+
|
|
68
|
+
# Plus template recommendations, if any matched:
|
|
69
|
+
for r in inference.recommendations:
|
|
70
|
+
print(f"{r.slug} (score={r.score:.2f})")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Async
|
|
74
|
+
|
|
75
|
+
Same surface, `await`-flavored:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import asyncio
|
|
79
|
+
from structx_sdk import AsyncStructX
|
|
80
|
+
|
|
81
|
+
async def main():
|
|
82
|
+
async with AsyncStructX(api_key="sx_...") as client:
|
|
83
|
+
result = await client.extract(content="…", schema={…})
|
|
84
|
+
print(result.data)
|
|
85
|
+
|
|
86
|
+
asyncio.run(main())
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Configuration
|
|
90
|
+
|
|
91
|
+
| Param | Default | Notes |
|
|
92
|
+
|----------------|-------------------------------|--------------------------------------------------------|
|
|
93
|
+
| `api_key` | `STRUCTX_API_KEY` env var | Required. |
|
|
94
|
+
| `base_url` | `STRUCTX_BASE_URL` env var, else `https://api.structx.ai` | Override for staging / self-hosted. |
|
|
95
|
+
| `timeout` | `30.0` seconds | Applied per request. |
|
|
96
|
+
| `retry` | `RetryPolicy(max_attempts=3, …)` | Tune via `RetryPolicy(...)`. |
|
|
97
|
+
| `default_headers` | `{}` | Merged into every request — e.g., for tracing IDs. |
|
|
98
|
+
|
|
99
|
+
Pick up credentials from the environment with `StructX.from_env()`:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
import os
|
|
103
|
+
os.environ["STRUCTX_API_KEY"] = "sx_..."
|
|
104
|
+
|
|
105
|
+
from structx_sdk import StructX
|
|
106
|
+
client = StructX.from_env()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Errors
|
|
110
|
+
|
|
111
|
+
All exceptions inherit from `StructXError`. Catch the specific class you care about:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from structx_sdk import RateLimitError, ValidationError, ServerError
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
result = client.extract(content=…, schema=…)
|
|
118
|
+
except RateLimitError as e:
|
|
119
|
+
# 429 — back off; e.retry_after, e.credits_used, e.credits_remaining are populated
|
|
120
|
+
print(f"Sleep {e.retry_after}s. Used {e.credits_used}/{e.credits_used + e.credits_remaining}.")
|
|
121
|
+
except ValidationError as e:
|
|
122
|
+
# 400/422 — fix your input. e.code carries the machine-readable reason.
|
|
123
|
+
print(f"Bad input: {e.code} — {e.message}")
|
|
124
|
+
except ServerError as e:
|
|
125
|
+
# 5xx — retry or contact support; e.request_id is your handle.
|
|
126
|
+
print(f"Server error (request_id={e.request_id})")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Full hierarchy:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
StructXError
|
|
133
|
+
├── TransportError # network failure — request never reached the server
|
|
134
|
+
└── ApiError # server responded with an error status
|
|
135
|
+
├── AuthenticationError # 401
|
|
136
|
+
├── PermissionDeniedError # 403
|
|
137
|
+
├── NotFoundError # 404
|
|
138
|
+
├── ValidationError # 400, 422
|
|
139
|
+
├── RateLimitError # 429 (carries retry_after, credits info)
|
|
140
|
+
└── ServerError # 5xx
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Retries
|
|
144
|
+
|
|
145
|
+
By default, **read** calls (`list_templates`, `list_models`, `usage`) auto-retry on transient 5xx and connection errors with exponential backoff.
|
|
146
|
+
|
|
147
|
+
**Write** calls (`extract`, `infer_schema`) retry ONLY on transport errors, never on 5xx — because a 5xx after a partial backend run may have already billed the call.
|
|
148
|
+
|
|
149
|
+
Customize via `RetryPolicy`:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from structx_sdk import StructX, RetryPolicy
|
|
153
|
+
|
|
154
|
+
client = StructX(
|
|
155
|
+
api_key="sx_...",
|
|
156
|
+
retry=RetryPolicy(
|
|
157
|
+
max_attempts=5,
|
|
158
|
+
initial_backoff=0.5,
|
|
159
|
+
max_backoff=60.0,
|
|
160
|
+
retry_on_5xx=True,
|
|
161
|
+
respect_retry_after=True,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Forward compatibility
|
|
167
|
+
|
|
168
|
+
Response models accept extra fields silently. When the API adds a new field, old SDK versions don't break — they just don't surface it as a typed attribute. Reach it via `result.model_dump()` or `result.__pydantic_extra__`.
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
git clone https://github.com/struct-x-ai/struct-x
|
|
174
|
+
cd struct-x/sdk/python
|
|
175
|
+
pip install -e ".[dev]"
|
|
176
|
+
pytest -q
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.18"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "structx-sdk"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Official Python SDK (structx-sdk) for struct-x — agent-native structured extraction."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "struct-x", email = "support@structx.ai" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"structured-extraction",
|
|
17
|
+
"llm",
|
|
18
|
+
"json-schema",
|
|
19
|
+
"extraction",
|
|
20
|
+
"ai",
|
|
21
|
+
"agent",
|
|
22
|
+
"mcp",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 4 - Beta",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.10",
|
|
31
|
+
"Programming Language :: Python :: 3.11",
|
|
32
|
+
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
34
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
36
|
+
"Typing :: Typed",
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"httpx>=0.27,<0.29",
|
|
40
|
+
"pydantic>=2.5,<3.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=8.0",
|
|
46
|
+
"pytest-asyncio>=0.23",
|
|
47
|
+
"respx>=0.21",
|
|
48
|
+
"mypy>=1.8",
|
|
49
|
+
"ruff>=0.5",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://structx.ai"
|
|
54
|
+
Documentation = "https://docs.structx.ai"
|
|
55
|
+
Repository = "https://github.com/struct-x-ai/struct-x"
|
|
56
|
+
Issues = "https://github.com/struct-x-ai/struct-x/issues"
|
|
57
|
+
Changelog = "https://github.com/struct-x-ai/struct-x/blob/main/sdk/python/CHANGELOG.md"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.version]
|
|
60
|
+
path = "src/structx_sdk/_version.py"
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.wheel]
|
|
63
|
+
packages = ["src/structx_sdk"]
|
|
64
|
+
|
|
65
|
+
[tool.hatch.build.targets.sdist]
|
|
66
|
+
include = [
|
|
67
|
+
"src/structx_sdk",
|
|
68
|
+
"README.md",
|
|
69
|
+
"CHANGELOG.md",
|
|
70
|
+
"LICENSE",
|
|
71
|
+
"pyproject.toml",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
asyncio_mode = "auto"
|
|
77
|
+
filterwarnings = ["error"]
|
|
78
|
+
|
|
79
|
+
[tool.ruff]
|
|
80
|
+
line-length = 100
|
|
81
|
+
target-version = "py310"
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint]
|
|
84
|
+
select = ["E", "F", "I", "B", "UP", "N"]
|
|
85
|
+
ignore = ["E501"]
|
|
86
|
+
|
|
87
|
+
[tool.mypy]
|
|
88
|
+
strict = true
|
|
89
|
+
python_version = "3.10"
|
|
90
|
+
files = ["src/structx_sdk"]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""struct-x Python SDK — official client for the structured-extraction API.
|
|
2
|
+
|
|
3
|
+
Quickstart:
|
|
4
|
+
|
|
5
|
+
from structx_sdk import StructX
|
|
6
|
+
|
|
7
|
+
client = StructX(api_key="sx_...")
|
|
8
|
+
result = client.extract(
|
|
9
|
+
content="<div>$99 Widget</div>",
|
|
10
|
+
schema={
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"price_cents": {"type": "integer"},
|
|
14
|
+
"title": {"type": "string"},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
print(result.data) # {'price_cents': 9900, 'title': 'Widget'}
|
|
19
|
+
print(result.field_confidences) # [FieldConfidence(field='price_cents', ...)]
|
|
20
|
+
|
|
21
|
+
Async variant — same surface:
|
|
22
|
+
|
|
23
|
+
from structx_sdk import AsyncStructX
|
|
24
|
+
|
|
25
|
+
async with AsyncStructX(api_key="sx_...") as client:
|
|
26
|
+
result = await client.extract(content="...", schema={...})
|
|
27
|
+
"""
|
|
28
|
+
from ._client import AsyncStructX, RetryPolicy, StructX
|
|
29
|
+
from ._exceptions import (
|
|
30
|
+
ApiError,
|
|
31
|
+
AuthenticationError,
|
|
32
|
+
NotFoundError,
|
|
33
|
+
PermissionDeniedError,
|
|
34
|
+
RateLimitError,
|
|
35
|
+
ServerError,
|
|
36
|
+
StructXError,
|
|
37
|
+
TransportError,
|
|
38
|
+
ValidationError,
|
|
39
|
+
)
|
|
40
|
+
from ._models import (
|
|
41
|
+
Extraction,
|
|
42
|
+
FieldConfidence,
|
|
43
|
+
InferenceResult,
|
|
44
|
+
InferredField,
|
|
45
|
+
InferredSchema,
|
|
46
|
+
Model,
|
|
47
|
+
Recommendation,
|
|
48
|
+
Template,
|
|
49
|
+
TokenCounts,
|
|
50
|
+
Usage,
|
|
51
|
+
)
|
|
52
|
+
from ._version import __version__
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# Clients
|
|
56
|
+
"StructX",
|
|
57
|
+
"AsyncStructX",
|
|
58
|
+
"RetryPolicy",
|
|
59
|
+
# Exceptions
|
|
60
|
+
"StructXError",
|
|
61
|
+
"TransportError",
|
|
62
|
+
"ApiError",
|
|
63
|
+
"AuthenticationError",
|
|
64
|
+
"PermissionDeniedError",
|
|
65
|
+
"NotFoundError",
|
|
66
|
+
"ValidationError",
|
|
67
|
+
"RateLimitError",
|
|
68
|
+
"ServerError",
|
|
69
|
+
# Models
|
|
70
|
+
"Extraction",
|
|
71
|
+
"FieldConfidence",
|
|
72
|
+
"TokenCounts",
|
|
73
|
+
"InferenceResult",
|
|
74
|
+
"InferredSchema",
|
|
75
|
+
"InferredField",
|
|
76
|
+
"Recommendation",
|
|
77
|
+
"Template",
|
|
78
|
+
"Model",
|
|
79
|
+
"Usage",
|
|
80
|
+
# Meta
|
|
81
|
+
"__version__",
|
|
82
|
+
]
|