solvapay-python 0.9.0__py3-none-any.whl → 0.9.2__py3-none-any.whl
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.
- solvapay/__init__.py +29 -2
- {solvapay_python-0.9.0.dist-info → solvapay_python-0.9.2.dist-info}/METADATA +73 -11
- {solvapay_python-0.9.0.dist-info → solvapay_python-0.9.2.dist-info}/RECORD +5 -5
- {solvapay_python-0.9.0.dist-info → solvapay_python-0.9.2.dist-info}/WHEEL +1 -1
- {solvapay_python-0.9.0.dist-info → solvapay_python-0.9.2.dist-info}/licenses/LICENSE +0 -0
solvapay/__init__.py
CHANGED
|
@@ -37,7 +37,7 @@ from solvapay.exceptions import (
|
|
|
37
37
|
)
|
|
38
38
|
from solvapay.models import BalanceResponse, Merchant, Plan, PlatformConfig, Product
|
|
39
39
|
from solvapay.paywall import PaywallRequired
|
|
40
|
-
from solvapay.webhooks import verify_webhook
|
|
40
|
+
from solvapay.webhooks import sign_webhook, verify_webhook
|
|
41
41
|
|
|
42
42
|
# Register stable exports in MANIFEST (HLD V1.2).
|
|
43
43
|
# stable(X) returns X unchanged — isinstance() continues to work (HLD SM1).
|
|
@@ -54,6 +54,7 @@ stable(APIServerError)
|
|
|
54
54
|
stable(APIConnectionError)
|
|
55
55
|
stable(APITimeoutError)
|
|
56
56
|
stable(PaywallRequired)
|
|
57
|
+
stable(sign_webhook)
|
|
57
58
|
stable(verify_webhook)
|
|
58
59
|
stable(BalanceResponse)
|
|
59
60
|
stable(Product)
|
|
@@ -99,10 +100,36 @@ __all__ = [
|
|
|
99
100
|
"SolvaPayAPIError",
|
|
100
101
|
"SolvaPayError",
|
|
101
102
|
"WebhookEvent",
|
|
103
|
+
# adapters exposed lazily via __getattr__ below (PEP 562)
|
|
104
|
+
"adapters",
|
|
102
105
|
"deprecated",
|
|
103
106
|
"experimental",
|
|
104
107
|
"paywall",
|
|
108
|
+
"sign_webhook",
|
|
105
109
|
"stable",
|
|
106
110
|
"verify_webhook",
|
|
107
111
|
]
|
|
108
|
-
__version__ = "0.9.
|
|
112
|
+
__version__ = "0.9.2"
|
|
113
|
+
|
|
114
|
+
# PEP 562 — lazy adapter submodule access.
|
|
115
|
+
# Adapters drag in optional heavy deps (fastmcp, langchain-core, fastapi).
|
|
116
|
+
# __getattr__ defers their load until first access; they never load on bare
|
|
117
|
+
# `import solvapay` for users who don't use framework adapters.
|
|
118
|
+
_LAZY_MODULES = {
|
|
119
|
+
"adapters": "solvapay.adapters",
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def __getattr__(name: str) -> object:
|
|
124
|
+
if name in _LAZY_MODULES:
|
|
125
|
+
import importlib
|
|
126
|
+
|
|
127
|
+
mod = importlib.import_module(_LAZY_MODULES[name])
|
|
128
|
+
globals()[name] = mod
|
|
129
|
+
return mod
|
|
130
|
+
raise AttributeError(f"module 'solvapay' has no attribute {name!r}")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def __dir__() -> list[str]:
|
|
134
|
+
# PEP 562: include lazy names alongside normal module attrs.
|
|
135
|
+
return sorted(set(globals()) | set(_LAZY_MODULES))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solvapay-python
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Community Python SDK for SolvaPay (agent-native payment rails)
|
|
5
5
|
Project-URL: Homepage, https://github.com/dhruv-sanan/solvapay-python
|
|
6
6
|
Project-URL: Issues, https://github.com/dhruv-sanan/solvapay-python/issues
|
|
@@ -21,15 +21,17 @@ Classifier: Topic :: Office/Business :: Financial
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
|
-
Requires-Dist: httpx
|
|
25
|
-
Requires-Dist: pydantic<2.
|
|
24
|
+
Requires-Dist: httpx<0.29,>=0.27
|
|
25
|
+
Requires-Dist: pydantic<2.14,>=2.6
|
|
26
26
|
Provides-Extra: asgi
|
|
27
|
+
Provides-Extra: bench
|
|
28
|
+
Requires-Dist: pytest-benchmark<6,>=4.0; extra == 'bench'
|
|
27
29
|
Provides-Extra: fastapi
|
|
28
30
|
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
29
31
|
Provides-Extra: langchain
|
|
30
|
-
Requires-Dist: langchain-core<
|
|
32
|
+
Requires-Dist: langchain-core<1.5,>=0.3; extra == 'langchain'
|
|
31
33
|
Provides-Extra: mcp
|
|
32
|
-
Requires-Dist: fastmcp<
|
|
34
|
+
Requires-Dist: fastmcp<4,>=3.2.0; extra == 'mcp'
|
|
33
35
|
Provides-Extra: retry
|
|
34
36
|
Requires-Dist: tenacity<10,>=8.2; extra == 'retry'
|
|
35
37
|
Description-Content-Type: text/markdown
|
|
@@ -138,16 +140,23 @@ if limits.within_limits:
|
|
|
138
140
|
|
|
139
141
|
## Idempotency
|
|
140
142
|
|
|
141
|
-
All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys
|
|
143
|
+
All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys:
|
|
142
144
|
|
|
143
145
|
```python
|
|
144
146
|
from solvapay.idempotency import from_payload
|
|
145
147
|
|
|
148
|
+
# Default: key includes UTC date — rolls at midnight, bounds replay past server TTL
|
|
146
149
|
key = from_payload("track_usage", customer_ref, product_ref, "requests", units)
|
|
147
150
|
sv.usage.track(..., idempotency_key=key) # retry-safe
|
|
151
|
+
|
|
152
|
+
# Hourly bucket (high-frequency ops)
|
|
153
|
+
key = from_payload("charge", customer_ref, time_bucket="hour")
|
|
154
|
+
|
|
155
|
+
# Pure payload hash — caller manages TTL externally
|
|
156
|
+
key = from_payload("idempotent_op", ref, time_bucket=None)
|
|
148
157
|
```
|
|
149
158
|
|
|
150
|
-
Retried POSTs **must reuse the same key**.
|
|
159
|
+
Retried POSTs **must reuse the same key**. A bucket roll (midnight / hour boundary) produces a new key — the server treats it as a new request.
|
|
151
160
|
|
|
152
161
|
---
|
|
153
162
|
|
|
@@ -174,7 +183,7 @@ except SolvaPayError as e:
|
|
|
174
183
|
... # catch-all
|
|
175
184
|
```
|
|
176
185
|
|
|
177
|
-
No built-in retries by
|
|
186
|
+
No built-in retries by default. `solvapay[retry]` ships `RetryTransport` — exponential backoff with jitter, 3 attempts, respects `OpSpec.retry_safety` (won't retry non-idempotent ops without an idempotency key). Or layer `tenacity` manually.
|
|
178
187
|
|
|
179
188
|
---
|
|
180
189
|
|
|
@@ -193,6 +202,30 @@ pipeline = WebhookPipeline(
|
|
|
193
202
|
envelope = pipeline.process(body=request.body, signature=request.headers["sv-signature"])
|
|
194
203
|
```
|
|
195
204
|
|
|
205
|
+
**Secret rotation** — pass multiple secrets; primary tried first, secondary on mismatch:
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
from solvapay import sign_webhook # top-level (v0.9.2+)
|
|
215
|
+
# from solvapay.webhooks import sign_webhook # subpackage path also works
|
|
216
|
+
|
|
217
|
+
header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
|
|
218
|
+
# → "t=1716470000,v1=abc123..."
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**ASGI adapter** — mount directly in FastAPI / Starlette / Litestar:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from solvapay.adapters.asgi import webhook_app
|
|
225
|
+
|
|
226
|
+
app.mount("/webhook", webhook_app(pipeline, on_event=handle))
|
|
227
|
+
```
|
|
228
|
+
|
|
196
229
|
**Typed events** — discriminated union over 13 event types:
|
|
197
230
|
|
|
198
231
|
```python
|
|
@@ -279,6 +312,9 @@ pip install solvapay-python # core
|
|
|
279
312
|
pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
|
|
280
313
|
pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
|
|
281
314
|
pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
|
|
315
|
+
pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
|
|
316
|
+
pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
|
|
317
|
+
pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
|
|
282
318
|
```
|
|
283
319
|
|
|
284
320
|
## Environment variables
|
|
@@ -302,17 +338,43 @@ pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
|
|
|
302
338
|
|
|
303
339
|
---
|
|
304
340
|
|
|
341
|
+
## API version pinning
|
|
342
|
+
|
|
343
|
+
Pin the API version your code was written against — prevents silent breakage when the server evolves:
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
sv = SolvaPay(api_version="2026-05-22") # sends Solvapay-Version header
|
|
347
|
+
sv = SolvaPay(api_version=None) # omit header (use server default)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
305
354
|
## Roadmap
|
|
306
355
|
|
|
307
356
|
| Version | Theme |
|
|
308
357
|
|---------|-------|
|
|
309
358
|
| v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
|
|
310
|
-
| v0.9 | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, contract tests, lint automation,
|
|
311
|
-
|
|
|
359
|
+
| v0.9 ✅ | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, ASGI adapter, secret rotation, `sign_webhook`, contract tests, lint automation, MkDocs site, supply-chain hygiene |
|
|
360
|
+
| v0.9.1 ✅ | Security & supply-chain quality — PyPI attestations (PEP 740 / Sigstore), CycloneDX SBOM on releases, `bandit` + `osv-scanner` CI, Hypothesis-driven secret-redaction property tests, constant-time `verify_webhook` smoke test, explicit PCI-scope statement |
|
|
361
|
+
| v0.9.2 ✅ | Performance & stability quality — cold-import baseline harness, PEP 562 lazy adapter imports, `solvapay.sign_webhook` top-level re-export, microbenchmark harness (`solvapay[bench]`), troubleshooting docs |
|
|
362
|
+
| v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
|
|
312
363
|
|
|
313
364
|
---
|
|
314
365
|
|
|
315
366
|
## Status
|
|
316
367
|
|
|
317
|
-
**v0.
|
|
368
|
+
**v0.9.2** — Performance & stability quality patch. Cold-import baseline harness (`tests/test_cold_import.py`, 1.5x regression gate). PEP 562 lazy adapter imports — bare `import solvapay` no longer loads framework adapter modules. `solvapay.sign_webhook` promoted to top-level (alongside `verify_webhook`). Microbenchmark harness under `tests/benchmarks/` (opt-in `solvapay[bench]`). New docs: `docs/troubleshooting.md` (top-10 errors) and `docs/architecture/cold-start.md`. 305 tests. 87% line coverage. `mypy --strict` clean (45 files).
|
|
369
|
+
|
|
370
|
+
**v0.9.1** — Security & supply-chain quality patch. PyPI uploads now carry PEP 740 attestations (Sigstore-backed via OIDC trusted publishing); each GitHub release ships a CycloneDX SBOM (`sbom.cdx.json`). `mypy --strict` clean (45 files). 302 tests. 87% line / 85% branch coverage.
|
|
371
|
+
|
|
372
|
+
**Supply chain & security posture:**
|
|
373
|
+
- PyPI trusted publishing with PEP 740 attestations (Sigstore)
|
|
374
|
+
- CycloneDX 1.6 SBOM attached to every release
|
|
375
|
+
- CI runs `bandit -r src/ -lll`, `osv-scanner --lockfile=uv.lock`, and `pip-audit` (cross-DB vuln check) on PR and nightly cron
|
|
376
|
+
- Constant-time HMAC comparison (`hmac.compare_digest`) regression-smoked
|
|
377
|
+
- Hypothesis-driven property tests assert no API key, hex token, or webhook secret leaks into log output at any level
|
|
378
|
+
- Tightened upper bounds on volatile deps (`httpx<0.29`, `pydantic<2.14`, `langchain-core<1.5`)
|
|
379
|
+
|
|
318
380
|
Community SDK, not official. Proposal: [solvapay/solvapay-sdk#187](https://github.com/solvapay/solvapay-sdk/issues/187).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
solvapay/__init__.py,sha256=
|
|
1
|
+
solvapay/__init__.py,sha256=SCzaOP-4tqWo90KW1d_L6YpDACNQqahD85iHeRa3X9w,3488
|
|
2
2
|
solvapay/_async_client.py,sha256=TdeCyvnfZQCJzkLnYC73WNEG_fYjKWb226Gct1wt7pI,10759
|
|
3
3
|
solvapay/_config.py,sha256=yPU_GM77pXwPfIzuncuLRNv4322q5bB9qaawN3izEB8,606
|
|
4
4
|
solvapay/_http.py,sha256=rtjU-MOmxsh3xO-HT7KtjYaTUagPlPqV5eBv-o51Y1Q,408
|
|
@@ -44,7 +44,7 @@ solvapay/webhooks/replay.py,sha256=DIv52mCwi1GRtAl7q_O_JWuFSJQMwcQ24JkOuD1uLmU,2
|
|
|
44
44
|
solvapay/webhooks/rotation.py,sha256=qYNMBBSp6Zvg59gPc2SUC1cqNIAizrBJ50MRf5B_ztg,1248
|
|
45
45
|
solvapay/webhooks/sign.py,sha256=X7rA0iZkDBsXo3nvxK8YnQ-zpLfMhCMY-r6ts_MUM90,1150
|
|
46
46
|
solvapay/webhooks/verify.py,sha256=TIkho3IG-S9IpXWvBLmDHdGfG9_XxUK6w87kNwkLQYo,1954
|
|
47
|
-
solvapay_python-0.9.
|
|
48
|
-
solvapay_python-0.9.
|
|
49
|
-
solvapay_python-0.9.
|
|
50
|
-
solvapay_python-0.9.
|
|
47
|
+
solvapay_python-0.9.2.dist-info/METADATA,sha256=pJb_W5Y_kURJU9HPlqoFUwlVX_Lt0lGd5JyVGTcDafk,13711
|
|
48
|
+
solvapay_python-0.9.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
49
|
+
solvapay_python-0.9.2.dist-info/licenses/LICENSE,sha256=wJURmEXLdSdApQdHG-RCwBoZVka1Oux8zNrLxGC30S8,1068
|
|
50
|
+
solvapay_python-0.9.2.dist-info/RECORD,,
|
|
File without changes
|