spreadspace 0.1.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.
- spreadspace-0.1.0/.gitignore +13 -0
- spreadspace-0.1.0/PKG-INFO +171 -0
- spreadspace-0.1.0/README.md +151 -0
- spreadspace-0.1.0/pyproject.toml +42 -0
- spreadspace-0.1.0/scripts/generate.sh +73 -0
- spreadspace-0.1.0/src/spreadspace/__init__.py +75 -0
- spreadspace-0.1.0/src/spreadspace/_transport.py +272 -0
- spreadspace-0.1.0/src/spreadspace/_version.py +12 -0
- spreadspace-0.1.0/src/spreadspace/client.py +308 -0
- spreadspace-0.1.0/src/spreadspace/errors.py +117 -0
- spreadspace-0.1.0/src/spreadspace/helpers/__init__.py +5 -0
- spreadspace-0.1.0/src/spreadspace/helpers/operations.py +196 -0
- spreadspace-0.1.0/src/spreadspace/helpers/pagination.py +89 -0
- spreadspace-0.1.0/src/spreadspace/helpers/upload.py +283 -0
- spreadspace-0.1.0/src/spreadspace/webhooks.py +330 -0
- spreadspace-0.1.0/tests/test_client.py +112 -0
- spreadspace-0.1.0/tests/test_embed_resource.py +151 -0
- spreadspace-0.1.0/tests/test_operations.py +194 -0
- spreadspace-0.1.0/tests/test_pagination.py +128 -0
- spreadspace-0.1.0/tests/test_transport.py +268 -0
- spreadspace-0.1.0/tests/test_upload.py +240 -0
- spreadspace-0.1.0/tests/test_webhooks.py +320 -0
- spreadspace-0.1.0/tests/test_webhooks_resource.py +163 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Generated OpenAPI core — produced by scripts/generate.sh in CI. DO NOT EDIT,
|
|
2
|
+
# never commit (regenerated every run from the spec).
|
|
3
|
+
src/spreadspace/_generated/
|
|
4
|
+
|
|
5
|
+
# Python build / test artifacts
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
.venv/
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spreadspace
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the SpreadSpace API.
|
|
5
|
+
Project-URL: Homepage, https://spreadspace.ai
|
|
6
|
+
Project-URL: Documentation, https://docs.spreadspace.ai
|
|
7
|
+
Project-URL: Source, https://github.com/spreadspace
|
|
8
|
+
Author: SpreadSpace
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: api,document,extraction,lending,sdk,spreadspace
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Requires-Dist: httpx<1,>=0.27
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# SpreadSpace Python SDK
|
|
22
|
+
|
|
23
|
+
Official Python client for the [SpreadSpace API](https://docs.spreadspace.ai) —
|
|
24
|
+
document extraction and financial spreading for lending.
|
|
25
|
+
|
|
26
|
+
> Some examples below require the generated core (built in CI) or a live sandbox
|
|
27
|
+
> key. They are marked **[needs sandbox key]** / **[needs generated core]**.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install spreadspace
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.9+.
|
|
36
|
+
|
|
37
|
+
## Authentication
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from spreadspace import SpreadSpace
|
|
41
|
+
|
|
42
|
+
client = SpreadSpace(api_key="ss_test_...") # or omit and set SPREADSPACE_API_KEY
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The key prefix selects the environment:
|
|
46
|
+
|
|
47
|
+
- `ss_test_...` — routes to your **sandbox tenant** (seed data, safe to experiment).
|
|
48
|
+
- `ss_live_...` — routes to your live tenant (real data).
|
|
49
|
+
|
|
50
|
+
If `api_key` is omitted, the client reads `SPREADSPACE_API_KEY` from the
|
|
51
|
+
environment. Never hard-code a live key; never commit any key.
|
|
52
|
+
|
|
53
|
+
## Client options
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
client = SpreadSpace(
|
|
57
|
+
api_key="ss_test_...",
|
|
58
|
+
base_url="https://api.spreadspace.ai", # override for a private deployment
|
|
59
|
+
api_version="2026-05-03", # pins the SpreadSpace-Version header
|
|
60
|
+
timeout=60.0, # seconds, per request
|
|
61
|
+
max_retries=2, # 429 + 5xx + transport errors
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### API version pinning
|
|
66
|
+
|
|
67
|
+
Every request sends a dated `SpreadSpace-Version` header. The SDK pins a default
|
|
68
|
+
version per release (decoupled from the SDK's own semver). Pin it explicitly to
|
|
69
|
+
insulate your integration from server-side changes, and override per call when
|
|
70
|
+
you need a newer surface:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
client.borrowers.list(api_version="2026-06-01") # one call on a newer version
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Pagination (lazy, auto cursor)
|
|
77
|
+
|
|
78
|
+
List endpoints return a lazy iterator that walks cursors for you — it fetches the
|
|
79
|
+
next page only as you consume it.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
for borrower in client.borrowers.list():
|
|
83
|
+
print(borrower["id"])
|
|
84
|
+
|
|
85
|
+
# Filters pass straight through and persist across pages:
|
|
86
|
+
for job in client.jobs.list(status="completed", limit=50):
|
|
87
|
+
print(job["id"])
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Async export + wait
|
|
91
|
+
|
|
92
|
+
Exports are asynchronous operations. `create` returns a handle; `wait` polls to
|
|
93
|
+
completion (or raises on timeout).
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
op = client.exports.create(borrower_id="...", format="xlsx")
|
|
97
|
+
result = op.wait(timeout=300) # seconds
|
|
98
|
+
print(result["download_url"])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Upload a document + wait for processing
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
job = client.documents.upload("statement.pdf", borrower_id="...")
|
|
105
|
+
final = job.wait(timeout=600) # seconds
|
|
106
|
+
print(final["status"])
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The upload helper requests a presigned URL, PUTs the file bytes directly to
|
|
110
|
+
storage with the matching `Content-Type` (part of the V4 signature), then returns
|
|
111
|
+
a job handle you can `wait` on.
|
|
112
|
+
|
|
113
|
+
## Error handling
|
|
114
|
+
|
|
115
|
+
All errors derive from `SpreadSpaceError`. Match on the typed subclass, never on
|
|
116
|
+
the message string:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from spreadspace import (
|
|
120
|
+
SpreadSpaceError, # base
|
|
121
|
+
NetworkError, # transport failure, no HTTP response
|
|
122
|
+
BadRequestError, # 400
|
|
123
|
+
AuthenticationError, # 401
|
|
124
|
+
PermissionDeniedError, # 403
|
|
125
|
+
NotFoundError, # 404
|
|
126
|
+
ConflictError, # 409
|
|
127
|
+
RateLimitError, # 429
|
|
128
|
+
InternalServerError, # 5xx
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
client.borrowers.get("missing-id")
|
|
133
|
+
except RateLimitError as e:
|
|
134
|
+
print("retry after", e.retry_after, "seconds")
|
|
135
|
+
except NotFoundError as e:
|
|
136
|
+
print("not found")
|
|
137
|
+
except SpreadSpaceError as e:
|
|
138
|
+
# Every error carries request_id — quote it in support tickets.
|
|
139
|
+
print(e.message, e.status_code, e.request_id)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`request_id` comes from the `X-Request-ID` response header (falling back to the
|
|
143
|
+
error body). Transient failures (429, 5xx, transport errors) are retried
|
|
144
|
+
automatically up to `max_retries` with exponential backoff + full jitter,
|
|
145
|
+
honoring `Retry-After`.
|
|
146
|
+
|
|
147
|
+
## Money is exact
|
|
148
|
+
|
|
149
|
+
Monetary values decode as `decimal.Decimal`, not `float` — the SDK reads the
|
|
150
|
+
literal digits off the wire, so amounts are exact with no float rounding:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
b = client.borrowers.get("...") # [needs sandbox key]
|
|
154
|
+
assert b["total_revenue"] == Decimal("1234.56") # never 1234.5600000000001
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
The generated OpenAPI core lives in `src/spreadspace/_generated/` and is built in
|
|
160
|
+
CI (`scripts/generate.sh`, Java-based — not run locally). It is **gitignored and
|
|
161
|
+
must never be hand-edited**. The hand-written ergonomic layer (transport,
|
|
162
|
+
helpers, typed errors) is the only code committed by hand.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pip install -e '.[dev]'
|
|
166
|
+
pytest
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# SpreadSpace Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python client for the [SpreadSpace API](https://docs.spreadspace.ai) —
|
|
4
|
+
document extraction and financial spreading for lending.
|
|
5
|
+
|
|
6
|
+
> Some examples below require the generated core (built in CI) or a live sandbox
|
|
7
|
+
> key. They are marked **[needs sandbox key]** / **[needs generated core]**.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install spreadspace
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Python 3.9+.
|
|
16
|
+
|
|
17
|
+
## Authentication
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from spreadspace import SpreadSpace
|
|
21
|
+
|
|
22
|
+
client = SpreadSpace(api_key="ss_test_...") # or omit and set SPREADSPACE_API_KEY
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The key prefix selects the environment:
|
|
26
|
+
|
|
27
|
+
- `ss_test_...` — routes to your **sandbox tenant** (seed data, safe to experiment).
|
|
28
|
+
- `ss_live_...` — routes to your live tenant (real data).
|
|
29
|
+
|
|
30
|
+
If `api_key` is omitted, the client reads `SPREADSPACE_API_KEY` from the
|
|
31
|
+
environment. Never hard-code a live key; never commit any key.
|
|
32
|
+
|
|
33
|
+
## Client options
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
client = SpreadSpace(
|
|
37
|
+
api_key="ss_test_...",
|
|
38
|
+
base_url="https://api.spreadspace.ai", # override for a private deployment
|
|
39
|
+
api_version="2026-05-03", # pins the SpreadSpace-Version header
|
|
40
|
+
timeout=60.0, # seconds, per request
|
|
41
|
+
max_retries=2, # 429 + 5xx + transport errors
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### API version pinning
|
|
46
|
+
|
|
47
|
+
Every request sends a dated `SpreadSpace-Version` header. The SDK pins a default
|
|
48
|
+
version per release (decoupled from the SDK's own semver). Pin it explicitly to
|
|
49
|
+
insulate your integration from server-side changes, and override per call when
|
|
50
|
+
you need a newer surface:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
client.borrowers.list(api_version="2026-06-01") # one call on a newer version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Pagination (lazy, auto cursor)
|
|
57
|
+
|
|
58
|
+
List endpoints return a lazy iterator that walks cursors for you — it fetches the
|
|
59
|
+
next page only as you consume it.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
for borrower in client.borrowers.list():
|
|
63
|
+
print(borrower["id"])
|
|
64
|
+
|
|
65
|
+
# Filters pass straight through and persist across pages:
|
|
66
|
+
for job in client.jobs.list(status="completed", limit=50):
|
|
67
|
+
print(job["id"])
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Async export + wait
|
|
71
|
+
|
|
72
|
+
Exports are asynchronous operations. `create` returns a handle; `wait` polls to
|
|
73
|
+
completion (or raises on timeout).
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
op = client.exports.create(borrower_id="...", format="xlsx")
|
|
77
|
+
result = op.wait(timeout=300) # seconds
|
|
78
|
+
print(result["download_url"])
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Upload a document + wait for processing
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
job = client.documents.upload("statement.pdf", borrower_id="...")
|
|
85
|
+
final = job.wait(timeout=600) # seconds
|
|
86
|
+
print(final["status"])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The upload helper requests a presigned URL, PUTs the file bytes directly to
|
|
90
|
+
storage with the matching `Content-Type` (part of the V4 signature), then returns
|
|
91
|
+
a job handle you can `wait` on.
|
|
92
|
+
|
|
93
|
+
## Error handling
|
|
94
|
+
|
|
95
|
+
All errors derive from `SpreadSpaceError`. Match on the typed subclass, never on
|
|
96
|
+
the message string:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from spreadspace import (
|
|
100
|
+
SpreadSpaceError, # base
|
|
101
|
+
NetworkError, # transport failure, no HTTP response
|
|
102
|
+
BadRequestError, # 400
|
|
103
|
+
AuthenticationError, # 401
|
|
104
|
+
PermissionDeniedError, # 403
|
|
105
|
+
NotFoundError, # 404
|
|
106
|
+
ConflictError, # 409
|
|
107
|
+
RateLimitError, # 429
|
|
108
|
+
InternalServerError, # 5xx
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
client.borrowers.get("missing-id")
|
|
113
|
+
except RateLimitError as e:
|
|
114
|
+
print("retry after", e.retry_after, "seconds")
|
|
115
|
+
except NotFoundError as e:
|
|
116
|
+
print("not found")
|
|
117
|
+
except SpreadSpaceError as e:
|
|
118
|
+
# Every error carries request_id — quote it in support tickets.
|
|
119
|
+
print(e.message, e.status_code, e.request_id)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`request_id` comes from the `X-Request-ID` response header (falling back to the
|
|
123
|
+
error body). Transient failures (429, 5xx, transport errors) are retried
|
|
124
|
+
automatically up to `max_retries` with exponential backoff + full jitter,
|
|
125
|
+
honoring `Retry-After`.
|
|
126
|
+
|
|
127
|
+
## Money is exact
|
|
128
|
+
|
|
129
|
+
Monetary values decode as `decimal.Decimal`, not `float` — the SDK reads the
|
|
130
|
+
literal digits off the wire, so amounts are exact with no float rounding:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
b = client.borrowers.get("...") # [needs sandbox key]
|
|
134
|
+
assert b["total_revenue"] == Decimal("1234.56") # never 1234.5600000000001
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
The generated OpenAPI core lives in `src/spreadspace/_generated/` and is built in
|
|
140
|
+
CI (`scripts/generate.sh`, Java-based — not run locally). It is **gitignored and
|
|
141
|
+
must never be hand-edited**. The hand-written ergonomic layer (transport,
|
|
142
|
+
helpers, typed errors) is the only code committed by hand.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pip install -e '.[dev]'
|
|
146
|
+
pytest
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "spreadspace"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Official Python SDK for the SpreadSpace API."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "SpreadSpace" }]
|
|
13
|
+
keywords = ["spreadspace", "api", "sdk", "document", "extraction", "lending"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = ["httpx>=0.27,<1"]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = ["pytest>=8", "mypy>=1.10"]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://spreadspace.ai"
|
|
26
|
+
Documentation = "https://docs.spreadspace.ai"
|
|
27
|
+
Source = "https://github.com/spreadspace"
|
|
28
|
+
|
|
29
|
+
# The generated OpenAPI core lands in src/spreadspace/_generated/ at build time
|
|
30
|
+
# (see scripts/generate.sh). The hand-written ergonomic layer lives alongside it
|
|
31
|
+
# and is the only code committed by hand.
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/spreadspace"]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
testpaths = ["tests"]
|
|
37
|
+
addopts = "-q"
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
python_version = "3.9"
|
|
41
|
+
strict = true
|
|
42
|
+
files = ["src/spreadspace"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regenerate the Python SDK's generated OpenAPI core.
|
|
3
|
+
#
|
|
4
|
+
# Self-hosted pipeline (no paid SDK vendor): we drive OpenAPI Generator directly.
|
|
5
|
+
# The hand-written ergonomic layer (transport, helpers, typed errors) lives in
|
|
6
|
+
# src/spreadspace/ and is committed; the machine-generated models + low-level
|
|
7
|
+
# api client land in src/spreadspace/_generated/ and are GITIGNORED — they are
|
|
8
|
+
# regenerated from the spec every run and must NEVER be hand-edited.
|
|
9
|
+
#
|
|
10
|
+
# Java is required (OpenAPI Generator is a JVM tool). Local dev has no JRE, so
|
|
11
|
+
# this is CI-only — see .github/workflows/sdk.yml. Author/verify here, run there.
|
|
12
|
+
#
|
|
13
|
+
# Idempotent + safe to re-run: it rebuilds the public spec, re-validates, and
|
|
14
|
+
# overwrites _generated/ from scratch each time.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# -- pinned versions --------------------------------------------------------
|
|
18
|
+
# CLI wrapper (npx) and the generator JAR are pinned to EXACT versions so the
|
|
19
|
+
# generated output is reproducible across CI runs and machines.
|
|
20
|
+
OPENAPI_GENERATOR_CLI_VERSION="2.20.0" # @openapitools/openapi-generator-cli (npm wrapper)
|
|
21
|
+
OPENAPI_GENERATOR_VERSION="${OPENAPI_GENERATOR_VERSION:-7.13.0}" # the generator JAR (recent 7.x)
|
|
22
|
+
export OPENAPI_GENERATOR_VERSION
|
|
23
|
+
|
|
24
|
+
# Generator target. NOTE: the exact `-g` name MUST be confirmed against the
|
|
25
|
+
# pinned JAR with `npx @openapitools/openapi-generator-cli@${OPENAPI_GENERATOR_CLI_VERSION} list`
|
|
26
|
+
# before trusting it — do NOT assume. In 7.x the maintained Python generator is
|
|
27
|
+
# `python` (the legacy `python-legacy` is separate). We pin `python`.
|
|
28
|
+
GENERATOR_NAME="python"
|
|
29
|
+
|
|
30
|
+
# -- paths ------------------------------------------------------------------
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
SDK_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" # sdk/python
|
|
33
|
+
REPO_ROOT="$(cd "${SDK_DIR}/../.." && pwd)" # repo root
|
|
34
|
+
PUBLIC_SPEC="${REPO_ROOT}/fern/api-public.json"
|
|
35
|
+
OUT_DIR="${SDK_DIR}/src/spreadspace/_generated"
|
|
36
|
+
|
|
37
|
+
OPENAPI_CLI="npx --yes @openapitools/openapi-generator-cli@${OPENAPI_GENERATOR_CLI_VERSION}"
|
|
38
|
+
|
|
39
|
+
echo "==> Refreshing public spec via fern/build-public-spec.sh"
|
|
40
|
+
# Reuse the ONE shared spec producer: strips /api/admin, /api/internal,
|
|
41
|
+
# /api/portfolios + localhost servers, writes fern/api-public.json. Needs jq + python3.
|
|
42
|
+
bash "${REPO_ROOT}/fern/build-public-spec.sh"
|
|
43
|
+
|
|
44
|
+
echo "==> Validating spec"
|
|
45
|
+
${OPENAPI_CLI} validate -i "${PUBLIC_SPEC}"
|
|
46
|
+
|
|
47
|
+
echo "==> Generating Python core into ${OUT_DIR}"
|
|
48
|
+
# --package-name spreadspace._generated keeps the generated package nested under
|
|
49
|
+
# the hand-written `spreadspace` package, so it can never collide with it. But
|
|
50
|
+
# the generator writes the package as a PATH under -o (i.e. <out>/spreadspace/
|
|
51
|
+
# _generated/), so we generate into a throwaway stage dir and move just the
|
|
52
|
+
# generated subtree into place — never touching the hand-written src/spreadspace.
|
|
53
|
+
# library=urllib3: the generated low-level client uses urllib3 (the default,
|
|
54
|
+
# zero extra deps in the generated tree). Our hand-written transport is httpx;
|
|
55
|
+
# the generated client is the typed-model + raw-call substrate the ergonomic
|
|
56
|
+
# layer sits on top of — we don't ship its HTTP path on the hot calls.
|
|
57
|
+
STAGE="$(mktemp -d)"
|
|
58
|
+
trap 'rm -rf "${STAGE}"' EXIT
|
|
59
|
+
${OPENAPI_CLI} generate \
|
|
60
|
+
-i "${PUBLIC_SPEC}" \
|
|
61
|
+
-g "${GENERATOR_NAME}" \
|
|
62
|
+
-o "${STAGE}" \
|
|
63
|
+
--package-name spreadspace._generated \
|
|
64
|
+
--additional-properties=library=urllib3,packageVersion=0.1.0,generateSourceCodeOnly=true
|
|
65
|
+
|
|
66
|
+
# Wipe first so removed models/operations don't linger (idempotent regen).
|
|
67
|
+
rm -rf "${OUT_DIR}"
|
|
68
|
+
mkdir -p "$(dirname "${OUT_DIR}")"
|
|
69
|
+
mv "${STAGE}/spreadspace/_generated" "${OUT_DIR}"
|
|
70
|
+
|
|
71
|
+
echo "==> Done."
|
|
72
|
+
echo "NOTE: ${OUT_DIR} is gitignored (see sdk/python/.gitignore) and is"
|
|
73
|
+
echo " regenerated from the spec on every run — NEVER hand-edit it."
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""SpreadSpace Python SDK.
|
|
2
|
+
|
|
3
|
+
from spreadspace import SpreadSpace
|
|
4
|
+
client = SpreadSpace(api_key="ss_test_...")
|
|
5
|
+
for borrower in client.borrowers.list():
|
|
6
|
+
...
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from ._version import DEFAULT_API_VERSION, SDK_VERSION
|
|
10
|
+
from .client import SpreadSpace
|
|
11
|
+
from .errors import (
|
|
12
|
+
APIStatusError,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
BadRequestError,
|
|
15
|
+
ConflictError,
|
|
16
|
+
InternalServerError,
|
|
17
|
+
NetworkError,
|
|
18
|
+
NotFoundError,
|
|
19
|
+
PermissionDeniedError,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
SpreadSpaceError,
|
|
22
|
+
)
|
|
23
|
+
from .helpers.operations import (
|
|
24
|
+
AsyncOperation,
|
|
25
|
+
AsyncOperationError,
|
|
26
|
+
AsyncOperationHandle,
|
|
27
|
+
AsyncOperationTimeout,
|
|
28
|
+
)
|
|
29
|
+
from .helpers.pagination import PageIterator
|
|
30
|
+
from .helpers.upload import JobHandle, PresignedUrl, UploadError, UploadTimeout
|
|
31
|
+
from .webhooks import (
|
|
32
|
+
DEFAULT_FRESHNESS_TOLERANCE_SECONDS,
|
|
33
|
+
SIGNATURE_HEADER_NAME,
|
|
34
|
+
WEBHOOK_EVENT_TYPES,
|
|
35
|
+
WebhookEvent,
|
|
36
|
+
WebhookSignatureError,
|
|
37
|
+
verify_and_parse_webhook,
|
|
38
|
+
verify_webhook_signature,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
# client + version
|
|
43
|
+
"SpreadSpace",
|
|
44
|
+
"SDK_VERSION",
|
|
45
|
+
"DEFAULT_API_VERSION",
|
|
46
|
+
# errors
|
|
47
|
+
"SpreadSpaceError",
|
|
48
|
+
"NetworkError",
|
|
49
|
+
"APIStatusError",
|
|
50
|
+
"BadRequestError",
|
|
51
|
+
"AuthenticationError",
|
|
52
|
+
"PermissionDeniedError",
|
|
53
|
+
"NotFoundError",
|
|
54
|
+
"ConflictError",
|
|
55
|
+
"RateLimitError",
|
|
56
|
+
"InternalServerError",
|
|
57
|
+
# helper types
|
|
58
|
+
"PageIterator",
|
|
59
|
+
"AsyncOperation",
|
|
60
|
+
"AsyncOperationHandle",
|
|
61
|
+
"AsyncOperationError",
|
|
62
|
+
"AsyncOperationTimeout",
|
|
63
|
+
"JobHandle",
|
|
64
|
+
"PresignedUrl",
|
|
65
|
+
"UploadError",
|
|
66
|
+
"UploadTimeout",
|
|
67
|
+
# webhooks
|
|
68
|
+
"verify_webhook_signature",
|
|
69
|
+
"verify_and_parse_webhook",
|
|
70
|
+
"WebhookSignatureError",
|
|
71
|
+
"WebhookEvent",
|
|
72
|
+
"WEBHOOK_EVENT_TYPES",
|
|
73
|
+
"SIGNATURE_HEADER_NAME",
|
|
74
|
+
"DEFAULT_FRESHNESS_TOLERANCE_SECONDS",
|
|
75
|
+
]
|