solvapay-python 0.7.1__tar.gz → 0.7.2__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.
Files changed (67) hide show
  1. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/CHANGELOG.md +15 -0
  2. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/PKG-INFO +82 -3
  3. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/README.md +81 -2
  4. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/pyproject.toml +1 -1
  5. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/langchain-paywall/pyproject.toml +1 -1
  6. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/pyproject.toml +1 -1
  7. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/__init__.py +1 -1
  8. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/paywall.py +23 -18
  9. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_lifecycle.py +3 -1
  10. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/uv.lock +1 -1
  11. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/.github/workflows/ci.yml +0 -0
  12. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/.github/workflows/publish.yml +0 -0
  13. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/.gitignore +0 -0
  14. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/.python-version +0 -0
  15. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/LICENSE +0 -0
  16. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/.env.example +0 -0
  17. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/.gitignore +0 -0
  18. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/README.md +0 -0
  19. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/claim.py +0 -0
  20. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/server.py +0 -0
  21. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/fastmcp-paywall/uv.lock +0 -0
  22. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/langchain-paywall/.env.example +0 -0
  23. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/langchain-paywall/.gitignore +0 -0
  24. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/langchain-paywall/README.md +0 -0
  25. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/langchain-paywall/agent.py +0 -0
  26. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/.env.example +0 -0
  27. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/.streamlit/config.toml +0 -0
  28. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/PLAN.md +0 -0
  29. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/README.md +0 -0
  30. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/agents.py +0 -0
  31. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/app.py +0 -0
  32. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/demo_customers.py +0 -0
  33. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/requirements.txt +0 -0
  34. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/sdk_gateway.py +0 -0
  35. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/examples/marketplace/ui_components.py +0 -0
  36. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/_async_client.py +0 -0
  37. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/_config.py +0 -0
  38. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/_http.py +0 -0
  39. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/client.py +0 -0
  40. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/events.py +0 -0
  41. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/exceptions.py +0 -0
  42. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/fastapi.py +0 -0
  43. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/idempotency.py +0 -0
  44. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/langchain.py +0 -0
  45. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/models.py +0 -0
  46. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/paywall_state.py +0 -0
  47. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/py.typed +0 -0
  48. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/src/solvapay/webhooks.py +0 -0
  49. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/__init__.py +0 -0
  50. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/conftest.py +0 -0
  51. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_admin.py +0 -0
  52. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_async_client.py +0 -0
  53. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_checkout.py +0 -0
  54. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_config.py +0 -0
  55. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_customer.py +0 -0
  56. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_errors.py +0 -0
  57. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_http.py +0 -0
  58. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_idempotency.py +0 -0
  59. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_invariants.py +0 -0
  60. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_langchain.py +0 -0
  61. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_limits.py +0 -0
  62. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_packaging.py +0 -0
  63. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_paywall.py +0 -0
  64. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_paywall_state.py +0 -0
  65. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_redaction.py +0 -0
  66. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_webhook_events.py +0 -0
  67. {solvapay_python-0.7.1 → solvapay_python-0.7.2}/tests/test_webhooks.py +0 -0
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.2 — 2026-05-18
4
+
5
+ Bug fixes and documentation update.
6
+
7
+ ### Fixed
8
+ - **`paywall.require_async`**: `AsyncSolvaPay()` instantiated without a caller-supplied `client=` was not closed after the decorated function returned, leaking the underlying `httpx.AsyncClient` connection pool. Wrapped in `try/finally`; `await sv.aclose()` called when the decorator owns the client.
9
+ - **`examples/fastmcp-paywall/pyproject.toml`**: dependency used wrong PyPI dist name (`solvapay`) and a stale `@v0.3.0` pin. Updated to `solvapay-python>=0.7.2`.
10
+ - **`examples/langchain-paywall/pyproject.toml`**: same wrong dist name and stale `@v0.5.0` pin. Updated to `solvapay-python[langchain]>=0.7.2`.
11
+
12
+ ### Docs
13
+ - **README** updated to v0.7.1 surface: `paywall_state.gate()`, error hierarchy, idempotency keys, admin ops table, marketplace example, roadmap entries for v0.7.0 and v0.7.1.
14
+
15
+ ### Internal
16
+ - `tests/test_lifecycle.py` reformatted (ruff format compliance)
17
+
3
18
  ## 0.7.1 — 2026-05-17
4
19
 
5
20
  Payments-grade hardening: structured errors, idempotency keys, py.typed, structured logging.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solvapay-python
3
- Version: 0.7.1
3
+ Version: 0.7.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
@@ -35,12 +35,15 @@ Description-Content-Type: text/markdown
35
35
 
36
36
  Community Python SDK for [SolvaPay](https://solvapay.com) — payment rails for the agentic economy.
37
37
 
38
- > **Status:** v0.6, community-maintained. Available on PyPI. Pending official adoption.
38
+ > **Status:** v0.7.2, community-maintained. Available on PyPI. Pending official adoption.
39
39
  > Mirrors the most-used surface of [@solvapay/core](https://github.com/solvapay/solvapay-sdk).
40
40
 
41
41
  Python is the dominant language for agent frameworks (LangChain, FastMCP, CrewAI, AutoGen). SolvaPay's official SDK is TypeScript-only. This SDK brings first-class Python support so agent developers can gate tools behind paywalls without switching ecosystems.
42
42
 
43
- > **New in v0.6:** Admin endpoints (products, plans, merchant, platform config). Published to PyPI.
43
+ > **New in v0.7.2:** Async resource leak fix in `@paywall.require_async` — `AsyncSolvaPay` now properly closed when owned by the decorator. Example dep fixes.
44
+ > **v0.7.1:** Full error hierarchy (`AuthenticationError`, `NotFoundError`, `RateLimitError`, `APIConnectionError`, `APITimeoutError`), idempotency keys on all mutating ops, `py.typed` PEP 561 marker, structured HTTP logging.
45
+ > **New in v0.7.0:** Real-API alignment (wire-format fixes), `paywall_state.gate()` enrichment helper, marketplace Streamlit demo.
46
+ > **v0.6:** Admin endpoints (products, plans, merchant, platform config). Published to PyPI.
44
47
  > **v0.5:** Paywall state classifier (`paywall_state` module) and LangChain `monetize_tool` decorator — gate any LangChain tool behind a SolvaPay paywall with one line.
45
48
  > **v0.4:** Async client (`AsyncSolvaPay`), lifecycle ops, typed webhook events.
46
49
 
@@ -150,12 +153,69 @@ if not limits.within_limits:
150
153
  print(d.checkout_url) # "https://solvapay.com/c/..."
151
154
  ```
152
155
 
156
+ For real-API calls use `gate()` instead — it enriches the bare `/v1/sdk/limits` response (which has no `plan` or `checkout_url`) in one call:
157
+
158
+ ```python
159
+ from solvapay.paywall_state import gate
160
+
161
+ decision = gate(sv, customer_ref="cus_x", product_ref="prd_y")
162
+ # decision.state — PaywallState.UPGRADE_REQUIRED (etc)
163
+ # decision.checkout_url — minted via create_checkout_session if needed
164
+ # decision.message — TS-style copy with URL inlined
165
+ ```
166
+
167
+ ## Error handling
168
+
169
+ v0.7.1 ships a structured exception hierarchy under `SolvaPayError`:
170
+
171
+ ```python
172
+ import time
173
+ from solvapay import (
174
+ SolvaPayError, # catch-all
175
+ APIError, # base for all HTTP errors — has .status_code, .request_id
176
+ AuthenticationError, # 401
177
+ NotFoundError, # 404
178
+ RateLimitError, # 429 — adds .retry_after
179
+ APIConnectionError, # network failure
180
+ APITimeoutError, # request timeout
181
+ )
182
+
183
+ try:
184
+ sv.get_customer("cus_missing")
185
+ except NotFoundError as e:
186
+ print(e.status_code, e.request_id)
187
+ except RateLimitError as e:
188
+ time.sleep(float(e.retry_after or 1))
189
+ except SolvaPayError:
190
+ raise
191
+ ```
192
+
193
+ Use `except SolvaPayError` as the catch-all. Prefer specific subclasses over checking `.status_code`.
194
+
195
+ ## Idempotency keys
196
+
197
+ All mutating ops accept an optional `idempotency_key` to make retries safe:
198
+
199
+ ```python
200
+ from solvapay.idempotency import from_payload
201
+
202
+ key = from_payload("checkout", customer_ref, product_ref)
203
+ session = sv.create_checkout_session(
204
+ customer_ref=customer_ref,
205
+ product_ref="prd_xyz",
206
+ idempotency_key=key,
207
+ )
208
+ ```
209
+
210
+ `from_payload(*parts)` hashes its args to a 32-hex-char deterministic key. Pass the same key on retry — SolvaPay deduplicates server-side.
211
+
153
212
  ## Examples
154
213
 
155
214
  | Path | What it shows |
156
215
  |---|---|
157
216
  | [`examples/fastmcp-paywall/`](examples/fastmcp-paywall/) | FastMCP server with two paywalled tools. Demo for `@paywall.require` + MCP. |
158
217
  | [`examples/langchain-paywall/`](examples/langchain-paywall/) | LangChain agent with `monetize_tool`. Shows paywall response in agent trace. |
218
+ | [`examples/marketplace/`](examples/marketplace/) | Streamlit demo — paywalled AI-agent marketplace. Real sandbox, real Gemini LLM, two demo customers (one subscribed, one free tier). Shows `paywall_state.gate()` in action. |
159
219
 
160
220
  ## TS ↔ Python parity
161
221
 
@@ -199,6 +259,22 @@ session = sv.create_checkout_session(
199
259
  | `cancel_purchase` | `POST /v1/sdk/purchases/{ref}/cancel` | Cancel a subscription |
200
260
  | `reactivate_purchase` | `POST /v1/sdk/purchases/{ref}/reactivate` | Reactivate cancelled purchase |
201
261
 
262
+ **Admin (new in v0.6):**
263
+
264
+ | Python | Verb + path | Description |
265
+ |---|---|---|
266
+ | `list_products` | `GET /v1/sdk/products` | List all products |
267
+ | `get_product` | `GET /v1/sdk/products/{ref}` | Fetch product |
268
+ | `create_product` | `POST /v1/sdk/products` | Create product |
269
+ | `delete_product` | `DELETE /v1/sdk/products/{ref}` | Delete product |
270
+ | `clone_product` | `POST /v1/sdk/products/{ref}/clone` | Clone product |
271
+ | `list_plans` | `GET /v1/sdk/products/{ref}/plans` | List plans |
272
+ | `create_plan` | `POST /v1/sdk/products/{ref}/plans` | Create plan |
273
+ | `update_plan` | `PUT /v1/sdk/products/{ref}/plans/{ref}` | Update plan |
274
+ | `delete_plan` | `DELETE /v1/sdk/products/{ref}/plans/{ref}` | Delete plan |
275
+ | `get_merchant` | `GET /v1/sdk/merchant` | Merchant info |
276
+ | `get_platform_config` | `GET /v1/sdk/platform-config` | Platform config |
277
+
202
278
  All methods available on both `SolvaPay` (sync) and `AsyncSolvaPay` (async).
203
279
 
204
280
  ## Webhook handler (FastAPI)
@@ -261,6 +337,9 @@ async def handle_webhook(request: Request) -> dict:
261
337
  - v0.4 — async client (`AsyncSolvaPay`), lifecycle ops, typed webhook events ✅
262
338
  - v0.5 — paywall state classifier, LangChain `monetize_tool` decorator ✅
263
339
  - v0.6 — admin endpoints (products, plans, merchant, platform config), PyPI publish ✅
340
+ - v0.7.0 — real-API wire-format fixes, `paywall_state.gate()`, marketplace demo ✅
341
+ - v0.7.1 — structured error hierarchy, idempotency keys, `py.typed`, structured logging ✅
342
+ - v0.7.2 — async resource leak fix (`require_async`), example dep fixes ✅
264
343
 
265
344
  ## Contributing
266
345
 
@@ -4,12 +4,15 @@
4
4
 
5
5
  Community Python SDK for [SolvaPay](https://solvapay.com) — payment rails for the agentic economy.
6
6
 
7
- > **Status:** v0.6, community-maintained. Available on PyPI. Pending official adoption.
7
+ > **Status:** v0.7.2, community-maintained. Available on PyPI. Pending official adoption.
8
8
  > Mirrors the most-used surface of [@solvapay/core](https://github.com/solvapay/solvapay-sdk).
9
9
 
10
10
  Python is the dominant language for agent frameworks (LangChain, FastMCP, CrewAI, AutoGen). SolvaPay's official SDK is TypeScript-only. This SDK brings first-class Python support so agent developers can gate tools behind paywalls without switching ecosystems.
11
11
 
12
- > **New in v0.6:** Admin endpoints (products, plans, merchant, platform config). Published to PyPI.
12
+ > **New in v0.7.2:** Async resource leak fix in `@paywall.require_async` — `AsyncSolvaPay` now properly closed when owned by the decorator. Example dep fixes.
13
+ > **v0.7.1:** Full error hierarchy (`AuthenticationError`, `NotFoundError`, `RateLimitError`, `APIConnectionError`, `APITimeoutError`), idempotency keys on all mutating ops, `py.typed` PEP 561 marker, structured HTTP logging.
14
+ > **New in v0.7.0:** Real-API alignment (wire-format fixes), `paywall_state.gate()` enrichment helper, marketplace Streamlit demo.
15
+ > **v0.6:** Admin endpoints (products, plans, merchant, platform config). Published to PyPI.
13
16
  > **v0.5:** Paywall state classifier (`paywall_state` module) and LangChain `monetize_tool` decorator — gate any LangChain tool behind a SolvaPay paywall with one line.
14
17
  > **v0.4:** Async client (`AsyncSolvaPay`), lifecycle ops, typed webhook events.
15
18
 
@@ -119,12 +122,69 @@ if not limits.within_limits:
119
122
  print(d.checkout_url) # "https://solvapay.com/c/..."
120
123
  ```
121
124
 
125
+ For real-API calls use `gate()` instead — it enriches the bare `/v1/sdk/limits` response (which has no `plan` or `checkout_url`) in one call:
126
+
127
+ ```python
128
+ from solvapay.paywall_state import gate
129
+
130
+ decision = gate(sv, customer_ref="cus_x", product_ref="prd_y")
131
+ # decision.state — PaywallState.UPGRADE_REQUIRED (etc)
132
+ # decision.checkout_url — minted via create_checkout_session if needed
133
+ # decision.message — TS-style copy with URL inlined
134
+ ```
135
+
136
+ ## Error handling
137
+
138
+ v0.7.1 ships a structured exception hierarchy under `SolvaPayError`:
139
+
140
+ ```python
141
+ import time
142
+ from solvapay import (
143
+ SolvaPayError, # catch-all
144
+ APIError, # base for all HTTP errors — has .status_code, .request_id
145
+ AuthenticationError, # 401
146
+ NotFoundError, # 404
147
+ RateLimitError, # 429 — adds .retry_after
148
+ APIConnectionError, # network failure
149
+ APITimeoutError, # request timeout
150
+ )
151
+
152
+ try:
153
+ sv.get_customer("cus_missing")
154
+ except NotFoundError as e:
155
+ print(e.status_code, e.request_id)
156
+ except RateLimitError as e:
157
+ time.sleep(float(e.retry_after or 1))
158
+ except SolvaPayError:
159
+ raise
160
+ ```
161
+
162
+ Use `except SolvaPayError` as the catch-all. Prefer specific subclasses over checking `.status_code`.
163
+
164
+ ## Idempotency keys
165
+
166
+ All mutating ops accept an optional `idempotency_key` to make retries safe:
167
+
168
+ ```python
169
+ from solvapay.idempotency import from_payload
170
+
171
+ key = from_payload("checkout", customer_ref, product_ref)
172
+ session = sv.create_checkout_session(
173
+ customer_ref=customer_ref,
174
+ product_ref="prd_xyz",
175
+ idempotency_key=key,
176
+ )
177
+ ```
178
+
179
+ `from_payload(*parts)` hashes its args to a 32-hex-char deterministic key. Pass the same key on retry — SolvaPay deduplicates server-side.
180
+
122
181
  ## Examples
123
182
 
124
183
  | Path | What it shows |
125
184
  |---|---|
126
185
  | [`examples/fastmcp-paywall/`](examples/fastmcp-paywall/) | FastMCP server with two paywalled tools. Demo for `@paywall.require` + MCP. |
127
186
  | [`examples/langchain-paywall/`](examples/langchain-paywall/) | LangChain agent with `monetize_tool`. Shows paywall response in agent trace. |
187
+ | [`examples/marketplace/`](examples/marketplace/) | Streamlit demo — paywalled AI-agent marketplace. Real sandbox, real Gemini LLM, two demo customers (one subscribed, one free tier). Shows `paywall_state.gate()` in action. |
128
188
 
129
189
  ## TS ↔ Python parity
130
190
 
@@ -168,6 +228,22 @@ session = sv.create_checkout_session(
168
228
  | `cancel_purchase` | `POST /v1/sdk/purchases/{ref}/cancel` | Cancel a subscription |
169
229
  | `reactivate_purchase` | `POST /v1/sdk/purchases/{ref}/reactivate` | Reactivate cancelled purchase |
170
230
 
231
+ **Admin (new in v0.6):**
232
+
233
+ | Python | Verb + path | Description |
234
+ |---|---|---|
235
+ | `list_products` | `GET /v1/sdk/products` | List all products |
236
+ | `get_product` | `GET /v1/sdk/products/{ref}` | Fetch product |
237
+ | `create_product` | `POST /v1/sdk/products` | Create product |
238
+ | `delete_product` | `DELETE /v1/sdk/products/{ref}` | Delete product |
239
+ | `clone_product` | `POST /v1/sdk/products/{ref}/clone` | Clone product |
240
+ | `list_plans` | `GET /v1/sdk/products/{ref}/plans` | List plans |
241
+ | `create_plan` | `POST /v1/sdk/products/{ref}/plans` | Create plan |
242
+ | `update_plan` | `PUT /v1/sdk/products/{ref}/plans/{ref}` | Update plan |
243
+ | `delete_plan` | `DELETE /v1/sdk/products/{ref}/plans/{ref}` | Delete plan |
244
+ | `get_merchant` | `GET /v1/sdk/merchant` | Merchant info |
245
+ | `get_platform_config` | `GET /v1/sdk/platform-config` | Platform config |
246
+
171
247
  All methods available on both `SolvaPay` (sync) and `AsyncSolvaPay` (async).
172
248
 
173
249
  ## Webhook handler (FastAPI)
@@ -230,6 +306,9 @@ async def handle_webhook(request: Request) -> dict:
230
306
  - v0.4 — async client (`AsyncSolvaPay`), lifecycle ops, typed webhook events ✅
231
307
  - v0.5 — paywall state classifier, LangChain `monetize_tool` decorator ✅
232
308
  - v0.6 — admin endpoints (products, plans, merchant, platform config), PyPI publish ✅
309
+ - v0.7.0 — real-API wire-format fixes, `paywall_state.gate()`, marketplace demo ✅
310
+ - v0.7.1 — structured error hierarchy, idempotency keys, `py.typed`, structured logging ✅
311
+ - v0.7.2 — async resource leak fix (`require_async`), example dep fixes ✅
233
312
 
234
313
  ## Contributing
235
314
 
@@ -4,7 +4,7 @@ version = "0.1.0"
4
4
  description = "FastMCP server gated by SolvaPay paywall — demo for solvapay-python"
5
5
  requires-python = ">=3.10"
6
6
  dependencies = [
7
- "solvapay @ git+https://github.com/dhruv-sanan/solvapay-python@v0.3.0",
7
+ "solvapay-python>=0.7.1",
8
8
  "fastmcp>=0.4",
9
9
  "httpx>=0.27",
10
10
  "python-dotenv>=1.0",
@@ -3,7 +3,7 @@ name = "langchain-paywall-example"
3
3
  version = "0.1.0"
4
4
  requires-python = ">=3.10"
5
5
  dependencies = [
6
- "solvapay[langchain] @ git+https://github.com/dhruv-sanan/solvapay-python@v0.5.0",
6
+ "solvapay-python[langchain]>=0.7.1",
7
7
  "langchain>=0.3",
8
8
  "langchain-openai>=0.2",
9
9
  "python-dotenv>=1.0",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "solvapay-python"
7
- version = "0.7.1"
7
+ version = "0.7.2"
8
8
  description = "Community Python SDK for SolvaPay (agent-native payment rails)"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -75,4 +75,4 @@ __all__ = [
75
75
  "paywall",
76
76
  "verify_webhook",
77
77
  ]
78
- __version__ = "0.7.1"
78
+ __version__ = "0.7.2"
@@ -124,24 +124,29 @@ def require_async(
124
124
  f"@paywall.require_async expected str kwarg '{customer_ref_arg}', "
125
125
  f"got {type(customer_ref).__name__}"
126
126
  )
127
- sv: AsyncSolvaPay = client if isinstance(client, AsyncSolvaPay) else AsyncSolvaPay()
128
- limits = await sv.check_limits(
129
- customer_ref=customer_ref,
130
- product_ref=product,
131
- plan_ref=plan,
132
- )
133
- if not limits.within_limits:
134
- checkout_url = limits.checkout_url
135
- if checkout_url is None:
136
- try:
137
- session = await sv.create_checkout_session(
138
- customer_ref=customer_ref, product_ref=product, plan_ref=plan
139
- )
140
- checkout_url = session.checkout_url
141
- except SolvaPayError:
142
- pass
143
- raise PaywallRequired(checkout_url=checkout_url)
144
- return await fn(*args, **kwargs)
127
+ _owns_client = not isinstance(client, AsyncSolvaPay)
128
+ sv: AsyncSolvaPay = AsyncSolvaPay() if _owns_client else client # type: ignore[assignment]
129
+ try:
130
+ limits = await sv.check_limits(
131
+ customer_ref=customer_ref,
132
+ product_ref=product,
133
+ plan_ref=plan,
134
+ )
135
+ if not limits.within_limits:
136
+ checkout_url = limits.checkout_url
137
+ if checkout_url is None:
138
+ try:
139
+ session = await sv.create_checkout_session(
140
+ customer_ref=customer_ref, product_ref=product, plan_ref=plan
141
+ )
142
+ checkout_url = session.checkout_url
143
+ except SolvaPayError:
144
+ pass
145
+ raise PaywallRequired(checkout_url=checkout_url)
146
+ return await fn(*args, **kwargs)
147
+ finally:
148
+ if _owns_client:
149
+ await sv.aclose()
145
150
 
146
151
  return wrapper
147
152
 
@@ -109,7 +109,9 @@ def test_get_customer_balance_returns_balance(client: SolvaPay) -> None:
109
109
  assert balance.customer_ref == "cus_1"
110
110
  assert balance.credits == 4250
111
111
  assert balance.credits_per_minor_unit == 100
112
- assert balance.balance == 0.425 # derived: credits / credits_per_minor_unit / 100 (minor→major unit)
112
+ assert (
113
+ balance.balance == 0.425
114
+ ) # derived: credits / credits_per_minor_unit / 100 (minor→major unit)
113
115
  assert balance.currency == "USD"
114
116
 
115
117
 
@@ -1046,7 +1046,7 @@ wheels = [
1046
1046
 
1047
1047
  [[package]]
1048
1048
  name = "solvapay-python"
1049
- version = "0.7.1"
1049
+ version = "0.7.2"
1050
1050
  source = { editable = "." }
1051
1051
  dependencies = [
1052
1052
  { name = "httpx" },
File without changes