solvapay-python 0.9.1__tar.gz → 0.9.3__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.9.1 → solvapay_python-0.9.3}/.github/workflows/ci.yml +11 -9
- solvapay_python-0.9.3/.osv-scanner.toml +6 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CHANGELOG.md +30 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/PKG-INFO +13 -3
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/README.md +10 -2
- solvapay_python-0.9.3/docs/architecture/cold-start.md +70 -0
- solvapay_python-0.9.3/docs/architecture/layers.md +84 -0
- solvapay_python-0.9.3/docs/troubleshooting.md +225 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/mkdocs.yml +4 -1
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/pyproject.toml +12 -2
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/__init__.py +29 -2
- solvapay_python-0.9.3/tests/_baselines/cold_import.json +1 -0
- solvapay_python-0.9.3/tests/benchmarks/test_benchmarks.py +46 -0
- solvapay_python-0.9.3/tests/test_cold_import.py +92 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_invariants.py +32 -0
- solvapay_python-0.9.3/tests/test_layer_dag.py +35 -0
- solvapay_python-0.9.3/tests/webhooks/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tools/api_baseline.json +4 -0
- solvapay_python-0.9.3/tools/importlinter.cfg +32 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/uv.lock +44 -5
- solvapay_python-0.9.1/docs/architecture/layers.md +0 -46
- solvapay_python-0.9.1/tools/importlinter.cfg +0 -42
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/ISSUE_TEMPLATE/question.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/dependabot.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/contract.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/docs.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.github/workflows/publish.yml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.gitignore +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/.python-version +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CODEOWNERS +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CODE_OF_CONDUCT.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/CONTRIBUTING.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/LICENSE +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/SECURITY.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/SUPPORT.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/assets/agent-marketplace.png +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/changelog.d/README.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/errors.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/guides/fastapi.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/guides/langchain.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/guides/mcp.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/idempotency.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/index.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/migration.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/client.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/exceptions.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/models.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/paywall.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/reference/webhooks.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/rfcs/0001-spending-policy.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/rfcs/0002-openapi-investigation.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/docs/webhooks.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/.env.example +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/.gitignore +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/README.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/claim.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/server.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/fastmcp-paywall/uv.lock +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/.env.example +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/.gitignore +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/README.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/agent.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/langchain-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/.env.example +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/.gitignore +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/.streamlit/config.toml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/PLAN.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/README.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/agents.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/app.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/demo_customers.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/requirements.txt +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/sdk_gateway.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/marketplace/ui_components.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/.env.example +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/.gitignore +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/README.md +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/agent_langchain.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/model.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/pyproject.toml +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/script_async.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/server_mcp.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/tool.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/examples/multi-framework-paywall/uv.lock +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_async_client.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_config.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_http.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_stability.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/_recipe.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/httpx_transport.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/_transport/middleware.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/asgi.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/langchain.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/adapters/mcp.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/client.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/events.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/exceptions.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/fastapi.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/idempotency.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/langchain.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/models.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/_registry.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/checkout.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/customers.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/limits.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/merchant.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/plans.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/products.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/purchases.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/operations/usage.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/core.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/decorators.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/meta.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/policy.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/resolvers.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall/state.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/paywall_state.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/py.typed +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/envelope.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/pipeline.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/replay.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/rotation.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/sign.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/src/solvapay/webhooks/verify.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_stability/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_stability/test_stable_returns_identity.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_aclose_cascade.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_error_wrapping.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_headers_case_insensitive.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_middleware_composition.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_protocol_conformance.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_recording_transport.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/_transport/test_retry_transport.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/test_asgi.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/test_langchain_protocol.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/adapters/test_mcp.py +0 -0
- {solvapay_python-0.9.1/tests/contract → solvapay_python-0.9.3/tests/benchmarks}/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/conftest.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/contract/README.md +0 -0
- {solvapay_python-0.9.1/tests/operations → solvapay_python-0.9.3/tests/contract}/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/contract/cassettes/.gitkeep +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/contract/test_contract_ops.py +0 -0
- {solvapay_python-0.9.1/tests/paywall → solvapay_python-0.9.3/tests/operations}/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/operations/test_namespace_api.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/operations/test_path_interpolation.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/operations/test_retry_safety_enum.py +0 -0
- {solvapay_python-0.9.1/tests/webhooks → solvapay_python-0.9.3/tests/paywall}/__init__.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_checkout_mint_error_surfaces.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_payable_tool_meta.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_resolvers.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/paywall/test_split_classes.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_admin.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_api_version.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_async_client.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_checkout.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_config.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_customer.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_errors.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_http.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_idempotency.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_langchain.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_lifecycle.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_limits.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_packaging.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_paywall.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_paywall_state.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_redaction.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_webhook_events.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_webhook_timing.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/test_webhooks.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_async_cache.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_clock_skew_vs_replay_ttl.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_rotation.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_seen_cache_atomic.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_sign.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tests/webhooks/test_webhook_pipeline.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tools/api_diff.py +0 -0
- {solvapay_python-0.9.1 → solvapay_python-0.9.3}/tools/lint_invariants.py +0 -0
|
@@ -19,15 +19,15 @@ jobs:
|
|
|
19
19
|
- run: uv sync --all-extras --dev
|
|
20
20
|
- name: bandit (high-severity, high-confidence only)
|
|
21
21
|
run: uv run bandit -r src/ -lll
|
|
22
|
-
- name: Install osv-scanner
|
|
23
|
-
run: |
|
|
24
|
-
curl -fsSL -o /tmp/osv-scanner.tar.gz \
|
|
25
|
-
https://github.com/google/osv-scanner/releases/download/v1.9.2/osv-scanner_linux_amd64.tar.gz
|
|
26
|
-
mkdir -p /tmp/osv && tar -xzf /tmp/osv-scanner.tar.gz -C /tmp/osv
|
|
27
|
-
sudo mv /tmp/osv/osv-scanner /usr/local/bin/osv-scanner
|
|
28
|
-
osv-scanner --version
|
|
29
22
|
- name: osv-scanner (lockfile vuln DB cross-check)
|
|
30
|
-
|
|
23
|
+
# PYSEC-2026-196: vuln in the GitHub runner's bundled pip, not in any
|
|
24
|
+
# solvapay-python dependency (pip is pulled into uv.lock transitively
|
|
25
|
+
# by build tooling). Ignore — runner image hygiene is GitHub's job.
|
|
26
|
+
run: |
|
|
27
|
+
docker run --rm -v "${PWD}:/src" -w /src \
|
|
28
|
+
ghcr.io/google/osv-scanner:latest \
|
|
29
|
+
scan source --lockfile=/src/uv.lock \
|
|
30
|
+
--config=/src/.osv-scanner.toml
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
test:
|
|
@@ -66,7 +66,9 @@ jobs:
|
|
|
66
66
|
- run: uv run lint-imports --config tools/importlinter.cfg
|
|
67
67
|
- run: uv run python tools/lint_invariants.py
|
|
68
68
|
- name: pip-audit
|
|
69
|
-
|
|
69
|
+
# PYSEC-2026-196: vuln in the GitHub runner's bundled pip (26.1.1, fixed in 26.1.2).
|
|
70
|
+
# Not a vuln in any solvapay-python dependency — runner-side hygiene only.
|
|
71
|
+
run: uv run pip-audit --ignore-vuln PYSEC-2026-196
|
|
70
72
|
- name: Check changelog fragment on PRs touching src/
|
|
71
73
|
if: github.event_name == 'pull_request'
|
|
72
74
|
shell: bash
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
[[IgnoredVulns]]
|
|
2
|
+
id = "PYSEC-2026-196"
|
|
3
|
+
# pip 26.1.1 vuln, fixed in 26.1.2. Pip lands in uv.lock transitively via build
|
|
4
|
+
# tooling on the GitHub runner — not a solvapay-python runtime/optional dep.
|
|
5
|
+
# Runner image hygiene is GitHub's responsibility.
|
|
6
|
+
reason = "Runner-side pip — not a project dependency"
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.3] — 2026-06-07
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- **Layer DAG now hard-enforced via single layered `import-linter` contract.** `tools/importlinter.cfg` replaces three sparse `forbidden` contracts with one `type = layers` contract covering the full HLD §V1.1 DAG (`_transport → _http → models → operations → client → paywall|webhooks → adapters → fastapi|langchain`). Closes the L2 `models` purity gap and the L5 `paywall`/`webhooks` framework-neutrality gap. `exhaustive = true` flags any new top-level module that ships without an explicit layer assignment. `ignore_type_checking_imports = true` strips `TYPE_CHECKING` blocks from the graph so cross-layer type hints do not trip the gate. CI-only change; no public-API impact.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.9.2] — 2026-06-06
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- ``solvapay.sign_webhook`` promoted to top-level namespace (alongside ``verify_webhook``), closing the inconsistency flagged in v0.9.0 deviation #3. The existing ``solvapay.webhooks.sign_webhook`` path is unchanged.
|
|
15
|
+
|
|
16
|
+
Cold-import baseline harness at ``tests/test_cold_import.py``: measures ``python -X importtime`` cumulative time and asserts within 1.5x of a committed baseline. The hard <200 ms CI gate is a v1.0 feature (HLD §V1.20).
|
|
17
|
+
|
|
18
|
+
Microbenchmark harness at ``tests/benchmarks/``: measures ``SolvaPay()`` construction, ``verify_webhook`` per-call, and ``from_payload`` derivation. Opt-in via ``solvapay[bench]`` extra. Excluded from default pytest run.
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
- ``docs/troubleshooting.md``: top-10 user errors with symptom, cause, and fix. Covers missing env vars, async-in-sync deadlock, missing extras, contract test skips, Windows paths, deprecation warnings, paywall checkout URL, ``verify_webhook`` errors, mypy mock patterns, and PyPI dist-name confusion.
|
|
23
|
+
|
|
24
|
+
``docs/architecture/cold-start.md``: explains the ``python -X importtime`` baseline harness, PEP 562 lazy adapter import pattern, and the v1.0 <150 ms cold-start budget (HLD §V1.20).
|
|
25
|
+
|
|
26
|
+
### Performance
|
|
27
|
+
|
|
28
|
+
- PEP 562 lazy adapter imports: bare ``import solvapay`` no longer loads ``fastmcp``, ``langchain-core``, or ``fastapi``. Framework adapter modules load on first attribute access only. ``solvapay.adapters`` is accessible via ``__getattr__`` and appears in ``dir(solvapay)``.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
3
33
|
## [0.9.1] — 2026-06-05
|
|
4
34
|
|
|
5
35
|
Security & supply-chain quality patch (no public API change).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solvapay-python
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
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
|
|
@@ -24,6 +24,8 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Requires-Dist: httpx<0.29,>=0.27
|
|
25
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
|
|
@@ -206,10 +208,11 @@ envelope = pipeline.process(body=request.body, signature=request.headers["sv-sig
|
|
|
206
208
|
pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
|
|
207
209
|
```
|
|
208
210
|
|
|
209
|
-
**Sign a webhook** (testing / outbound fanout):
|
|
211
|
+
**Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
|
|
210
212
|
|
|
211
213
|
```python
|
|
212
|
-
from solvapay
|
|
214
|
+
from solvapay import sign_webhook # top-level (v0.9.2+)
|
|
215
|
+
# from solvapay.webhooks import sign_webhook # subpackage path also works
|
|
213
216
|
|
|
214
217
|
header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
|
|
215
218
|
# → "t=1716470000,v1=abc123..."
|
|
@@ -311,6 +314,7 @@ pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-c
|
|
|
311
314
|
pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
|
|
312
315
|
pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
|
|
313
316
|
pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
|
|
317
|
+
pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
|
|
314
318
|
```
|
|
315
319
|
|
|
316
320
|
## Environment variables
|
|
@@ -354,12 +358,18 @@ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
|
|
|
354
358
|
| v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
|
|
355
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 |
|
|
356
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
|
+
| v0.9.3 ✅ | Layer DAG hard-enforcement — single `type = layers` `import-linter` contract replaces three sparse `forbidden` contracts; closes the L2 `models` purity gap and the L5 `paywall`/`webhooks` framework-neutrality gap; `exhaustive = true` flags any unlayered top-level module; `ignore_type_checking_imports = true` keeps cross-layer type hints lint-clean |
|
|
357
363
|
| v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
|
|
358
364
|
|
|
359
365
|
---
|
|
360
366
|
|
|
361
367
|
## Status
|
|
362
368
|
|
|
369
|
+
**v0.9.3** — Layer DAG hard-enforcement quality patch. `tools/importlinter.cfg` now ships a single `type = layers` contract covering the full HLD §V1.1 DAG (`_transport → _http → models → operations → client → paywall|webhooks → adapters → fastapi|langchain`), replacing three sparse `forbidden` contracts. `exhaustive = true` flags any new top-level module that ships without an explicit layer assignment; `ignore_type_checking_imports = true` strips `TYPE_CHECKING` blocks from the dependency graph so cross-layer type hints do not trip the gate. New `tests/test_layer_dag.py` shells the contract from pytest as a belt-and-suspenders gate. `docs/architecture/layers.md` refreshed. CI-only change; no public-API impact. 306 tests. 87% line coverage. `mypy --strict` clean (45 files).
|
|
370
|
+
|
|
371
|
+
**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).
|
|
372
|
+
|
|
363
373
|
**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.
|
|
364
374
|
|
|
365
375
|
**Supply chain & security posture:**
|
|
@@ -170,10 +170,11 @@ envelope = pipeline.process(body=request.body, signature=request.headers["sv-sig
|
|
|
170
170
|
pipeline = WebhookPipeline(["whsec_new...", "whsec_old..."])
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
**Sign a webhook** (testing / outbound fanout):
|
|
173
|
+
**Sign a webhook** (testing / outbound fanout) — available at the top level since v0.9.2:
|
|
174
174
|
|
|
175
175
|
```python
|
|
176
|
-
from solvapay
|
|
176
|
+
from solvapay import sign_webhook # top-level (v0.9.2+)
|
|
177
|
+
# from solvapay.webhooks import sign_webhook # subpackage path also works
|
|
177
178
|
|
|
178
179
|
header = sign_webhook(body=b'{"type":"purchase.created"}', secret="whsec_...")
|
|
179
180
|
# → "t=1716470000,v1=abc123..."
|
|
@@ -275,6 +276,7 @@ pip install 'solvapay-python[langchain]' # + LangChain adapter (langchain-c
|
|
|
275
276
|
pip install 'solvapay-python[fastapi]' # + FastAPI webhook router
|
|
276
277
|
pip install 'solvapay-python[asgi]' # + raw ASGI webhook adapter (no extra deps)
|
|
277
278
|
pip install 'solvapay-python[retry]' # + RetryTransport (tenacity)
|
|
279
|
+
pip install 'solvapay-python[bench]' # + pytest-benchmark (dev/perf testing)
|
|
278
280
|
```
|
|
279
281
|
|
|
280
282
|
## Environment variables
|
|
@@ -318,12 +320,18 @@ Default is `"2026-05-22"` (v0.9 ship date). Bump only on major SDK versions.
|
|
|
318
320
|
| v0.8 ✅ | V1 architecture spine — Transport kernel, OpSpec registry, paywall/webhook packages, `@payable_tool`, stability manifest, layer DAG CI gate |
|
|
319
321
|
| 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 |
|
|
320
322
|
| 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 |
|
|
323
|
+
| 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 |
|
|
324
|
+
| v0.9.3 ✅ | Layer DAG hard-enforcement — single `type = layers` `import-linter` contract replaces three sparse `forbidden` contracts; closes the L2 `models` purity gap and the L5 `paywall`/`webhooks` framework-neutrality gap; `exhaustive = true` flags any unlayered top-level module; `ignore_type_checking_imports = true` keeps cross-layer type hints lint-clean |
|
|
321
325
|
| v1.0 | Gated on founder signal — OpenAPI-generated models, WSGI/Lambda adapters, V2 planning |
|
|
322
326
|
|
|
323
327
|
---
|
|
324
328
|
|
|
325
329
|
## Status
|
|
326
330
|
|
|
331
|
+
**v0.9.3** — Layer DAG hard-enforcement quality patch. `tools/importlinter.cfg` now ships a single `type = layers` contract covering the full HLD §V1.1 DAG (`_transport → _http → models → operations → client → paywall|webhooks → adapters → fastapi|langchain`), replacing three sparse `forbidden` contracts. `exhaustive = true` flags any new top-level module that ships without an explicit layer assignment; `ignore_type_checking_imports = true` strips `TYPE_CHECKING` blocks from the dependency graph so cross-layer type hints do not trip the gate. New `tests/test_layer_dag.py` shells the contract from pytest as a belt-and-suspenders gate. `docs/architecture/layers.md` refreshed. CI-only change; no public-API impact. 306 tests. 87% line coverage. `mypy --strict` clean (45 files).
|
|
332
|
+
|
|
333
|
+
**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).
|
|
334
|
+
|
|
327
335
|
**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.
|
|
328
336
|
|
|
329
337
|
**Supply chain & security posture:**
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Cold-Start Performance
|
|
2
|
+
|
|
3
|
+
## What we measure
|
|
4
|
+
|
|
5
|
+
`python -X importtime -c "import solvapay"` prints a line per imported module:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
import time: <self_µs> | <cumulative_µs> | <module_name>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The `cumulative_µs` column for the root `solvapay` module is the total cold-import cost — the number we track.
|
|
12
|
+
|
|
13
|
+
## The baseline harness (`tests/test_cold_import.py`)
|
|
14
|
+
|
|
15
|
+
Introduced in v0.9.2, `tests/test_cold_import.py` runs the above command via `subprocess` and:
|
|
16
|
+
|
|
17
|
+
1. **First run** — writes `tests/_baselines/cold_import.json` with the measured value.
|
|
18
|
+
2. **Subsequent runs** — asserts the current measurement is within **1.5×** of the baseline.
|
|
19
|
+
|
|
20
|
+
The 1.5× regression factor is intentionally loose — it catches gross regressions (importing a new heavy dependency eagerly) without failing on normal M-series vs CI machine variance.
|
|
21
|
+
|
|
22
|
+
The committed baseline is re-measured and updated whenever a structural change affects import cost (e.g. lazy adapter imports in v0.9.2).
|
|
23
|
+
|
|
24
|
+
### Current baseline
|
|
25
|
+
|
|
26
|
+
| Version | M-series (macOS) | What changed |
|
|
27
|
+
|---------|-----------------|--------------|
|
|
28
|
+
| v0.9.1 | ~88 ms | Initial measurement |
|
|
29
|
+
| v0.9.2 | ~100 ms | PEP 562 `__getattr__` + `__dir__` added; adapters lazy |
|
|
30
|
+
|
|
31
|
+
## PEP 562 lazy adapter imports
|
|
32
|
+
|
|
33
|
+
Adapter modules (`solvapay.adapters.mcp`, `solvapay.adapters.langchain`, etc.) depend on optional heavy packages — `fastmcp`, `langchain-core`, `fastapi`. These are only installed when the user runs `pip install solvapay-python[mcp]` etc.
|
|
34
|
+
|
|
35
|
+
Before v0.9.2, accessing `solvapay.adapters` required an explicit subpackage import. In v0.9.2, `solvapay/__init__.py` defines a PEP 562 `__getattr__` that loads `solvapay.adapters` lazily on first attribute access:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# In solvapay/__init__.py
|
|
39
|
+
_LAZY_MODULES = {"adapters": "solvapay.adapters"}
|
|
40
|
+
|
|
41
|
+
def __getattr__(name: str) -> object:
|
|
42
|
+
if name in _LAZY_MODULES:
|
|
43
|
+
import importlib
|
|
44
|
+
mod = importlib.import_module(_LAZY_MODULES[name])
|
|
45
|
+
globals()[name] = mod # cache for subsequent accesses
|
|
46
|
+
return mod
|
|
47
|
+
raise AttributeError(f"module 'solvapay' has no attribute {name!r}")
|
|
48
|
+
|
|
49
|
+
def __dir__() -> list[str]:
|
|
50
|
+
return sorted(set(globals()) | set(_LAZY_MODULES))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This ensures:
|
|
54
|
+
- `import solvapay` — **zero adapter cost** regardless of which extras are installed.
|
|
55
|
+
- `solvapay.adapters` — triggers a single load on first access, cached thereafter.
|
|
56
|
+
- `from solvapay.adapters import mcp` — works via Python's normal subpackage import path (unaffected by `__getattr__`).
|
|
57
|
+
- `dir(solvapay)` — lists `"adapters"` per PEP 562 convention.
|
|
58
|
+
|
|
59
|
+
## v1.0 hard budget (HLD §V1.20)
|
|
60
|
+
|
|
61
|
+
v0.9.2 ships **measurement only**. The hard gate is a v1.0 feature:
|
|
62
|
+
|
|
63
|
+
| Environment | Budget |
|
|
64
|
+
|-------------|--------|
|
|
65
|
+
| M-series Mac | < 150 ms |
|
|
66
|
+
| x86 CI | < 300 ms |
|
|
67
|
+
|
|
68
|
+
v1.0 achieves this primarily via **`@cached_property` lazy namespace materialization** — `SolvaPay.__init__` will no longer eagerly construct `customers`, `checkout`, `limits`, etc. Each namespace is constructed on first access (`sv.customers.ensure(...)`) and cached as an instance attribute. This eliminates the linear startup cost as V2/V3 expand the namespace count.
|
|
69
|
+
|
|
70
|
+
See `HLD.md §V1.3 RN1-v2` and `§V1.20` for the full contract.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Layer Architecture (HLD V1.1)
|
|
2
|
+
|
|
3
|
+
The SDK is organized into strict layers. Each layer may only import from layers below it.
|
|
4
|
+
Violations fail CI via `import-linter` (see `tools/importlinter.cfg`).
|
|
5
|
+
|
|
6
|
+
From **v0.9.3** the DAG is enforced as a single `type = layers` contract, replacing the
|
|
7
|
+
earlier three sparse `forbidden` contracts. `exhaustive = true` flags any new top-level
|
|
8
|
+
module that ships without an explicit layer assignment.
|
|
9
|
+
|
|
10
|
+
## Layer DAG
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
L7 solvapay.fastapi — Top-level adapter shims (re-export from L6.adapters.*)
|
|
14
|
+
solvapay.langchain
|
|
15
|
+
L6 solvapay.adapters — Framework adapters (MCP, LangChain, ASGI, FastAPI)
|
|
16
|
+
L5 solvapay.paywall — Paywall / AsyncPaywall primitives
|
|
17
|
+
solvapay.webhooks — Webhook verify + pipeline
|
|
18
|
+
L4 solvapay.client — SolvaPay / AsyncSolvaPay public facades
|
|
19
|
+
solvapay._async_client
|
|
20
|
+
L3 solvapay.operations — Resource namespace operations (sv.customers.ensure etc.)
|
|
21
|
+
L2 solvapay.models — Pydantic request/response models
|
|
22
|
+
L1 solvapay._http — HTTP-client wrapper around the transport kernel
|
|
23
|
+
L0 solvapay._transport — Transport Protocol + httpx impl + middleware
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
> **Note.** The HLD ships an aspirational L1 `_wire` slot that is currently unused.
|
|
27
|
+
> The shipped layout uses L1 for `_http` (the existing transport-kernel wrapper).
|
|
28
|
+
> When `_wire` ships, it will sit between `_transport` and `_http`.
|
|
29
|
+
|
|
30
|
+
## Allowed imports
|
|
31
|
+
|
|
32
|
+
A module at L_n may import from any module at L_(n-1) or below, plus the layer-independent
|
|
33
|
+
leaf modules (`exceptions`, `_stability`, `_config`, `idempotency`, `events`, `paywall_state`).
|
|
34
|
+
|
|
35
|
+
| Layer | May import |
|
|
36
|
+
|-------|------------|
|
|
37
|
+
| L0 `_transport` | stdlib, httpx, leaf modules |
|
|
38
|
+
| L1 `_http` | L0 + leaf modules |
|
|
39
|
+
| L2 `models` | stdlib, pydantic, leaf modules |
|
|
40
|
+
| L3 `operations` | L0–L2 + leaf modules |
|
|
41
|
+
| L4 `client` / `_async_client` | L0–L3 + leaf modules |
|
|
42
|
+
| L5 `paywall` / `webhooks` | L0–L4 + leaf modules |
|
|
43
|
+
| L6 `adapters` | L0–L5 + leaf modules |
|
|
44
|
+
| L7 `fastapi` / `langchain` (top-level shims) | L0–L6 + leaf modules |
|
|
45
|
+
| `tests/` | everything (permissive; tests live outside the `solvapay` container) |
|
|
46
|
+
|
|
47
|
+
Sibling modules on the same row (e.g. `paywall` ↔ `webhooks`, or `client` ↔ `_async_client`)
|
|
48
|
+
may not import each other. Each is independently constrained against the layers below.
|
|
49
|
+
|
|
50
|
+
## Why L7 = stability tier, not function (DG2 lock)
|
|
51
|
+
|
|
52
|
+
`solvapay.experimental` (reserved for v1.0) groups features by **stability**, not by
|
|
53
|
+
domain. A webhook adapter that is still unstable lives in L6 (adapters) while it's being
|
|
54
|
+
proven, then graduates to the canonical path once stable. This prevents the
|
|
55
|
+
`experimental` namespace from becoming a permanent home for half-finished features.
|
|
56
|
+
|
|
57
|
+
## Leaf modules (layer-independent)
|
|
58
|
+
|
|
59
|
+
These are listed in `exhaustive_ignores` in the contract because they have no upward edges
|
|
60
|
+
and no downward consumers beyond the boundary primitives:
|
|
61
|
+
|
|
62
|
+
- `solvapay.exceptions`
|
|
63
|
+
- `solvapay._stability`
|
|
64
|
+
- `solvapay._config`
|
|
65
|
+
- `solvapay.idempotency`
|
|
66
|
+
- `solvapay.events`
|
|
67
|
+
- `solvapay.paywall_state`
|
|
68
|
+
|
|
69
|
+
## TYPE_CHECKING imports
|
|
70
|
+
|
|
71
|
+
The contract sets `ignore_type_checking_imports = true`. Imports inside `if TYPE_CHECKING:`
|
|
72
|
+
blocks are stripped from the dependency graph, so cross-layer type hints (e.g.
|
|
73
|
+
`from solvapay.client import SolvaPay` for annotation purposes) do not trip the gate.
|
|
74
|
+
`mypy` still resolves these via the standard `from __future__ import annotations`
|
|
75
|
+
pattern enforced by `tools/lint_invariants.py`.
|
|
76
|
+
|
|
77
|
+
## Enforcement
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
~/.local/bin/uv run lint-imports --config tools/importlinter.cfg
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Runs in CI on every push and PR. Also wrapped by `tests/test_layer_dag.py` so the
|
|
84
|
+
contract is exercised by `pytest` for local feedback.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Common errors and how to fix them.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. `AuthenticationError` — API key not set
|
|
8
|
+
|
|
9
|
+
**Symptom:**
|
|
10
|
+
```
|
|
11
|
+
solvapay.exceptions.AuthenticationError: No API key provided.
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Cause:** `SOLVAPAY_SECRET_KEY` env var is not set and no `api_key=` argument was passed.
|
|
15
|
+
|
|
16
|
+
**Fix:**
|
|
17
|
+
```bash
|
|
18
|
+
export SOLVAPAY_SECRET_KEY=sk_sandbox_...
|
|
19
|
+
```
|
|
20
|
+
Or pass it explicitly:
|
|
21
|
+
```python
|
|
22
|
+
sv = SolvaPay(api_key="sk_sandbox_...")
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Async-in-sync deadlock
|
|
28
|
+
|
|
29
|
+
**Symptom:**
|
|
30
|
+
```
|
|
31
|
+
RuntimeError: cannot run event loop while another event loop is running
|
|
32
|
+
```
|
|
33
|
+
Or a call to `await sv.customers.ensure(...)` hangs indefinitely.
|
|
34
|
+
|
|
35
|
+
**Cause:** Mixing `AsyncSolvaPay` with sync framework code, or running async SDK methods from a Jupyter notebook without `await`.
|
|
36
|
+
|
|
37
|
+
**Fix:** Use `SolvaPay` (sync) for sync frameworks (FastAPI route handlers are async — use `AsyncSolvaPay` there). In Jupyter, `await` the call directly or use `asyncio.run()` in a standalone script.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
# FastAPI — use AsyncSolvaPay
|
|
41
|
+
@app.get("/check")
|
|
42
|
+
async def check(customer_ref: str):
|
|
43
|
+
async with AsyncSolvaPay() as sv:
|
|
44
|
+
result = await sv.limits.acheck(customer_ref=customer_ref, product_ref="prd_x")
|
|
45
|
+
return result
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 3. `ModuleNotFoundError` — missing optional extra
|
|
51
|
+
|
|
52
|
+
**Symptom:**
|
|
53
|
+
```
|
|
54
|
+
ModuleNotFoundError: No module named 'fastmcp'
|
|
55
|
+
ModuleNotFoundError: No module named 'langchain_core'
|
|
56
|
+
ModuleNotFoundError: No module named 'tenacity'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Cause:** Optional adapter or middleware not installed.
|
|
60
|
+
|
|
61
|
+
**Fix:** Install the relevant extra:
|
|
62
|
+
```bash
|
|
63
|
+
pip install solvapay-python[mcp] # fastmcp
|
|
64
|
+
pip install solvapay-python[langchain] # langchain-core
|
|
65
|
+
pip install solvapay-python[retry] # tenacity (RetryTransport)
|
|
66
|
+
pip install solvapay-python[fastapi] # fastapi
|
|
67
|
+
pip install solvapay-python[bench] # pytest-benchmark (dev only)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 4. Contract tests skip silently
|
|
73
|
+
|
|
74
|
+
**Symptom:**
|
|
75
|
+
```
|
|
76
|
+
3 skipped — SOLVAPAY_SANDBOX_KEY not set
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Cause:** Sandbox tests require a live sandbox key and are intentionally skipped in CI unless the secret is configured.
|
|
80
|
+
|
|
81
|
+
**Fix:** For local sandbox testing:
|
|
82
|
+
```bash
|
|
83
|
+
export SOLVAPAY_SANDBOX_KEY=sk_sandbox_...
|
|
84
|
+
export SOLVAPAY_TEST_CUSTOMER_REF=cus_...
|
|
85
|
+
export SOLVAPAY_TEST_PRODUCT_REF=prd_...
|
|
86
|
+
uv run pytest -m contract
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 5. Windows virtualenv / `uv` path issues
|
|
92
|
+
|
|
93
|
+
**Symptom:**
|
|
94
|
+
```
|
|
95
|
+
FileNotFoundError: [WinError 2] The system cannot find the file specified
|
|
96
|
+
```
|
|
97
|
+
Or `uv run` fails to find the installed package.
|
|
98
|
+
|
|
99
|
+
**Cause:** Windows uses different path separators and virtualenv activation conventions.
|
|
100
|
+
|
|
101
|
+
**Fix:** Use `uv` from the project root — it manages the `.venv` automatically:
|
|
102
|
+
```bat
|
|
103
|
+
cd path\to\solvapay-python
|
|
104
|
+
uv sync --all-extras --dev
|
|
105
|
+
uv run pytest
|
|
106
|
+
```
|
|
107
|
+
Do not manually activate the venv; let `uv run` handle it.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 6. `DeprecationWarning: Flat method deprecated` in test output
|
|
112
|
+
|
|
113
|
+
**Symptom:**
|
|
114
|
+
```
|
|
115
|
+
DeprecationWarning: Flat method deprecated. Use sv.customers.ensure(...) instead.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Cause:** Old flat-method API (e.g. `sv.ensure_customer(...)`) is still being called. These shims emit a `DeprecationWarning` on every call and will be removed in v2.0.
|
|
119
|
+
|
|
120
|
+
**Fix:** Migrate to the namespace API:
|
|
121
|
+
```python
|
|
122
|
+
# Old (deprecated)
|
|
123
|
+
sv.ensure_customer("ext_ref")
|
|
124
|
+
|
|
125
|
+
# New (stable)
|
|
126
|
+
sv.customers.ensure("ext_ref")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
To silence the warning in tests that still use the old path intentionally, add to `pyproject.toml`:
|
|
130
|
+
```toml
|
|
131
|
+
filterwarnings = ["ignore:Flat method deprecated:DeprecationWarning"]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 7. `PaywallRequired` raised with `checkout_url=None`
|
|
137
|
+
|
|
138
|
+
**Symptom:**
|
|
139
|
+
```python
|
|
140
|
+
except PaywallRequired as e:
|
|
141
|
+
print(e.checkout_url) # None — no redirect URL available
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Cause:** In v0.8/v0.9, the SDK does NOT automatically call `checkout.create_session()` on every gate hit. Auto-minting is triggered only when the `LimitResponse` from the server includes a `checkoutUrl` field.
|
|
145
|
+
|
|
146
|
+
**Fix:** Use `paywall_state.gate()` for a fully enriched decision, or construct the checkout URL yourself:
|
|
147
|
+
```python
|
|
148
|
+
from solvapay.paywall_state import gate
|
|
149
|
+
|
|
150
|
+
decision = gate(sv, customer_ref=ref, product_ref="prd_x")
|
|
151
|
+
if decision.state != PaywallState.OK:
|
|
152
|
+
redirect_to(decision.checkout_url)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Note: in v1.0 this behavior changes — see `HLD.md §V1.6 PW4`.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 8. `verify_webhook` raises `ValueError`
|
|
160
|
+
|
|
161
|
+
**Symptom:**
|
|
162
|
+
```
|
|
163
|
+
ValueError: No valid signature found
|
|
164
|
+
ValueError: Request timestamp too old
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Cause:**
|
|
168
|
+
- Wrong secret (key rotation in progress, or `SOLVAPAY_WEBHOOK_SECRET` not matching the SolvaPay dashboard secret).
|
|
169
|
+
- System clock drift exceeding `tolerance` (default 300 seconds).
|
|
170
|
+
|
|
171
|
+
**Fix:**
|
|
172
|
+
```python
|
|
173
|
+
# Check your secret matches the SolvaPay dashboard exactly.
|
|
174
|
+
verify_webhook(body=raw_body, signature=header, secret=os.environ["SOLVAPAY_WEBHOOK_SECRET"])
|
|
175
|
+
|
|
176
|
+
# For testing with sign_webhook, pass a recent timestamp:
|
|
177
|
+
from solvapay.webhooks import sign_webhook
|
|
178
|
+
sig = sign_webhook(body, secret, timestamp=int(time.time()))
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 9. `mypy --strict` errors in paywall tests
|
|
184
|
+
|
|
185
|
+
**Symptom:**
|
|
186
|
+
```
|
|
187
|
+
error: Argument "client" to "Paywall" has incompatible type "MagicMock"; expected "SolvaPay"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Cause:** `MagicMock(spec=SolvaPay)` does not know about instance attrs like `.limits`, `.customers` set in `__init__`. The spec reflects the class, not the instance.
|
|
191
|
+
|
|
192
|
+
**Fix:** Use plain `MagicMock()` without `spec=`:
|
|
193
|
+
```python
|
|
194
|
+
# WRONG
|
|
195
|
+
client = MagicMock(spec=SolvaPay) # AttributeError on .limits
|
|
196
|
+
|
|
197
|
+
# CORRECT
|
|
198
|
+
client = MagicMock()
|
|
199
|
+
client.limits.check.return_value = LimitResponse(within_limits=True, remaining=5)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Add `# type: ignore[arg-type]` on the `Paywall(client=...)` line to satisfy mypy strict.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 10. `pip install solvapay` vs `pip install solvapay-python`
|
|
207
|
+
|
|
208
|
+
**Symptom:**
|
|
209
|
+
```
|
|
210
|
+
ERROR: No matching distribution found for solvapay
|
|
211
|
+
```
|
|
212
|
+
Or: installed `solvapay` but `from solvapay import SolvaPay` is empty / old.
|
|
213
|
+
|
|
214
|
+
**Cause:** The distribution is named `solvapay-python` on PyPI (avoids namespace conflict before official adoption). The import name is still `solvapay`.
|
|
215
|
+
|
|
216
|
+
**Fix:**
|
|
217
|
+
```bash
|
|
218
|
+
pip install solvapay-python # correct
|
|
219
|
+
pip install solvapay-python[mcp] # with MCP adapter
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The import surface is unchanged:
|
|
223
|
+
```python
|
|
224
|
+
from solvapay import SolvaPay, AsyncSolvaPay
|
|
225
|
+
```
|
|
@@ -33,7 +33,9 @@ nav:
|
|
|
33
33
|
- reference/models.md
|
|
34
34
|
- reference/webhooks.md
|
|
35
35
|
- reference/paywall.md
|
|
36
|
-
- Architecture:
|
|
36
|
+
- Architecture:
|
|
37
|
+
- Layers: architecture/layers.md
|
|
38
|
+
- Cold-Start: architecture/cold-start.md
|
|
37
39
|
- Guides:
|
|
38
40
|
- MCP: guides/mcp.md
|
|
39
41
|
- LangChain: guides/langchain.md
|
|
@@ -42,3 +44,4 @@ nav:
|
|
|
42
44
|
- Idempotency: idempotency.md
|
|
43
45
|
- Webhooks: webhooks.md
|
|
44
46
|
- Migration: migration.md
|
|
47
|
+
- Troubleshooting: troubleshooting.md
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "solvapay-python"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.3"
|
|
8
8
|
description = "Community Python SDK for SolvaPay (agent-native payment rails)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -39,6 +39,7 @@ langchain = ["langchain-core>=0.3,<1.5"]
|
|
|
39
39
|
mcp = ["fastmcp>=3.2.0,<4"]
|
|
40
40
|
asgi = [] # raw ASGI; no extra deps required
|
|
41
41
|
retry = ["tenacity>=8.2,<10"]
|
|
42
|
+
bench = ["pytest-benchmark>=4.0,<6"]
|
|
42
43
|
|
|
43
44
|
[project.urls]
|
|
44
45
|
Homepage = "https://github.com/dhruv-sanan/solvapay-python"
|
|
@@ -94,7 +95,11 @@ ignore_missing_imports = true
|
|
|
94
95
|
[tool.pytest.ini_options]
|
|
95
96
|
testpaths = ["tests"]
|
|
96
97
|
asyncio_mode = "auto"
|
|
97
|
-
markers = [
|
|
98
|
+
markers = [
|
|
99
|
+
"contract: sandbox contract tests (require SOLVAPAY_SANDBOX_KEY)",
|
|
100
|
+
"benchmark: microbenchmarks — run with: uv run pytest tests/benchmarks/ --benchmark-only",
|
|
101
|
+
]
|
|
102
|
+
addopts = "--ignore=tests/benchmarks"
|
|
98
103
|
filterwarnings = [
|
|
99
104
|
# Flat shim methods intentionally emit DeprecationWarning (removed in v2.0).
|
|
100
105
|
# stacklevel=2 makes the warning appear from the test file, so module filter
|
|
@@ -130,3 +135,8 @@ showcontent = true
|
|
|
130
135
|
directory = "removal"
|
|
131
136
|
name = "Removals"
|
|
132
137
|
showcontent = true
|
|
138
|
+
|
|
139
|
+
[[tool.towncrier.type]]
|
|
140
|
+
directory = "performance"
|
|
141
|
+
name = "Performance"
|
|
142
|
+
showcontent = true
|
|
@@ -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.3"
|
|
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))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"darwin": 99.82}
|