solvapay-python 0.7.2__tar.gz → 0.8.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.
- solvapay_python-0.8.0/.github/ISSUE_TEMPLATE/bug.yml +27 -0
- solvapay_python-0.8.0/.github/ISSUE_TEMPLATE/feature.yml +23 -0
- solvapay_python-0.8.0/.github/ISSUE_TEMPLATE/question.yml +14 -0
- solvapay_python-0.8.0/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/.github/workflows/ci.yml +3 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/CHANGELOG.md +25 -0
- solvapay_python-0.8.0/CODEOWNERS +1 -0
- solvapay_python-0.8.0/CODE_OF_CONDUCT.md +27 -0
- solvapay_python-0.8.0/CONTRIBUTING.md +58 -0
- solvapay_python-0.8.0/PKG-INFO +315 -0
- solvapay_python-0.8.0/README.md +282 -0
- solvapay_python-0.8.0/SECURITY.md +36 -0
- solvapay_python-0.8.0/SUPPORT.md +10 -0
- solvapay_python-0.8.0/assets/agent-marketplace.png +0 -0
- solvapay_python-0.8.0/docs/architecture/layers.md +46 -0
- solvapay_python-0.8.0/docs/rfcs/0001-spending-policy.md +60 -0
- solvapay_python-0.8.0/examples/marketplace/.gitignore +4 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/.env.example +3 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/.gitignore +4 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/README.md +52 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/agent_langchain.py +43 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/model.py +13 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/pyproject.toml +9 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/script_async.py +43 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/server_mcp.py +28 -0
- solvapay_python-0.8.0/examples/multi-framework-paywall/tool.py +36 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/pyproject.toml +4 -2
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/__init__.py +31 -1
- solvapay_python-0.8.0/src/solvapay/_async_client.py +308 -0
- solvapay_python-0.8.0/src/solvapay/_http.py +21 -0
- solvapay_python-0.8.0/src/solvapay/_stability.py +60 -0
- solvapay_python-0.8.0/src/solvapay/_transport/__init__.py +106 -0
- solvapay_python-0.8.0/src/solvapay/_transport/_recipe.py +63 -0
- solvapay_python-0.8.0/src/solvapay/_transport/httpx_transport.py +397 -0
- solvapay_python-0.8.0/src/solvapay/_transport/middleware.py +233 -0
- solvapay_python-0.8.0/src/solvapay/adapters/__init__.py +3 -0
- solvapay_python-0.8.0/src/solvapay/adapters/langchain.py +88 -0
- solvapay_python-0.8.0/src/solvapay/adapters/mcp.py +158 -0
- solvapay_python-0.8.0/src/solvapay/client.py +303 -0
- solvapay_python-0.8.0/src/solvapay/langchain.py +15 -0
- solvapay_python-0.8.0/src/solvapay/operations/__init__.py +7 -0
- solvapay_python-0.8.0/src/solvapay/operations/_registry.py +117 -0
- solvapay_python-0.8.0/src/solvapay/operations/checkout.py +75 -0
- solvapay_python-0.8.0/src/solvapay/operations/customers.py +256 -0
- solvapay_python-0.8.0/src/solvapay/operations/limits.py +77 -0
- solvapay_python-0.8.0/src/solvapay/operations/merchant.py +71 -0
- solvapay_python-0.8.0/src/solvapay/operations/plans.py +167 -0
- solvapay_python-0.8.0/src/solvapay/operations/products.py +141 -0
- solvapay_python-0.8.0/src/solvapay/operations/purchases.py +103 -0
- solvapay_python-0.8.0/src/solvapay/operations/usage.py +75 -0
- solvapay_python-0.8.0/src/solvapay/paywall/__init__.py +32 -0
- solvapay_python-0.8.0/src/solvapay/paywall/core.py +152 -0
- solvapay_python-0.8.0/src/solvapay/paywall/decorators.py +126 -0
- solvapay_python-0.8.0/src/solvapay/paywall/meta.py +18 -0
- solvapay_python-0.8.0/src/solvapay/paywall/policy.py +14 -0
- solvapay_python-0.8.0/src/solvapay/paywall/resolvers.py +60 -0
- solvapay_python-0.8.0/src/solvapay/paywall/state.py +23 -0
- solvapay_python-0.8.0/src/solvapay/webhooks/__init__.py +28 -0
- solvapay_python-0.8.0/src/solvapay/webhooks/envelope.py +16 -0
- solvapay_python-0.8.0/src/solvapay/webhooks/pipeline.py +88 -0
- solvapay_python-0.8.0/src/solvapay/webhooks/replay.py +42 -0
- solvapay_python-0.8.0/src/solvapay/webhooks/rotation.py +32 -0
- solvapay_python-0.7.2/src/solvapay/webhooks.py → solvapay_python-0.8.0/src/solvapay/webhooks/verify.py +4 -22
- solvapay_python-0.8.0/tests/_stability/__init__.py +0 -0
- solvapay_python-0.8.0/tests/_stability/test_stable_returns_identity.py +75 -0
- solvapay_python-0.8.0/tests/_transport/__init__.py +0 -0
- solvapay_python-0.8.0/tests/_transport/test_aclose_cascade.py +100 -0
- solvapay_python-0.8.0/tests/_transport/test_error_wrapping.py +66 -0
- solvapay_python-0.8.0/tests/_transport/test_headers_case_insensitive.py +52 -0
- solvapay_python-0.8.0/tests/_transport/test_middleware_composition.py +51 -0
- solvapay_python-0.8.0/tests/_transport/test_protocol_conformance.py +46 -0
- solvapay_python-0.8.0/tests/adapters/__init__.py +0 -0
- solvapay_python-0.8.0/tests/adapters/test_langchain_protocol.py +99 -0
- solvapay_python-0.8.0/tests/adapters/test_mcp.py +165 -0
- solvapay_python-0.8.0/tests/operations/__init__.py +0 -0
- solvapay_python-0.8.0/tests/operations/test_namespace_api.py +59 -0
- solvapay_python-0.8.0/tests/operations/test_path_interpolation.py +54 -0
- solvapay_python-0.8.0/tests/operations/test_retry_safety_enum.py +57 -0
- solvapay_python-0.8.0/tests/paywall/__init__.py +0 -0
- solvapay_python-0.8.0/tests/paywall/test_checkout_mint_error_surfaces.py +52 -0
- solvapay_python-0.8.0/tests/paywall/test_payable_tool_meta.py +59 -0
- solvapay_python-0.8.0/tests/paywall/test_resolvers.py +47 -0
- solvapay_python-0.8.0/tests/paywall/test_split_classes.py +34 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_invariants.py +2 -2
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_langchain.py +21 -16
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_paywall.py +5 -5
- solvapay_python-0.8.0/tests/webhooks/__init__.py +0 -0
- solvapay_python-0.8.0/tests/webhooks/test_clock_skew_vs_replay_ttl.py +58 -0
- solvapay_python-0.8.0/tests/webhooks/test_seen_cache_atomic.py +54 -0
- solvapay_python-0.8.0/tests/webhooks/test_webhook_pipeline.py +66 -0
- solvapay_python-0.8.0/tools/api_baseline.json +82 -0
- solvapay_python-0.8.0/tools/api_diff.py +60 -0
- solvapay_python-0.8.0/tools/importlinter.cfg +42 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/uv.lock +795 -114
- solvapay_python-0.7.2/PKG-INFO +0 -357
- solvapay_python-0.7.2/README.md +0 -326
- solvapay_python-0.7.2/src/solvapay/_async_client.py +0 -387
- solvapay_python-0.7.2/src/solvapay/_http.py +0 -220
- solvapay_python-0.7.2/src/solvapay/client.py +0 -380
- solvapay_python-0.7.2/src/solvapay/langchain.py +0 -67
- solvapay_python-0.7.2/src/solvapay/paywall.py +0 -153
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/.github/workflows/publish.yml +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/.gitignore +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/.python-version +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/LICENSE +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/.env.example +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/.gitignore +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/README.md +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/claim.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/pyproject.toml +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/server.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/fastmcp-paywall/uv.lock +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/langchain-paywall/.env.example +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/langchain-paywall/.gitignore +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/langchain-paywall/README.md +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/langchain-paywall/agent.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/langchain-paywall/pyproject.toml +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/.env.example +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/.streamlit/config.toml +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/PLAN.md +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/README.md +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/agents.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/app.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/demo_customers.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/requirements.txt +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/sdk_gateway.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/examples/marketplace/ui_components.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/_config.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/events.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/exceptions.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/fastapi.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/idempotency.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/models.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/paywall_state.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/src/solvapay/py.typed +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/__init__.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/conftest.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_admin.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_async_client.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_checkout.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_config.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_customer.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_errors.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_http.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_idempotency.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_lifecycle.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_limits.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_packaging.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_paywall_state.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_redaction.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_webhook_events.py +0 -0
- {solvapay_python-0.7.2 → solvapay_python-0.8.0}/tests/test_webhooks.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Bug Report
|
|
2
|
+
description: Something broken in the SDK
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: Please fill in the details below.
|
|
8
|
+
- type: input
|
|
9
|
+
id: version
|
|
10
|
+
attributes:
|
|
11
|
+
label: SDK version
|
|
12
|
+
placeholder: "0.8.0"
|
|
13
|
+
validations:
|
|
14
|
+
required: true
|
|
15
|
+
- type: textarea
|
|
16
|
+
id: description
|
|
17
|
+
attributes:
|
|
18
|
+
label: What happened?
|
|
19
|
+
description: What did you expect vs what actually happened?
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
- type: textarea
|
|
23
|
+
id: repro
|
|
24
|
+
attributes:
|
|
25
|
+
label: Minimal repro
|
|
26
|
+
description: Minimal Python snippet to reproduce
|
|
27
|
+
render: python
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Feature Request
|
|
2
|
+
description: Suggest a new capability
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What problem does this solve?
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: proposal
|
|
13
|
+
attributes:
|
|
14
|
+
label: Proposed solution
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: dropdown
|
|
18
|
+
id: priority
|
|
19
|
+
attributes:
|
|
20
|
+
label: Priority
|
|
21
|
+
options:
|
|
22
|
+
- Nice to have
|
|
23
|
+
- Blocking a use-case
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: Question
|
|
2
|
+
description: How do I...?
|
|
3
|
+
labels: [question]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: question
|
|
7
|
+
attributes:
|
|
8
|
+
label: What are you trying to do?
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
11
|
+
- type: textarea
|
|
12
|
+
id: tried
|
|
13
|
+
attributes:
|
|
14
|
+
label: What have you tried?
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
## What
|
|
2
|
+
|
|
3
|
+
<!-- Brief description of the change -->
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
<!-- Problem it solves or feature it adds -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] Tests added / updated
|
|
12
|
+
- [ ] `CHANGELOG.md` entry added
|
|
13
|
+
- [ ] Layer DAG respected (`uv run lint-imports` passes)
|
|
14
|
+
- [ ] If new public exports: `tools/api_baseline.json` updated
|
|
15
|
+
- [ ] Docs updated (if public API changed)
|
|
@@ -20,5 +20,8 @@ jobs:
|
|
|
20
20
|
run: uv python install ${{ matrix.python-version }}
|
|
21
21
|
- run: uv sync --all-extras --dev
|
|
22
22
|
- run: uv run ruff check src tests
|
|
23
|
+
- run: uv run ruff format --check src tests
|
|
23
24
|
- run: uv run mypy src
|
|
24
25
|
- run: uv run pytest -v
|
|
26
|
+
- run: uv run python tools/api_diff.py
|
|
27
|
+
- run: uv run lint-imports --config tools/importlinter.cfg
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0 — 2026-05-23
|
|
4
|
+
|
|
5
|
+
V1 architecture spine + AI-agent moat. 10 atomic commits establishing locked architecture invariants per HLD §V1.1–V1.19.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **Transport Protocol kernel** (`solvapay._transport`): `Transport`/`AsyncTransport` Protocol shapes, `RequestSpec`/`ResponseSpec` frozen dataclasses, case-insensitive `Headers`, canonical middleware stack (`Redacting` → `Logging` → `IdempotencyHeader` → `ContextPropagating`), `default_stack()` recipe.
|
|
9
|
+
- **Operations registry + resource-namespace API**: `OpSpec` + `RetrySafety` enum, `REGISTRY`, path interpolation with `urllib.parse.quote`. All clients gain eager namespace attrs: `sv.customers`, `sv.checkout`, `sv.limits`, `sv.purchases`, `sv.usage`, `sv.products`, `sv.plans`, `sv.merchant`.
|
|
10
|
+
- **Paywall package** (`solvapay.paywall`): `Paywall`/`AsyncPaywall` split with type-narrowed construction, `PaywallRequired.checkout_mint_error`, `PayableToolMeta(_meta_version=1)`, `KwargsResolver`/`PositionalResolver`/`PydanticBodyResolver`, `@require`/`@require_async`/`@payable_tool` decorators.
|
|
11
|
+
- **Webhook pipeline** (`solvapay.webhooks`): `WebhookPipeline` with two-knob config (`max_clock_skew_seconds`, `replay_ttl_seconds`), atomic `SeenEventCache.try_claim` (`threading.Lock`), `MultiSecretVerifier` interface, `WebhookEnvelope` dataclass.
|
|
12
|
+
- **`solvapay.adapters.mcp`** (optional `solvapay[mcp]`): `@payable_tool` decorator + four schema flavors (`payable_tool_mcp_schema`, `payable_tool_openai_function`, `payable_tool_anthropic_tool`, `payable_tool_langchain_args_schema`), `register_payable_tool_fastmcp`.
|
|
13
|
+
- **Stability manifest** (`solvapay._stability`): `stable(X)→X` identity decorator registers in `MANIFEST`; `experimental` emits once-per-process `RuntimeWarning`; `deprecated(removed_in=)`. CI gate: `tools/api_diff.py` fails on `@stable` symbol removal without prior `@deprecated`.
|
|
14
|
+
- **Import-linter DAG gate**: `tools/importlinter.cfg` (3 contracts), `docs/architecture/layers.md`. CI fails on layer violation.
|
|
15
|
+
- **LangChain adapter refactor** (`solvapay.adapters.langchain`): Protocol duck-typing — no hard `langchain_core` import at module level; absorbs 0.3→0.4 churn.
|
|
16
|
+
- **Multi-framework demo** (`examples/multi-framework-paywall/`): one `@payable_tool`-decorated function runs across FastMCP, LangChain, and raw async — "one tool, three runtimes, one paywall."
|
|
17
|
+
- **Governance scaffold**: `CODEOWNERS`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, `SUPPORT.md`, `CONTRIBUTING.md`, GitHub issue templates, PR template, `docs/rfcs/0001-spending-policy.md`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Flat methods deprecated**: `sv.ensure_customer()`, `sv.check_limits()`, etc. now emit `DeprecationWarning(stacklevel=2)` per call. Use `sv.customers.ensure()`, `sv.limits.check()`, etc. Flat shims removed in v2.0.
|
|
21
|
+
- **`langchain-core` upper bound**: `langchain-core>=0.3,<0.4` now enforced in both dev and optional deps.
|
|
22
|
+
- **`solvapay.langchain`** replaced by `solvapay.adapters.langchain`; old path is a deprecated shim.
|
|
23
|
+
|
|
24
|
+
### Deprecated
|
|
25
|
+
- `SolvaPayAPIError` — use `APIError`. Alias fires `DeprecationWarning`; removed in v2.0.
|
|
26
|
+
- All flat client methods (`sv.ensure_customer`, `sv.check_limits`, etc.) — use resource-namespace API.
|
|
27
|
+
|
|
3
28
|
## 0.7.2 — 2026-05-18
|
|
4
29
|
|
|
5
30
|
Bug fixes and documentation update.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @dhruv-sanan
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We pledge to make participation in our community a harassment-free experience for everyone.
|
|
6
|
+
|
|
7
|
+
## Our Standards
|
|
8
|
+
|
|
9
|
+
**Positive behavior:**
|
|
10
|
+
- Using welcoming and inclusive language
|
|
11
|
+
- Being respectful of differing viewpoints
|
|
12
|
+
- Gracefully accepting constructive criticism
|
|
13
|
+
- Focusing on what is best for the community
|
|
14
|
+
|
|
15
|
+
**Unacceptable behavior:**
|
|
16
|
+
- Harassment, trolling, or personal attacks
|
|
17
|
+
- Publishing others' private information without permission
|
|
18
|
+
- Other conduct which could reasonably be considered inappropriate
|
|
19
|
+
|
|
20
|
+
## Enforcement
|
|
21
|
+
|
|
22
|
+
Community leaders may remove, edit, or reject contributions that violate this Code of Conduct.
|
|
23
|
+
Report issues to `dhruv.sanan@greyorange.com`.
|
|
24
|
+
|
|
25
|
+
## Attribution
|
|
26
|
+
|
|
27
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Local development
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/dhruv-sanan/solvapay-python
|
|
7
|
+
cd solvapay-python
|
|
8
|
+
uv sync --all-extras --dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Running checks
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv run ruff check src tests # lint
|
|
15
|
+
uv run ruff format --check src tests # format gate
|
|
16
|
+
uv run mypy src # type check
|
|
17
|
+
uv run pytest -v # tests
|
|
18
|
+
uv run python tools/api_diff.py # stability manifest gate
|
|
19
|
+
uv run lint-imports --config tools/importlinter.cfg # layer DAG gate
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Layer DAG rules
|
|
23
|
+
|
|
24
|
+
The SDK has a strict layer hierarchy — see [docs/architecture/layers.md](docs/architecture/layers.md).
|
|
25
|
+
|
|
26
|
+
**Hard rules:**
|
|
27
|
+
- `_transport` must not import `client`, `paywall`, `adapters`, or `operations`
|
|
28
|
+
- `operations` must not import `paywall` or `adapters`
|
|
29
|
+
- `client` must not import `adapters` or `experimental`
|
|
30
|
+
|
|
31
|
+
CI fails on violations via `import-linter`.
|
|
32
|
+
|
|
33
|
+
## PR checklist
|
|
34
|
+
|
|
35
|
+
- [ ] Tests added for new behavior
|
|
36
|
+
- [ ] `CHANGELOG.md` updated
|
|
37
|
+
- [ ] `docs/` updated if public API changed
|
|
38
|
+
- [ ] Layer DAG respected (`uv run lint-imports` passes)
|
|
39
|
+
- [ ] Stability manifest updated if new public exports added
|
|
40
|
+
|
|
41
|
+
## Commit style
|
|
42
|
+
|
|
43
|
+
[Conventional Commits](https://www.conventionalcommits.org/):
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
feat: add AsyncPaywall class
|
|
47
|
+
fix(webhooks): set replay_ttl_seconds default correctly
|
|
48
|
+
refactor: extract _transport package
|
|
49
|
+
docs: update README with MCP quickstart
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Adding a new resource namespace
|
|
53
|
+
|
|
54
|
+
1. Add `OpSpec` entries to `src/solvapay/operations/_registry.py`
|
|
55
|
+
2. Create `src/solvapay/operations/<resource>.py` with sync + async methods
|
|
56
|
+
3. Wire namespace attr in `SolvaPay.__init__` and `AsyncSolvaPay.__init__`
|
|
57
|
+
4. Add deprecated flat shim in `client.py` / `_async_client.py`
|
|
58
|
+
5. Tests in `tests/operations/`
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: solvapay-python
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: Community Python SDK for SolvaPay (agent-native payment rails)
|
|
5
|
+
Project-URL: Homepage, https://github.com/dhruv-sanan/solvapay-python
|
|
6
|
+
Project-URL: Issues, https://github.com/dhruv-sanan/solvapay-python/issues
|
|
7
|
+
Project-URL: Official TS SDK, https://github.com/solvapay/solvapay-sdk
|
|
8
|
+
Author: Dhruv Sanan
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,fintech,mcp,payments,solvapay
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: httpx>=0.27
|
|
25
|
+
Requires-Dist: pydantic<2.13,>=2.6
|
|
26
|
+
Provides-Extra: fastapi
|
|
27
|
+
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
|
|
28
|
+
Provides-Extra: langchain
|
|
29
|
+
Requires-Dist: langchain-core<0.4,>=0.3; extra == 'langchain'
|
|
30
|
+
Provides-Extra: mcp
|
|
31
|
+
Requires-Dist: fastmcp<0.5,>=0.4; extra == 'mcp'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# solvapay-python
|
|
35
|
+
|
|
36
|
+
Community Python SDK for [SolvaPay](https://solvapay.com) — agentic payment rails.
|
|
37
|
+
`pip install solvapay-python` · Python 3.10+ · Fully typed (py.typed) · [PyPI](https://pypi.org/project/solvapay-python/)
|
|
38
|
+
|
|
39
|
+
[](https://github.com/dhruv-sanan/solvapay-python/actions/workflows/ci.yml)
|
|
40
|
+
[](https://pypi.org/project/solvapay-python/)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## One tool. Three runtimes. One paywall.
|
|
45
|
+
|
|
46
|
+
`@payable_tool` stamps a function with payment metadata once. Any framework reads it — no per-framework wiring.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# Define once
|
|
50
|
+
from solvapay.adapters.mcp import payable_tool
|
|
51
|
+
|
|
52
|
+
@payable_tool(product="prd_search")
|
|
53
|
+
def web_search(*, customer_ref: str, query: str) -> list[str]:
|
|
54
|
+
"""Search the web."""
|
|
55
|
+
return do_real_search(query)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Run on MCP (Claude Desktop / Claude Code)
|
|
60
|
+
from solvapay.adapters.mcp import register_payable_tool_fastmcp
|
|
61
|
+
from fastmcp import FastMCP
|
|
62
|
+
|
|
63
|
+
mcp = FastMCP("My App")
|
|
64
|
+
register_payable_tool_fastmcp(mcp, web_search)
|
|
65
|
+
mcp.run()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
# Run on LangChain
|
|
70
|
+
from solvapay.adapters.langchain import monetize_tool
|
|
71
|
+
|
|
72
|
+
paid = monetize_tool(web_search, product="prd_search")
|
|
73
|
+
# Blocked callers get {"paywall_required": True, "checkout_url": "..."}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# Run raw async
|
|
78
|
+
async with AsyncSolvaPay() as sv:
|
|
79
|
+
limits = await sv.limits.acheck(customer_ref="cus_123", product_ref="prd_search")
|
|
80
|
+
if limits.within_limits:
|
|
81
|
+
result = await web_search(customer_ref="cus_123", query="hello")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`pip install 'solvapay-python[mcp]'` · `pip install 'solvapay-python[langchain]'`
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quickstart
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install solvapay-python
|
|
92
|
+
export SOLVAPAY_SECRET_KEY=sk_...
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from solvapay import SolvaPay
|
|
97
|
+
|
|
98
|
+
sv = SolvaPay()
|
|
99
|
+
|
|
100
|
+
# Ensure customer exists
|
|
101
|
+
customer_ref = sv.customers.ensure("user_alice")
|
|
102
|
+
|
|
103
|
+
# Create checkout session
|
|
104
|
+
session = sv.checkout.create_session(customer_ref=customer_ref, product_ref="prd_0QKI8NHF")
|
|
105
|
+
print(session.checkout_url)
|
|
106
|
+
|
|
107
|
+
# Check limits and track usage
|
|
108
|
+
limits = sv.limits.check(customer_ref=customer_ref, product_ref="prd_0QKI8NHF")
|
|
109
|
+
if limits.within_limits:
|
|
110
|
+
sv.usage.track(
|
|
111
|
+
customer_ref=customer_ref,
|
|
112
|
+
product_ref="prd_0QKI8NHF",
|
|
113
|
+
meter_name="requests",
|
|
114
|
+
units=1.0,
|
|
115
|
+
idempotency_key="req_abc123", # idempotent — safe to retry
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Stable API
|
|
122
|
+
|
|
123
|
+
| Namespace | Sync | Async |
|
|
124
|
+
|-----------|------|-------|
|
|
125
|
+
| `sv.customers` | `ensure`, `get`, `update`, `balance` | `aensure`, `aget`, `aupdate`, `abalance` |
|
|
126
|
+
| `sv.checkout` | `create_session` | `acreate_session` |
|
|
127
|
+
| `sv.limits` | `check` | `acheck` |
|
|
128
|
+
| `sv.purchases` | `cancel`, `reactivate` | `acancel`, `areactivate` |
|
|
129
|
+
| `sv.usage` | `track` | `atrack` |
|
|
130
|
+
| `sv.products` | `list`, `get`, `create`, `delete`, `clone` | `a` prefix on each |
|
|
131
|
+
| `sv.plans` | `list`, `create`, `update`, `delete` | `a` prefix on each |
|
|
132
|
+
| `sv.merchant` | `get`, `get_platform_config` | `aget`, `aget_platform_config` |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Idempotency
|
|
137
|
+
|
|
138
|
+
All mutating ops accept `idempotency_key`. Use `solvapay.idempotency.from_payload` to derive deterministic keys from request content:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from solvapay.idempotency import from_payload
|
|
142
|
+
|
|
143
|
+
key = from_payload("track_usage", customer_ref, product_ref, "requests", units)
|
|
144
|
+
sv.usage.track(..., idempotency_key=key) # retry-safe
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Retried POSTs **must reuse the same key**. Key should change only when the logical request changes.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Errors and retries
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from solvapay import (
|
|
155
|
+
AuthenticationError, PermissionError, NotFoundError,
|
|
156
|
+
RateLimitError, InvalidRequestError, APIServerError,
|
|
157
|
+
APIConnectionError, APITimeoutError, SolvaPayError,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
sv.customers.ensure("cus_123")
|
|
162
|
+
except AuthenticationError:
|
|
163
|
+
... # 401 — bad key
|
|
164
|
+
except RateLimitError as e:
|
|
165
|
+
... # 429 — retry after e.retry_after seconds
|
|
166
|
+
except APIConnectionError:
|
|
167
|
+
... # network failure
|
|
168
|
+
except APITimeoutError:
|
|
169
|
+
... # request timed out (default 30s)
|
|
170
|
+
except SolvaPayError as e:
|
|
171
|
+
... # catch-all
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
No built-in retries by design. Layer `tenacity` manually. `solvapay[retry]` RetryTransport ships in v0.9.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Webhooks
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
import os
|
|
182
|
+
from solvapay.webhooks import WebhookPipeline
|
|
183
|
+
|
|
184
|
+
pipeline = WebhookPipeline(
|
|
185
|
+
[os.environ["SOLVAPAY_WEBHOOK_SECRET"]],
|
|
186
|
+
max_clock_skew_seconds=300,
|
|
187
|
+
replay_ttl_seconds=600,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
envelope = pipeline.process(body=request.body, signature=request.headers["sv-signature"])
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Typed events** — discriminated union over 13 event types:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from solvapay import WebhookEvent, PurchaseCreated, PaymentSucceeded
|
|
197
|
+
from pydantic import TypeAdapter
|
|
198
|
+
|
|
199
|
+
event = TypeAdapter(WebhookEvent).validate_python(envelope.event)
|
|
200
|
+
|
|
201
|
+
match event:
|
|
202
|
+
case PurchaseCreated():
|
|
203
|
+
print(f"New purchase: {event.data['purchaseRef']}")
|
|
204
|
+
case PaymentSucceeded():
|
|
205
|
+
print(f"Payment: {event.data['amount']}")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Paywall decorator
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from solvapay.paywall import require, PaywallRequired
|
|
214
|
+
|
|
215
|
+
@require(product="prd_0QKI8NHF", client=sv)
|
|
216
|
+
def run_query(*, customer_ref: str, query: str) -> str:
|
|
217
|
+
return expensive_query(query)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
result = run_query(customer_ref="cus_123", query="hello")
|
|
221
|
+
except PaywallRequired as e:
|
|
222
|
+
print(f"Upgrade at: {e.checkout_url}")
|
|
223
|
+
if e.checkout_mint_error:
|
|
224
|
+
print(f"Could not auto-mint checkout URL: {e.checkout_mint_error}")
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Async version: `@require_async`. For MCP/LangChain: `@payable_tool` (see above).
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Async
|
|
232
|
+
|
|
233
|
+
`AsyncSolvaPay` is the supported async pattern. Always use `async with` — it guarantees `aclose()`:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from solvapay import AsyncSolvaPay
|
|
237
|
+
|
|
238
|
+
async with AsyncSolvaPay() as sv:
|
|
239
|
+
customer_ref = await sv.customers.aensure("user_alice")
|
|
240
|
+
limits = await sv.limits.acheck(customer_ref=customer_ref, product_ref="prd_0QKI8NHF")
|
|
241
|
+
if limits.within_limits:
|
|
242
|
+
await sv.usage.atrack(
|
|
243
|
+
customer_ref=customer_ref,
|
|
244
|
+
product_ref="prd_0QKI8NHF",
|
|
245
|
+
meter_name="requests",
|
|
246
|
+
units=1.0,
|
|
247
|
+
)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Migrating from v0.7.x
|
|
253
|
+
|
|
254
|
+
Flat methods still work but emit `DeprecationWarning`. Removed in v2.0.
|
|
255
|
+
|
|
256
|
+
| v0.7.x | v0.8+ |
|
|
257
|
+
|--------|-------|
|
|
258
|
+
| `sv.ensure_customer(ref)` | `sv.customers.ensure(ref)` |
|
|
259
|
+
| `sv.get_customer(ref)` | `sv.customers.get(ref)` |
|
|
260
|
+
| `sv.check_limits(...)` | `sv.limits.check(...)` |
|
|
261
|
+
| `sv.track_usage(...)` | `sv.usage.track(...)` |
|
|
262
|
+
| `sv.create_checkout_session(...)` | `sv.checkout.create_session(...)` |
|
|
263
|
+
| `sv.cancel_purchase(ref)` | `sv.purchases.cancel(ref)` |
|
|
264
|
+
| `sv.reactivate_purchase(ref)` | `sv.purchases.reactivate(ref)` |
|
|
265
|
+
| `sv.get_customer_balance(ref)` | `sv.customers.balance(ref)` |
|
|
266
|
+
| `sv.list_products()` | `sv.products.list()` |
|
|
267
|
+
| `sv.create_plan(prd, ...)` | `sv.plans.create(prd, ...)` |
|
|
268
|
+
| `sv.get_merchant()` | `sv.merchant.get()` |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Installation
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
pip install solvapay-python # core
|
|
276
|
+
pip install 'solvapay-python[mcp]' # + FastMCP adapter (FastMCP ≥0.4)
|
|
277
|
+
pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-core ≥0.3)
|
|
278
|
+
pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Environment variables
|
|
282
|
+
|
|
283
|
+
| Variable | Required | Default |
|
|
284
|
+
|----------|----------|---------|
|
|
285
|
+
| `SOLVAPAY_SECRET_KEY` | Yes | — |
|
|
286
|
+
| `SOLVAPAY_API_BASE_URL` | No | `https://api.solvapay.com` |
|
|
287
|
+
| `SOLVAPAY_WEBHOOK_SECRET` | For webhooks | — |
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Examples
|
|
292
|
+
|
|
293
|
+
| Path | What |
|
|
294
|
+
|------|------|
|
|
295
|
+
| `examples/multi-framework-paywall/` | One `@payable_tool` → FastMCP + LangChain + raw async |
|
|
296
|
+
| `examples/marketplace/` | Streamlit AI-agent marketplace — real SolvaPay sandbox + Gemini LLM |
|
|
297
|
+
| `examples/fastmcp-paywall/` | FastMCP server gated by `@paywall.require` |
|
|
298
|
+
| `examples/langchain-paywall/` | LangChain agent with `monetize_tool` |
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Roadmap
|
|
303
|
+
|
|
304
|
+
| Version | Theme |
|
|
305
|
+
|---------|-------|
|
|
306
|
+
| v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
|
|
307
|
+
| v0.9 | Production polish — API-version pinning, idempotency TTL, `RetryTransport`, `RecordingTransport`, contract tests, lint automation, doc site (MkDocs Material), supply-chain hygiene |
|
|
308
|
+
| v1.0 | Gated on founder signal — OpenAPI-generated models, full secret rotation, production hardening |
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Status
|
|
313
|
+
|
|
314
|
+
**v0.8.0** — V1 architecture spine + AI-agent moat. `mypy --strict` clean (43 files). 261 tests. 89% line coverage.
|
|
315
|
+
Community SDK, not official. Proposal: [solvapay/solvapay-sdk#187](https://github.com/solvapay/solvapay-sdk/issues/187).
|