spaps 0.5.1__tar.gz → 0.6.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.
- {spaps-0.5.1 → spaps-0.6.2}/.gitignore +4 -0
- {spaps-0.5.1 → spaps-0.6.2}/CHANGELOG.md +41 -0
- {spaps-0.5.1 → spaps-0.6.2}/FULL_CHECKLIST.md +1 -1
- {spaps-0.5.1 → spaps-0.6.2}/PKG-INFO +207 -10
- {spaps-0.5.1 → spaps-0.6.2}/README.md +206 -9
- {spaps-0.5.1 → spaps-0.6.2}/pyproject.toml +5 -1
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/__init__.py +150 -4
- spaps-0.6.2/src/spaps_client/app_links.py +230 -0
- spaps-0.6.2/src/spaps_client/app_links_async.py +183 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/async_client.py +21 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/auth.py +456 -26
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/auth_async.py +287 -15
- spaps-0.6.2/src/spaps_client/capability.py +785 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/client.py +62 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/crypto.py +11 -6
- spaps-0.6.2/src/spaps_client/dayrate.py +239 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/email.py +16 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/email_async.py +3 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/entitlements.py +273 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/entitlements_async.py +224 -1
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/payments.py +7 -5
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/payments_async.py +6 -4
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/sessions.py +4 -3
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/sessions_async.py +4 -4
- spaps-0.6.2/src/spaps_client/users.py +409 -0
- spaps-0.6.2/src/spaps_client/users_async.py +272 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/whitelist.py +4 -7
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/whitelist_async.py +4 -8
- {spaps-0.5.1 → spaps-0.6.2}/tests/integration/test_build.py +23 -1
- spaps-0.6.2/tests/unit/test_app_links.py +177 -0
- spaps-0.6.2/tests/unit/test_async_client.py +838 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_auth.py +521 -3
- spaps-0.6.2/tests/unit/test_capability.py +215 -0
- spaps-0.6.2/tests/unit/test_dayrate.py +137 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_email.py +31 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_entitlements.py +340 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_payments.py +18 -21
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_quickstart_full_adoption_async.py +7 -11
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_readme_snippets.py +14 -2
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_sessions.py +2 -4
- spaps-0.6.2/tests/unit/test_users.py +343 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_users_async.py +148 -1
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_whitelist.py +3 -7
- spaps-0.5.1/src/spaps_client/users.py +0 -156
- spaps-0.5.1/src/spaps_client/users_async.py +0 -102
- spaps-0.5.1/tests/unit/test_async_client.py +0 -411
- spaps-0.5.1/tests/unit/test_users.py +0 -164
- {spaps-0.5.1 → spaps-0.6.2}/LICENSE +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/TDD_PLAN.md +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/TODO.md +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/docs/RELEASE_CHECKLIST.md +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/scripts/manage_version.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/config.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/crypto_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/dayrate_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/device_flow.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/http.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/http_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/issue_reporting.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/issue_reporting_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/marketing.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/metrics.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/metrics_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/permissions.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/py.typed +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/secure_messages.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/secure_messages_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/skill_evals.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/skill_evals_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/storage.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/support_telemetry.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/usage.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/usage_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/webhooks.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/x402.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/src/spaps_client/x402_async.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/conftest.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/integration/test_smoke.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_client.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_config.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_device_flow.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_http.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_issue_reporting.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_marketing.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_metadata.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_metrics.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_permissions.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_secure_messages.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_skill_evals.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_storage.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_support_telemetry.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_usage.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_webhooks.py +0 -0
- {spaps-0.5.1 → spaps-0.6.2}/tests/unit/test_x402.py +0 -0
|
@@ -164,6 +164,7 @@ backup-feb8.sql
|
|
|
164
164
|
# Local modes/swarm artifacts
|
|
165
165
|
.ntm/
|
|
166
166
|
.skillbox/
|
|
167
|
+
artifacts/openapi-contract/
|
|
167
168
|
MODES_ANALYSIS_PROGRESS.md
|
|
168
169
|
MODES_OF_REASONING_REPORT_AND_ANALYSIS_OF_PROJECT.md
|
|
169
170
|
MODE_OUTPUT_*.md
|
|
@@ -200,3 +201,6 @@ refactor/
|
|
|
200
201
|
|
|
201
202
|
# bv (beads viewer) local config and caches
|
|
202
203
|
.bv/
|
|
204
|
+
|
|
205
|
+
# Beads compliance audit dir (managed by /beads-compliance-and-completion-verification skill)
|
|
206
|
+
/beads_compliance_audit/
|
|
@@ -6,6 +6,47 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.6.2] - 2026-06-16
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Accepted `stream`, `owner_id`, `subject_override`, and `body_override` on `EmailClient.send()` and accepted `stream` on `AsyncEmailClient.send()` so Python consumers can delegate SPAPS email sends without hand-rolling the `/api/email/send` envelope.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Bumped the Python client package metadata to `0.6.2`.
|
|
18
|
+
|
|
19
|
+
## [0.6.1] - 2026-06-14
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Added sync `DayrateClient` helpers and sync/async `AppLinksClient` helpers, with high-level `SpapsClient.dayrate`, `SpapsClient.app_links`, and `AsyncSpapsClient.app_links` accessors.
|
|
24
|
+
- Added sync and async `UsersClient.memberships` helpers for listing, inviting/adding, and removing app account users.
|
|
25
|
+
- Added sync and async `EntitlementsClient.project_grants` helpers for granting, revoking, listing, checking, and claiming project access grants.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Accepted canonical SPAPS password reset request envelopes that return `success`, `data.email`, `data.sent_at`, `request_id`, and `timestamp` without requiring a top-level English `message`.
|
|
30
|
+
- Exposed omitted password reset request messages as `None` instead of an empty string placeholder so apps keep ownership of success copy.
|
|
31
|
+
- Preserved password reset confirmation parsing for canonical envelopes where the success copy lives at `data.message`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Bumped the Python client package metadata to `0.6.1`; HTMA should consume `spaps>=0.6.1` or the matching unreleased local wheel/source checkout before retrying the reset flow.
|
|
36
|
+
|
|
37
|
+
## [0.6.0] - 2026-06-13
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- Added sync and async auth helpers for OIDC nonce/sign-in, WebAuthn registration/assertion, TOTP MFA enrollment/verification/disable, and SMS OTP request/verify flows.
|
|
42
|
+
- Added typed MFA challenge responses so token-issuing auth methods can return either tokens or an MFA challenge without storing incomplete credentials.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Bumped the Python client package metadata to `0.6.0`.
|
|
47
|
+
|
|
48
|
+
## [0.5.1] - 2026-06-04
|
|
49
|
+
|
|
9
50
|
### Added
|
|
10
51
|
|
|
11
52
|
- Added sync and async issue-reporting screenshot attachment helpers for private upload, list, access URL, delete, and attach-by-ID create/update/reply flows.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- [x] Payments: update payment method
|
|
13
13
|
- [x] Payments: Stripe checkout (existing)
|
|
14
14
|
- [x] Payments: Stripe payment intent (existing)
|
|
15
|
-
- [x] Payments: wallet deposit/status
|
|
15
|
+
- [x] Payments: wallet deposit/status helpers intentionally raise `NotImplementedError` for backend 501 stubs
|
|
16
16
|
- [x] Payments: subscription details/cancel (existing)
|
|
17
17
|
- [x] Payments: crypto invoices (create/get/reconcile)
|
|
18
18
|
- [x] Payments: crypto webhooks signature helper
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spaps
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Sweet Potato Authentication & Payment Service Python client
|
|
5
5
|
Project-URL: Homepage, https://api.sweetpotato.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/sweet-potato/spaps
|
|
@@ -80,7 +80,7 @@ This package targets Python 3.9+.
|
|
|
80
80
|
## Quick Start
|
|
81
81
|
|
|
82
82
|
```python
|
|
83
|
-
from spaps_client import SpapsClient
|
|
83
|
+
from spaps_client import MfaRequiredChallenge, SpapsClient
|
|
84
84
|
|
|
85
85
|
client = SpapsClient(
|
|
86
86
|
base_url="http://localhost:3301",
|
|
@@ -88,10 +88,20 @@ client = SpapsClient(
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
try:
|
|
91
|
-
|
|
91
|
+
login = client.auth.sign_in_with_password(
|
|
92
92
|
email="user@example.com",
|
|
93
93
|
password="correct-horse-battery-staple",
|
|
94
94
|
)
|
|
95
|
+
if isinstance(login, MfaRequiredChallenge):
|
|
96
|
+
tokens = client.auth.mfa_verify(
|
|
97
|
+
challenge_id=login.challenge_id,
|
|
98
|
+
challenge=login.challenge,
|
|
99
|
+
code="123456",
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
tokens = login
|
|
103
|
+
|
|
104
|
+
bootstrap = client.auth.get_session_context()
|
|
95
105
|
session = client.sessions.get_current_session()
|
|
96
106
|
products = client.payments.list_products(
|
|
97
107
|
category="subscription",
|
|
@@ -100,6 +110,7 @@ try:
|
|
|
100
110
|
)
|
|
101
111
|
|
|
102
112
|
print(tokens.user.email)
|
|
113
|
+
print(bootstrap.tier)
|
|
103
114
|
print(session.session_id)
|
|
104
115
|
print(products.total)
|
|
105
116
|
finally:
|
|
@@ -133,24 +144,68 @@ async def main() -> None:
|
|
|
133
144
|
asyncio.run(main())
|
|
134
145
|
```
|
|
135
146
|
|
|
147
|
+
## Modern Auth Methods
|
|
148
|
+
|
|
149
|
+
The auth client exposes the active SPAPS auth routes in both sync and async forms:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from spaps_client import MfaRequiredChallenge, SpapsClient
|
|
153
|
+
|
|
154
|
+
client = SpapsClient(base_url="http://localhost:3301", api_key="spaps_test_key")
|
|
155
|
+
|
|
156
|
+
methods = client.auth.list_methods()
|
|
157
|
+
print([item.method for item in methods.methods if item.enabled])
|
|
158
|
+
|
|
159
|
+
oidc = client.auth.oidc_nonce()
|
|
160
|
+
login = client.auth.oidc_sign_in(
|
|
161
|
+
provider="google",
|
|
162
|
+
id_token="provider-id-token",
|
|
163
|
+
challenge_id=oidc.challenge_id,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if isinstance(login, MfaRequiredChallenge):
|
|
167
|
+
login = client.auth.mfa_verify(
|
|
168
|
+
challenge_id=login.challenge_id,
|
|
169
|
+
challenge=login.challenge,
|
|
170
|
+
recovery_code="recovery-code",
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
TOTP management and passkey registration are authenticated operations and use stored tokens unless you pass `access_token` explicitly:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
enrollment = client.auth.mfa_totp_enroll()
|
|
178
|
+
activation = client.auth.mfa_totp_activate(code="123456")
|
|
179
|
+
options = client.auth.webauthn_register_options()
|
|
180
|
+
credential = client.auth.webauthn_register_verify(
|
|
181
|
+
challenge_id=options.challenge_id,
|
|
182
|
+
credential={"id": "browser-produced-credential"},
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
WebAuthn helpers intentionally pass browser-produced credential JSON through to the API; the Python client does not emulate authenticators or perform CTAP operations.
|
|
187
|
+
|
|
136
188
|
## Core Surface
|
|
137
189
|
|
|
138
190
|
### High-Level Clients
|
|
139
191
|
|
|
140
192
|
| Client | Best for |
|
|
141
193
|
| --- | --- |
|
|
142
|
-
| `SpapsClient` | Sync auth, sessions, payments, usage, whitelist, secure messages, issue reporting, marketing, metrics, skill evals, and support telemetry |
|
|
143
|
-
| `AsyncSpapsClient` | Async auth, sessions, payments, usage, whitelist, secure messages, issue reporting, metrics, entitlements,
|
|
194
|
+
| `SpapsClient` | Sync auth, sessions, payments, usage, access decisions, capability graph inspection, whitelist, secure messages, issue reporting, marketing, metrics, app links, dayrate, skill evals, and support telemetry |
|
|
195
|
+
| `AsyncSpapsClient` | Async auth, sessions, payments, usage, whitelist, secure messages, issue reporting, metrics, entitlements, app links, dayrate, skill evals, and users |
|
|
144
196
|
|
|
145
197
|
### Standalone Helpers
|
|
146
198
|
|
|
147
199
|
| Helper | Purpose |
|
|
148
200
|
| --- | --- |
|
|
149
|
-
| `EntitlementsClient` / `AsyncEntitlementsClient` | Resource entitlements, purchase history, manual grants, and
|
|
201
|
+
| `EntitlementsClient` / `AsyncEntitlementsClient` | Resource entitlements, purchase history, manual grants/revokes, and project grants |
|
|
150
202
|
| `UsageClient` / `AsyncUsageClient` | Secret-key usage authorization and immutable usage recording |
|
|
203
|
+
| `CapabilityClient` | Access decisions, action preparation, decision traces, graph inspection, and contract discovery |
|
|
151
204
|
| `EmailClient` / `AsyncEmailClient` | Template lookup, preview, and send flows |
|
|
152
|
-
| `UsersClient` / `AsyncUsersClient` | Batch user and
|
|
205
|
+
| `UsersClient` / `AsyncUsersClient` | Batch user/email lookups and app membership administration |
|
|
153
206
|
| `MarketingClient` | Browser-safe marketing event emission and server-side experiment results |
|
|
207
|
+
| `AppLinksClient` / `AsyncAppLinksClient` | Authenticated short-link creation/update and public link resolution |
|
|
208
|
+
| `DayrateClient` / `AsyncDayrateClient` | Availability, booking, cancellation, x402 booking holds, and admin booking reads |
|
|
154
209
|
| `DeviceFlowClient` | Device-code login workflows |
|
|
155
210
|
| `PermissionChecker` | Role and admin convenience checks |
|
|
156
211
|
| `verify_spaps_webhook` | Signature verification for incoming SPAPS webhooks |
|
|
@@ -184,6 +239,76 @@ Common parameters:
|
|
|
184
239
|
|
|
185
240
|
## Common Flows
|
|
186
241
|
|
|
242
|
+
### Agent Access Decisions
|
|
243
|
+
|
|
244
|
+
`client.access.check_access(...)` returns a typed decision for both allowed and
|
|
245
|
+
denied outcomes. A blocked action is not an exception; inspect `allowed`,
|
|
246
|
+
`outcome`, and `next_actions`.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from spaps_client import AccessDecisionRequest, CapabilityError, SpapsClient
|
|
250
|
+
|
|
251
|
+
client = SpapsClient(
|
|
252
|
+
base_url="https://api.example.test",
|
|
253
|
+
api_key="spaps_sec_example",
|
|
254
|
+
)
|
|
255
|
+
client.set_tokens(access_token="admin-access-token")
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
decision = client.access.check_access(
|
|
259
|
+
AccessDecisionRequest.model_validate(
|
|
260
|
+
{
|
|
261
|
+
"actor": {"actor_type": "user", "actor_ref": "user_123"},
|
|
262
|
+
"action": "checkout.create",
|
|
263
|
+
"resource": {"resource_type": "product", "resource_ref": "dayrate"},
|
|
264
|
+
"controls": {"entitlement_key": "bookme_paid"},
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if not decision.allowed:
|
|
270
|
+
print(decision.outcome, decision.next_actions)
|
|
271
|
+
|
|
272
|
+
prepared = client.access.prepare_action(
|
|
273
|
+
{
|
|
274
|
+
"access": {
|
|
275
|
+
"actor": {"actor_ref": "admin_123"},
|
|
276
|
+
"action": "admin.delete_user",
|
|
277
|
+
"resource": {"resource_type": "user", "resource_ref": "user_123"},
|
|
278
|
+
},
|
|
279
|
+
"include_command_templates": True,
|
|
280
|
+
"environment": "production",
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
print(prepared.status, prepared.execution.operator_gate_required)
|
|
284
|
+
|
|
285
|
+
contract = client.contract.get_contract()
|
|
286
|
+
refresh = client.graph.refresh_graph(correlation_id="local-refresh")
|
|
287
|
+
nodes = client.graph.list_graph_nodes(node_type="x402_resource", q="dayrate")
|
|
288
|
+
explanation = client.access.explain_decision(decision.decision_trace_id)
|
|
289
|
+
print(contract.version, refresh.status, refresh.diagnostics.phase2_gate.recommendation if refresh.diagnostics.phase2_gate else None)
|
|
290
|
+
print(nodes.projection.projection_status if nodes.projection else None, explanation.graph_node_keys)
|
|
291
|
+
except CapabilityError as exc:
|
|
292
|
+
print(exc.status_code, exc.code, exc.request_id, exc.diagnostics, exc.remediations)
|
|
293
|
+
finally:
|
|
294
|
+
client.close()
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`client.contract.get_contract()` includes graph vocabulary fields:
|
|
298
|
+
`graph_node_types`, `graph_edge_types`, `graph_source_domains`, and
|
|
299
|
+
`source_domain_notes`. Use them before filtering graph nodes by stable v1 types
|
|
300
|
+
such as `wallet`, `api_key`, `role`, and `approver`.
|
|
301
|
+
|
|
302
|
+
`CapabilityError` exposes both Python-native names (`status_code`,
|
|
303
|
+
`error_code`, `request_id`) and parity aliases (`status`, `code`). Diagnostics
|
|
304
|
+
and remediations preserve server-provided `details` fields.
|
|
305
|
+
|
|
306
|
+
`operator-gated` is compatibility/descriptive input when a client supplies it;
|
|
307
|
+
it does not authorize mutation command templates. SPAPS returns mutation
|
|
308
|
+
command templates only when the request has a server-recognized non-publishable
|
|
309
|
+
key and an authenticated admin/operator user context. Publishable callers
|
|
310
|
+
cannot self-attest the gate.
|
|
311
|
+
|
|
187
312
|
### Magic Links and Password Reset
|
|
188
313
|
|
|
189
314
|
```python
|
|
@@ -196,15 +321,36 @@ client = SpapsClient(
|
|
|
196
321
|
|
|
197
322
|
try:
|
|
198
323
|
client.auth.send_magic_link(email="user@example.com")
|
|
199
|
-
client.auth.request_password_reset(email="user@example.com")
|
|
200
|
-
client.auth.confirm_password_reset(
|
|
324
|
+
reset_request = client.auth.request_password_reset(email="user@example.com")
|
|
325
|
+
reset_confirm = client.auth.confirm_password_reset(
|
|
201
326
|
token="reset-token-from-email",
|
|
202
327
|
new_password="Sup3rStrong!",
|
|
203
328
|
)
|
|
329
|
+
print(reset_request.email, reset_request.sent_at, reset_confirm.message)
|
|
204
330
|
finally:
|
|
205
331
|
client.close()
|
|
206
332
|
```
|
|
207
333
|
|
|
334
|
+
Password reset responses use the standard SPAPS success envelope with top-level
|
|
335
|
+
`success`, `data`, `request_id`, and `timestamp`. The Python client returns the
|
|
336
|
+
nested `data` payload: reset requests expose `email` and `sent_at`, while reset
|
|
337
|
+
confirmation exposes the nested `message`. A top-level English `message` is not
|
|
338
|
+
canonical and should not drive UI copy. Prefer app-owned success copy, or map
|
|
339
|
+
future nested `data.message_key`/`data.status` values in your app.
|
|
340
|
+
|
|
341
|
+
#### Downstream Adoption
|
|
342
|
+
|
|
343
|
+
HTMA and future SPAPS consumers should use `spaps>=0.6.2` for password reset
|
|
344
|
+
flows and stream-aware email sends. Until `0.6.2` is published to PyPI, consume
|
|
345
|
+
a local wheel or source checkout from the Sweet Potato commit that carries the
|
|
346
|
+
`0.6.2` changelog entry.
|
|
347
|
+
|
|
348
|
+
Release execution is intentionally separate from this handoff: run the manual
|
|
349
|
+
`Publish Python Client` workflow when ready so release automation builds the
|
|
350
|
+
already-bumped package metadata and publishes to PyPI. Do not use
|
|
351
|
+
`npm run publish:python-client`, deploy SPAPS, or publish the package as part of
|
|
352
|
+
a consumer-only adoption check.
|
|
353
|
+
|
|
208
354
|
### Authenticated Stripe Checkout
|
|
209
355
|
|
|
210
356
|
`create_checkout_session` is the convenience path for authenticated checkout.
|
|
@@ -286,6 +432,57 @@ For dashboard and policy views, use `client.usage.get_features()`,
|
|
|
286
432
|
`client.usage.get_status(...)`, and `client.usage.get_history(...)` from the
|
|
287
433
|
same trusted backend context.
|
|
288
434
|
|
|
435
|
+
### Account Memberships and Project Grants
|
|
436
|
+
|
|
437
|
+
Use these helpers from a trusted backend with a secret SPAPS key. Admin mutation
|
|
438
|
+
helpers also require an admin access token; do not call membership invitation,
|
|
439
|
+
project grant, or project revoke helpers from browser code.
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
from spaps_client import EntitlementsClient, UsersClient
|
|
443
|
+
|
|
444
|
+
admin_token = "admin-access-token"
|
|
445
|
+
|
|
446
|
+
users = UsersClient(
|
|
447
|
+
base_url="https://api.example.test",
|
|
448
|
+
api_key="spaps_sec_example",
|
|
449
|
+
access_token=admin_token,
|
|
450
|
+
)
|
|
451
|
+
entitlements = EntitlementsClient(
|
|
452
|
+
base_url="https://api.example.test",
|
|
453
|
+
api_key="spaps_sec_example",
|
|
454
|
+
access_token=admin_token,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
try:
|
|
458
|
+
users.memberships.add_account_user(
|
|
459
|
+
email="teammate@example.com",
|
|
460
|
+
capabilities={"projects": ["project_123"]},
|
|
461
|
+
metadata={"invited_by": "admin@example.com"},
|
|
462
|
+
)
|
|
463
|
+
entitlements.project_grants.grant_user(
|
|
464
|
+
project_id="project_123",
|
|
465
|
+
entitlement_key="pds.project.viewer",
|
|
466
|
+
email="teammate@example.com",
|
|
467
|
+
reason="Project teammate invite",
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
project_users = entitlements.project_grants.list_project_users(
|
|
471
|
+
"project_123",
|
|
472
|
+
entitlement_key="pds.project.viewer",
|
|
473
|
+
)
|
|
474
|
+
user_projects = entitlements.project_grants.list_user_projects("user_123")
|
|
475
|
+
access = entitlements.project_grants.check_access(
|
|
476
|
+
project_id="project_123",
|
|
477
|
+
entitlement_key="pds.project.viewer",
|
|
478
|
+
user_id="user_123",
|
|
479
|
+
)
|
|
480
|
+
print(project_users.count, user_projects.count, access.has_access)
|
|
481
|
+
finally:
|
|
482
|
+
users.close()
|
|
483
|
+
entitlements.close()
|
|
484
|
+
```
|
|
485
|
+
|
|
289
486
|
### Marketing Events
|
|
290
487
|
|
|
291
488
|
Use `client.marketing.emit_event(...)` with a publishable key to record anonymous
|
|
@@ -589,7 +786,7 @@ It can. Pass `RetryConfig` if you want retry and backoff behavior.
|
|
|
589
786
|
## Metadata
|
|
590
787
|
|
|
591
788
|
- `package_name`: `spaps`
|
|
592
|
-
- `latest_version`: `0.
|
|
789
|
+
- `latest_version`: `0.6.2`
|
|
593
790
|
- `minimum_runtime`: `Python >=3.9`
|
|
594
791
|
- `api_base_url`: `https://api.sweetpotato.dev`
|
|
595
792
|
|
|
@@ -24,7 +24,7 @@ This package targets Python 3.9+.
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
26
|
```python
|
|
27
|
-
from spaps_client import SpapsClient
|
|
27
|
+
from spaps_client import MfaRequiredChallenge, SpapsClient
|
|
28
28
|
|
|
29
29
|
client = SpapsClient(
|
|
30
30
|
base_url="http://localhost:3301",
|
|
@@ -32,10 +32,20 @@ client = SpapsClient(
|
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
try:
|
|
35
|
-
|
|
35
|
+
login = client.auth.sign_in_with_password(
|
|
36
36
|
email="user@example.com",
|
|
37
37
|
password="correct-horse-battery-staple",
|
|
38
38
|
)
|
|
39
|
+
if isinstance(login, MfaRequiredChallenge):
|
|
40
|
+
tokens = client.auth.mfa_verify(
|
|
41
|
+
challenge_id=login.challenge_id,
|
|
42
|
+
challenge=login.challenge,
|
|
43
|
+
code="123456",
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
tokens = login
|
|
47
|
+
|
|
48
|
+
bootstrap = client.auth.get_session_context()
|
|
39
49
|
session = client.sessions.get_current_session()
|
|
40
50
|
products = client.payments.list_products(
|
|
41
51
|
category="subscription",
|
|
@@ -44,6 +54,7 @@ try:
|
|
|
44
54
|
)
|
|
45
55
|
|
|
46
56
|
print(tokens.user.email)
|
|
57
|
+
print(bootstrap.tier)
|
|
47
58
|
print(session.session_id)
|
|
48
59
|
print(products.total)
|
|
49
60
|
finally:
|
|
@@ -77,24 +88,68 @@ async def main() -> None:
|
|
|
77
88
|
asyncio.run(main())
|
|
78
89
|
```
|
|
79
90
|
|
|
91
|
+
## Modern Auth Methods
|
|
92
|
+
|
|
93
|
+
The auth client exposes the active SPAPS auth routes in both sync and async forms:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from spaps_client import MfaRequiredChallenge, SpapsClient
|
|
97
|
+
|
|
98
|
+
client = SpapsClient(base_url="http://localhost:3301", api_key="spaps_test_key")
|
|
99
|
+
|
|
100
|
+
methods = client.auth.list_methods()
|
|
101
|
+
print([item.method for item in methods.methods if item.enabled])
|
|
102
|
+
|
|
103
|
+
oidc = client.auth.oidc_nonce()
|
|
104
|
+
login = client.auth.oidc_sign_in(
|
|
105
|
+
provider="google",
|
|
106
|
+
id_token="provider-id-token",
|
|
107
|
+
challenge_id=oidc.challenge_id,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if isinstance(login, MfaRequiredChallenge):
|
|
111
|
+
login = client.auth.mfa_verify(
|
|
112
|
+
challenge_id=login.challenge_id,
|
|
113
|
+
challenge=login.challenge,
|
|
114
|
+
recovery_code="recovery-code",
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
TOTP management and passkey registration are authenticated operations and use stored tokens unless you pass `access_token` explicitly:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
enrollment = client.auth.mfa_totp_enroll()
|
|
122
|
+
activation = client.auth.mfa_totp_activate(code="123456")
|
|
123
|
+
options = client.auth.webauthn_register_options()
|
|
124
|
+
credential = client.auth.webauthn_register_verify(
|
|
125
|
+
challenge_id=options.challenge_id,
|
|
126
|
+
credential={"id": "browser-produced-credential"},
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
WebAuthn helpers intentionally pass browser-produced credential JSON through to the API; the Python client does not emulate authenticators or perform CTAP operations.
|
|
131
|
+
|
|
80
132
|
## Core Surface
|
|
81
133
|
|
|
82
134
|
### High-Level Clients
|
|
83
135
|
|
|
84
136
|
| Client | Best for |
|
|
85
137
|
| --- | --- |
|
|
86
|
-
| `SpapsClient` | Sync auth, sessions, payments, usage, whitelist, secure messages, issue reporting, marketing, metrics, skill evals, and support telemetry |
|
|
87
|
-
| `AsyncSpapsClient` | Async auth, sessions, payments, usage, whitelist, secure messages, issue reporting, metrics, entitlements,
|
|
138
|
+
| `SpapsClient` | Sync auth, sessions, payments, usage, access decisions, capability graph inspection, whitelist, secure messages, issue reporting, marketing, metrics, app links, dayrate, skill evals, and support telemetry |
|
|
139
|
+
| `AsyncSpapsClient` | Async auth, sessions, payments, usage, whitelist, secure messages, issue reporting, metrics, entitlements, app links, dayrate, skill evals, and users |
|
|
88
140
|
|
|
89
141
|
### Standalone Helpers
|
|
90
142
|
|
|
91
143
|
| Helper | Purpose |
|
|
92
144
|
| --- | --- |
|
|
93
|
-
| `EntitlementsClient` / `AsyncEntitlementsClient` | Resource entitlements, purchase history, manual grants, and
|
|
145
|
+
| `EntitlementsClient` / `AsyncEntitlementsClient` | Resource entitlements, purchase history, manual grants/revokes, and project grants |
|
|
94
146
|
| `UsageClient` / `AsyncUsageClient` | Secret-key usage authorization and immutable usage recording |
|
|
147
|
+
| `CapabilityClient` | Access decisions, action preparation, decision traces, graph inspection, and contract discovery |
|
|
95
148
|
| `EmailClient` / `AsyncEmailClient` | Template lookup, preview, and send flows |
|
|
96
|
-
| `UsersClient` / `AsyncUsersClient` | Batch user and
|
|
149
|
+
| `UsersClient` / `AsyncUsersClient` | Batch user/email lookups and app membership administration |
|
|
97
150
|
| `MarketingClient` | Browser-safe marketing event emission and server-side experiment results |
|
|
151
|
+
| `AppLinksClient` / `AsyncAppLinksClient` | Authenticated short-link creation/update and public link resolution |
|
|
152
|
+
| `DayrateClient` / `AsyncDayrateClient` | Availability, booking, cancellation, x402 booking holds, and admin booking reads |
|
|
98
153
|
| `DeviceFlowClient` | Device-code login workflows |
|
|
99
154
|
| `PermissionChecker` | Role and admin convenience checks |
|
|
100
155
|
| `verify_spaps_webhook` | Signature verification for incoming SPAPS webhooks |
|
|
@@ -128,6 +183,76 @@ Common parameters:
|
|
|
128
183
|
|
|
129
184
|
## Common Flows
|
|
130
185
|
|
|
186
|
+
### Agent Access Decisions
|
|
187
|
+
|
|
188
|
+
`client.access.check_access(...)` returns a typed decision for both allowed and
|
|
189
|
+
denied outcomes. A blocked action is not an exception; inspect `allowed`,
|
|
190
|
+
`outcome`, and `next_actions`.
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from spaps_client import AccessDecisionRequest, CapabilityError, SpapsClient
|
|
194
|
+
|
|
195
|
+
client = SpapsClient(
|
|
196
|
+
base_url="https://api.example.test",
|
|
197
|
+
api_key="spaps_sec_example",
|
|
198
|
+
)
|
|
199
|
+
client.set_tokens(access_token="admin-access-token")
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
decision = client.access.check_access(
|
|
203
|
+
AccessDecisionRequest.model_validate(
|
|
204
|
+
{
|
|
205
|
+
"actor": {"actor_type": "user", "actor_ref": "user_123"},
|
|
206
|
+
"action": "checkout.create",
|
|
207
|
+
"resource": {"resource_type": "product", "resource_ref": "dayrate"},
|
|
208
|
+
"controls": {"entitlement_key": "bookme_paid"},
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if not decision.allowed:
|
|
214
|
+
print(decision.outcome, decision.next_actions)
|
|
215
|
+
|
|
216
|
+
prepared = client.access.prepare_action(
|
|
217
|
+
{
|
|
218
|
+
"access": {
|
|
219
|
+
"actor": {"actor_ref": "admin_123"},
|
|
220
|
+
"action": "admin.delete_user",
|
|
221
|
+
"resource": {"resource_type": "user", "resource_ref": "user_123"},
|
|
222
|
+
},
|
|
223
|
+
"include_command_templates": True,
|
|
224
|
+
"environment": "production",
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
print(prepared.status, prepared.execution.operator_gate_required)
|
|
228
|
+
|
|
229
|
+
contract = client.contract.get_contract()
|
|
230
|
+
refresh = client.graph.refresh_graph(correlation_id="local-refresh")
|
|
231
|
+
nodes = client.graph.list_graph_nodes(node_type="x402_resource", q="dayrate")
|
|
232
|
+
explanation = client.access.explain_decision(decision.decision_trace_id)
|
|
233
|
+
print(contract.version, refresh.status, refresh.diagnostics.phase2_gate.recommendation if refresh.diagnostics.phase2_gate else None)
|
|
234
|
+
print(nodes.projection.projection_status if nodes.projection else None, explanation.graph_node_keys)
|
|
235
|
+
except CapabilityError as exc:
|
|
236
|
+
print(exc.status_code, exc.code, exc.request_id, exc.diagnostics, exc.remediations)
|
|
237
|
+
finally:
|
|
238
|
+
client.close()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`client.contract.get_contract()` includes graph vocabulary fields:
|
|
242
|
+
`graph_node_types`, `graph_edge_types`, `graph_source_domains`, and
|
|
243
|
+
`source_domain_notes`. Use them before filtering graph nodes by stable v1 types
|
|
244
|
+
such as `wallet`, `api_key`, `role`, and `approver`.
|
|
245
|
+
|
|
246
|
+
`CapabilityError` exposes both Python-native names (`status_code`,
|
|
247
|
+
`error_code`, `request_id`) and parity aliases (`status`, `code`). Diagnostics
|
|
248
|
+
and remediations preserve server-provided `details` fields.
|
|
249
|
+
|
|
250
|
+
`operator-gated` is compatibility/descriptive input when a client supplies it;
|
|
251
|
+
it does not authorize mutation command templates. SPAPS returns mutation
|
|
252
|
+
command templates only when the request has a server-recognized non-publishable
|
|
253
|
+
key and an authenticated admin/operator user context. Publishable callers
|
|
254
|
+
cannot self-attest the gate.
|
|
255
|
+
|
|
131
256
|
### Magic Links and Password Reset
|
|
132
257
|
|
|
133
258
|
```python
|
|
@@ -140,15 +265,36 @@ client = SpapsClient(
|
|
|
140
265
|
|
|
141
266
|
try:
|
|
142
267
|
client.auth.send_magic_link(email="user@example.com")
|
|
143
|
-
client.auth.request_password_reset(email="user@example.com")
|
|
144
|
-
client.auth.confirm_password_reset(
|
|
268
|
+
reset_request = client.auth.request_password_reset(email="user@example.com")
|
|
269
|
+
reset_confirm = client.auth.confirm_password_reset(
|
|
145
270
|
token="reset-token-from-email",
|
|
146
271
|
new_password="Sup3rStrong!",
|
|
147
272
|
)
|
|
273
|
+
print(reset_request.email, reset_request.sent_at, reset_confirm.message)
|
|
148
274
|
finally:
|
|
149
275
|
client.close()
|
|
150
276
|
```
|
|
151
277
|
|
|
278
|
+
Password reset responses use the standard SPAPS success envelope with top-level
|
|
279
|
+
`success`, `data`, `request_id`, and `timestamp`. The Python client returns the
|
|
280
|
+
nested `data` payload: reset requests expose `email` and `sent_at`, while reset
|
|
281
|
+
confirmation exposes the nested `message`. A top-level English `message` is not
|
|
282
|
+
canonical and should not drive UI copy. Prefer app-owned success copy, or map
|
|
283
|
+
future nested `data.message_key`/`data.status` values in your app.
|
|
284
|
+
|
|
285
|
+
#### Downstream Adoption
|
|
286
|
+
|
|
287
|
+
HTMA and future SPAPS consumers should use `spaps>=0.6.2` for password reset
|
|
288
|
+
flows and stream-aware email sends. Until `0.6.2` is published to PyPI, consume
|
|
289
|
+
a local wheel or source checkout from the Sweet Potato commit that carries the
|
|
290
|
+
`0.6.2` changelog entry.
|
|
291
|
+
|
|
292
|
+
Release execution is intentionally separate from this handoff: run the manual
|
|
293
|
+
`Publish Python Client` workflow when ready so release automation builds the
|
|
294
|
+
already-bumped package metadata and publishes to PyPI. Do not use
|
|
295
|
+
`npm run publish:python-client`, deploy SPAPS, or publish the package as part of
|
|
296
|
+
a consumer-only adoption check.
|
|
297
|
+
|
|
152
298
|
### Authenticated Stripe Checkout
|
|
153
299
|
|
|
154
300
|
`create_checkout_session` is the convenience path for authenticated checkout.
|
|
@@ -230,6 +376,57 @@ For dashboard and policy views, use `client.usage.get_features()`,
|
|
|
230
376
|
`client.usage.get_status(...)`, and `client.usage.get_history(...)` from the
|
|
231
377
|
same trusted backend context.
|
|
232
378
|
|
|
379
|
+
### Account Memberships and Project Grants
|
|
380
|
+
|
|
381
|
+
Use these helpers from a trusted backend with a secret SPAPS key. Admin mutation
|
|
382
|
+
helpers also require an admin access token; do not call membership invitation,
|
|
383
|
+
project grant, or project revoke helpers from browser code.
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
from spaps_client import EntitlementsClient, UsersClient
|
|
387
|
+
|
|
388
|
+
admin_token = "admin-access-token"
|
|
389
|
+
|
|
390
|
+
users = UsersClient(
|
|
391
|
+
base_url="https://api.example.test",
|
|
392
|
+
api_key="spaps_sec_example",
|
|
393
|
+
access_token=admin_token,
|
|
394
|
+
)
|
|
395
|
+
entitlements = EntitlementsClient(
|
|
396
|
+
base_url="https://api.example.test",
|
|
397
|
+
api_key="spaps_sec_example",
|
|
398
|
+
access_token=admin_token,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
users.memberships.add_account_user(
|
|
403
|
+
email="teammate@example.com",
|
|
404
|
+
capabilities={"projects": ["project_123"]},
|
|
405
|
+
metadata={"invited_by": "admin@example.com"},
|
|
406
|
+
)
|
|
407
|
+
entitlements.project_grants.grant_user(
|
|
408
|
+
project_id="project_123",
|
|
409
|
+
entitlement_key="pds.project.viewer",
|
|
410
|
+
email="teammate@example.com",
|
|
411
|
+
reason="Project teammate invite",
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
project_users = entitlements.project_grants.list_project_users(
|
|
415
|
+
"project_123",
|
|
416
|
+
entitlement_key="pds.project.viewer",
|
|
417
|
+
)
|
|
418
|
+
user_projects = entitlements.project_grants.list_user_projects("user_123")
|
|
419
|
+
access = entitlements.project_grants.check_access(
|
|
420
|
+
project_id="project_123",
|
|
421
|
+
entitlement_key="pds.project.viewer",
|
|
422
|
+
user_id="user_123",
|
|
423
|
+
)
|
|
424
|
+
print(project_users.count, user_projects.count, access.has_access)
|
|
425
|
+
finally:
|
|
426
|
+
users.close()
|
|
427
|
+
entitlements.close()
|
|
428
|
+
```
|
|
429
|
+
|
|
233
430
|
### Marketing Events
|
|
234
431
|
|
|
235
432
|
Use `client.marketing.emit_event(...)` with a publishable key to record anonymous
|
|
@@ -533,7 +730,7 @@ It can. Pass `RetryConfig` if you want retry and backoff behavior.
|
|
|
533
730
|
## Metadata
|
|
534
731
|
|
|
535
732
|
- `package_name`: `spaps`
|
|
536
|
-
- `latest_version`: `0.
|
|
733
|
+
- `latest_version`: `0.6.2`
|
|
537
734
|
- `minimum_runtime`: `Python >=3.9`
|
|
538
735
|
- `api_base_url`: `https://api.sweetpotato.dev`
|
|
539
736
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spaps"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.2"
|
|
8
8
|
description = "Sweet Potato Authentication & Payment Service Python client"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -70,6 +70,10 @@ strict_optional = true
|
|
|
70
70
|
warn_unused_configs = true
|
|
71
71
|
ignore_missing_imports = true
|
|
72
72
|
|
|
73
|
+
[[tool.mypy.overrides]]
|
|
74
|
+
module = ["click", "click.*"]
|
|
75
|
+
follow_imports = "skip"
|
|
76
|
+
|
|
73
77
|
[tool.coverage.run]
|
|
74
78
|
omit = [
|
|
75
79
|
"src/spaps_client/*_async.py",
|