spaps 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spaps-0.1.0.dist-info/METADATA +181 -0
- spaps-0.1.0.dist-info/RECORD +28 -0
- spaps-0.1.0.dist-info/WHEEL +4 -0
- spaps-0.1.0.dist-info/licenses/LICENSE +22 -0
- spaps_client/__init__.py +163 -0
- spaps_client/async_client.py +214 -0
- spaps_client/auth.py +411 -0
- spaps_client/auth_async.py +251 -0
- spaps_client/client.py +213 -0
- spaps_client/config.py +77 -0
- spaps_client/crypto.py +282 -0
- spaps_client/crypto_async.py +135 -0
- spaps_client/http.py +171 -0
- spaps_client/http_async.py +85 -0
- spaps_client/metrics.py +56 -0
- spaps_client/metrics_async.py +47 -0
- spaps_client/payments.py +410 -0
- spaps_client/payments_async.py +246 -0
- spaps_client/py.typed +0 -0
- spaps_client/secure_messages.py +172 -0
- spaps_client/secure_messages_async.py +105 -0
- spaps_client/sessions.py +267 -0
- spaps_client/sessions_async.py +152 -0
- spaps_client/storage.py +145 -0
- spaps_client/usage.py +266 -0
- spaps_client/usage_async.py +140 -0
- spaps_client/whitelist.py +269 -0
- spaps_client/whitelist_async.py +199 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spaps
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Sweet Potato Authentication & Payment Service Python client
|
|
5
|
+
Project-URL: Homepage, https://api.sweetpotato.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/sweet-potato/spaps
|
|
7
|
+
Author-email: Sweet Potato Team <support@sweetpotato.dev>
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Sweet Potato Team
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: authentication,payments,spaps,sweet-potato
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
41
|
+
Requires-Python: >=3.9
|
|
42
|
+
Requires-Dist: httpx<0.28.0,>=0.27.0
|
|
43
|
+
Requires-Dist: pydantic<3.0.0,>=2.7.0
|
|
44
|
+
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: build<2.0.0,>=1.2.1; extra == 'dev'
|
|
46
|
+
Requires-Dist: httpx<0.28.0,>=0.27.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: mypy<2.0.0,>=1.10.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: pydantic<3.0.0,>=2.7.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
52
|
+
Requires-Dist: respx<0.23.0,>=0.22.0; extra == 'dev'
|
|
53
|
+
Requires-Dist: ruff<1.0.0,>=0.5.5; extra == 'dev'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
id: spaps-python-sdk
|
|
58
|
+
title: Sweet Potato Python Client
|
|
59
|
+
category: sdk
|
|
60
|
+
tags:
|
|
61
|
+
- sdk
|
|
62
|
+
- python
|
|
63
|
+
- client
|
|
64
|
+
ai_summary: |
|
|
65
|
+
Explains installation, configuration, and usage patterns for the spaps Python
|
|
66
|
+
SDK, including environment setup, async support, and integration guidance for
|
|
67
|
+
backend services.
|
|
68
|
+
last_updated: 2025-02-14
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# Sweet Potato Python Client
|
|
72
|
+
|
|
73
|
+
> Python SDK for the Sweet Potato Authentication & Payment Service (SPAPS).
|
|
74
|
+
|
|
75
|
+
This package is under active development. Follow the TDD plan in `TDD_PLAN.md`
|
|
76
|
+
to track progress and upcoming milestones.
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
Install from PyPI:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install spaps
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
For local development inside this repository:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install -e .[dev]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pytest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Available clients
|
|
99
|
+
|
|
100
|
+
- `AuthClient` – wallet, email/password, and magic link flows
|
|
101
|
+
- `SessionsClient` – current session, validation, listing, revocation
|
|
102
|
+
- `PaymentsClient` – checkout sessions, wallet deposits, crypto invoices
|
|
103
|
+
- `UsageClient` – feature usage snapshots, recording, aggregated history
|
|
104
|
+
- `SecureMessagesClient` – encrypted message creation and retrieval
|
|
105
|
+
- `MetricsClient` – health and metrics convenience helpers
|
|
106
|
+
|
|
107
|
+
### Quickstart
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from spaps_client import SpapsClient
|
|
111
|
+
|
|
112
|
+
spaps = SpapsClient(base_url="http://localhost:3300", api_key="test_key_local_dev_only")
|
|
113
|
+
|
|
114
|
+
# Authenticate (tokens are persisted automatically)
|
|
115
|
+
spaps.auth.sign_in_with_password(email="user@example.com", password="Secret123!")
|
|
116
|
+
|
|
117
|
+
# Call downstream services using the stored access token
|
|
118
|
+
current = spaps.sessions.get_current_session()
|
|
119
|
+
print(current.session_id)
|
|
120
|
+
|
|
121
|
+
checkout = spaps.payments.create_checkout_session(
|
|
122
|
+
price_id="price_123",
|
|
123
|
+
mode="subscription",
|
|
124
|
+
success_url="https://example.com/success",
|
|
125
|
+
cancel_url="https://example.com/cancel",
|
|
126
|
+
)
|
|
127
|
+
print(checkout.checkout_url)
|
|
128
|
+
|
|
129
|
+
spaps.close()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Configure retry/backoff and structured logging when constructing the client:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from spaps_client import SpapsClient, RetryConfig, default_logging_hooks
|
|
136
|
+
|
|
137
|
+
spaps = SpapsClient(
|
|
138
|
+
base_url="http://localhost:3300",
|
|
139
|
+
api_key="test_key_local_dev_only",
|
|
140
|
+
retry_config=RetryConfig(max_attempts=4, backoff_factor=0.2),
|
|
141
|
+
logging_hooks=default_logging_hooks(),
|
|
142
|
+
)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Async Quickstart
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
import asyncio
|
|
149
|
+
from spaps_client import AsyncSpapsClient
|
|
150
|
+
|
|
151
|
+
async def main():
|
|
152
|
+
client = AsyncSpapsClient(base_url="http://localhost:3300", api_key="test_key_local_dev_only")
|
|
153
|
+
try:
|
|
154
|
+
await client.auth.sign_in_with_password(email="user@example.com", password="Secret123!")
|
|
155
|
+
current = await client.sessions.list_sessions()
|
|
156
|
+
print(len(current.sessions))
|
|
157
|
+
finally:
|
|
158
|
+
await client.aclose()
|
|
159
|
+
|
|
160
|
+
asyncio.run(main())
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Useful Scripts
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm run test:python-client # run pytest from repo root
|
|
167
|
+
npm run lint:python-client # ruff linting
|
|
168
|
+
npm run typecheck:python-client # mypy type checking
|
|
169
|
+
npm run build:python-client # build wheel/sdist and run twine check
|
|
170
|
+
npm run publish:python-client # build and upload via twine (requires PYPI_TOKEN)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Refer to `docs/RELEASE_CHECKLIST.md` for the full release process.
|
|
174
|
+
|
|
175
|
+
Refer to the repository root documentation for integration details.
|
|
176
|
+
|
|
177
|
+
## Documentation
|
|
178
|
+
|
|
179
|
+
- [Quickstart (Python section)](../../docs/getting-started/quickstart.md#python-example---using-spaps)
|
|
180
|
+
- [Python Backend Integration Guide](../../docs/guides/python-backend.md)
|
|
181
|
+
- API references under `docs/api/` include Python usage snippets for sessions, payments, usage, whitelist, and secure messages.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
spaps_client/__init__.py,sha256=W3NbWfDwSIpmZEQOggm407Vd60Q50V-x2WJpuao9L4c,4049
|
|
2
|
+
spaps_client/async_client.py,sha256=2IdoQnRmKet4cgOagNtGlIyYe3wkEZa3I4NkeNU6gf0,7489
|
|
3
|
+
spaps_client/auth.py,sha256=y2V0liV2oWKQ_Os5hqjzBaHtYVQy9TcFeZEgL7VAmTI,12950
|
|
4
|
+
spaps_client/auth_async.py,sha256=pSoDcqpnSQrcavY0qpFF9tt3q2Fr3-jj0wA2OzHegDE,9152
|
|
5
|
+
spaps_client/client.py,sha256=XTGnaHnrCVvZ7StTINthh3ZfhIJJTqV3vrhBnAvxBjI,7190
|
|
6
|
+
spaps_client/config.py,sha256=CzjtyZWnlvRuOirTxjvNipdky6-f5Hlz_lZ1VKry5lQ,2183
|
|
7
|
+
spaps_client/crypto.py,sha256=f6qmfynGstmwaj453RzmSwN2Z-CMzYbmWc2L4QBS4LY,8210
|
|
8
|
+
spaps_client/crypto_async.py,sha256=ZjrU2lr4d11mKavflWeByyQ3x3A0WfEaRnWdbV38xMw,4452
|
|
9
|
+
spaps_client/http.py,sha256=CzhS0lNWWDRncTRdB5mwZXB58r5nkc6ivfKq63g5OTo,5781
|
|
10
|
+
spaps_client/http_async.py,sha256=NfhT5S0HUnHMsS0RGC_2dsU6tbASJO9ZXFaNPcusCew,2819
|
|
11
|
+
spaps_client/metrics.py,sha256=hsP3cKu6Od3LqQQCxrX_F-gY8psIa3huIy_4EtF4ypo,1634
|
|
12
|
+
spaps_client/metrics_async.py,sha256=Ckl4182Q828BMxkQjJWh-ERwBvnidl00zsAPMrktBIY,1443
|
|
13
|
+
spaps_client/payments.py,sha256=NJJC9xgmk7T71bH_0rU4m6_1_F7JpVsnO5kVo2Uybng,11887
|
|
14
|
+
spaps_client/payments_async.py,sha256=Rj2ftZ1_W-hRneTCAG9EeA6UiWlYirs2FemfPLJGhM8,8037
|
|
15
|
+
spaps_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
spaps_client/secure_messages.py,sha256=Yq1EBoRdZ2CATQxoCFcOu_ZDNM8ieJts866dpdEdb2c,5140
|
|
17
|
+
spaps_client/secure_messages_async.py,sha256=CGMAFTbI-Na0VgA48o-JLEURDoxF4trxSuDscm1Nuhk,3705
|
|
18
|
+
spaps_client/sessions.py,sha256=74wyOIdCaKU8qJPFsXCPK8JLcuPeS1meCNo805yw-9U,8502
|
|
19
|
+
spaps_client/sessions_async.py,sha256=5L5CVqB5nh3jwTQfrUZo_narMCNbNtXBP-u-ySy5ylI,5605
|
|
20
|
+
spaps_client/storage.py,sha256=VrDBwj23CUpCAw_fH4faLNjzrwsJFgGV8Xth3vxoPTM,4503
|
|
21
|
+
spaps_client/usage.py,sha256=4QVpANBqDLt4xikTt8GBpvtuhTEGyopsveJY_AwNzIQ,7717
|
|
22
|
+
spaps_client/usage_async.py,sha256=HBv2rYxKXOl8rGVJhMRrzbfhltMuh_--O4m7zkZzT2U,4731
|
|
23
|
+
spaps_client/whitelist.py,sha256=EgscgzgTDkuFRvwxOVXsRSNFpOC3I_4ShuDdVNXj0Gs,8127
|
|
24
|
+
spaps_client/whitelist_async.py,sha256=D7le5zhOpU1Y7hOlU831t3TTCvXUjHHjDXkCjnrq-JQ,6716
|
|
25
|
+
spaps-0.1.0.dist-info/METADATA,sha256=prj-CeSPmvGzceLvLZ-g1qHyoo7ael9tjxXDwWDLxlc,6222
|
|
26
|
+
spaps-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
27
|
+
spaps-0.1.0.dist-info/licenses/LICENSE,sha256=62pDTGM_ffmWeJk1DglVsvYY7BMWZxbgvj0o0napi-I,1075
|
|
28
|
+
spaps-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sweet Potato Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
spaps_client/__init__.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sweet Potato Authentication & Payment Service Python client.
|
|
3
|
+
|
|
4
|
+
This package exposes modules for authentication, session management,
|
|
5
|
+
payments, and supporting utilities as implementation progresses.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .auth import AuthClient, AuthError, NonceResponse, TokenPair, TokenUser
|
|
9
|
+
from .sessions import (
|
|
10
|
+
SessionError,
|
|
11
|
+
SessionSummary,
|
|
12
|
+
SessionValidationResult,
|
|
13
|
+
SessionListResult,
|
|
14
|
+
SessionRecord,
|
|
15
|
+
SessionTouchResult,
|
|
16
|
+
SessionRevokeResult,
|
|
17
|
+
SessionsClient,
|
|
18
|
+
)
|
|
19
|
+
from .payments import (
|
|
20
|
+
PaymentsClient,
|
|
21
|
+
PaymentsError,
|
|
22
|
+
CheckoutSession,
|
|
23
|
+
PaymentIntent,
|
|
24
|
+
WalletDeposit,
|
|
25
|
+
WalletTransaction,
|
|
26
|
+
SubscriptionPlan,
|
|
27
|
+
SubscriptionDetail,
|
|
28
|
+
SubscriptionCancellation,
|
|
29
|
+
BalanceOverview,
|
|
30
|
+
BalanceAmounts,
|
|
31
|
+
UsageSummary,
|
|
32
|
+
PaymentMethodUpdateResult,
|
|
33
|
+
)
|
|
34
|
+
from .whitelist import (
|
|
35
|
+
WhitelistClient,
|
|
36
|
+
WhitelistError,
|
|
37
|
+
WhitelistEntry,
|
|
38
|
+
WhitelistCheckResult,
|
|
39
|
+
WhitelistListResult,
|
|
40
|
+
WhitelistMessage,
|
|
41
|
+
)
|
|
42
|
+
from .config import Settings, create_http_client
|
|
43
|
+
from .crypto import (
|
|
44
|
+
CryptoPaymentsClient,
|
|
45
|
+
CryptoPaymentsError,
|
|
46
|
+
CryptoInvoice,
|
|
47
|
+
CryptoInvoiceStatus,
|
|
48
|
+
CryptoReconcileJob,
|
|
49
|
+
verify_crypto_webhook_signature,
|
|
50
|
+
)
|
|
51
|
+
from .usage import (
|
|
52
|
+
UsageClient,
|
|
53
|
+
UsageError,
|
|
54
|
+
UsagePeriod,
|
|
55
|
+
UsageFeature,
|
|
56
|
+
UsageFeaturesResponse,
|
|
57
|
+
UsageRecordUsage,
|
|
58
|
+
UsageRecordResult,
|
|
59
|
+
UsageHistoryEntry,
|
|
60
|
+
UsageHistoryResponse,
|
|
61
|
+
)
|
|
62
|
+
from .secure_messages import (
|
|
63
|
+
SecureMessagesClient,
|
|
64
|
+
SecureMessagesError,
|
|
65
|
+
SecureMessage,
|
|
66
|
+
)
|
|
67
|
+
from .metrics import MetricsClient
|
|
68
|
+
from .auth_async import AsyncAuthClient
|
|
69
|
+
from .sessions_async import AsyncSessionsClient
|
|
70
|
+
from .payments_async import AsyncPaymentsClient
|
|
71
|
+
from .usage_async import AsyncUsageClient
|
|
72
|
+
from .whitelist_async import AsyncWhitelistClient
|
|
73
|
+
from .secure_messages_async import AsyncSecureMessagesClient
|
|
74
|
+
from .metrics_async import AsyncMetricsClient
|
|
75
|
+
from .crypto_async import AsyncCryptoPaymentsClient
|
|
76
|
+
from .async_client import AsyncSpapsClient
|
|
77
|
+
from .http_async import RetryAsyncClient
|
|
78
|
+
from .client import SpapsClient
|
|
79
|
+
from .storage import (
|
|
80
|
+
StoredTokens,
|
|
81
|
+
TokenStorage,
|
|
82
|
+
InMemoryTokenStorage,
|
|
83
|
+
FileTokenStorage,
|
|
84
|
+
)
|
|
85
|
+
from .http import RetryConfig, LoggingHooks, default_logging_hooks
|
|
86
|
+
|
|
87
|
+
__all__ = [
|
|
88
|
+
"__version__",
|
|
89
|
+
"AuthClient",
|
|
90
|
+
"AuthError",
|
|
91
|
+
"NonceResponse",
|
|
92
|
+
"TokenPair",
|
|
93
|
+
"TokenUser",
|
|
94
|
+
"SessionsClient",
|
|
95
|
+
"SessionListResult",
|
|
96
|
+
"SessionRecord",
|
|
97
|
+
"SessionTouchResult",
|
|
98
|
+
"SessionRevokeResult",
|
|
99
|
+
"PaymentsClient",
|
|
100
|
+
"PaymentsError",
|
|
101
|
+
"CheckoutSession",
|
|
102
|
+
"PaymentIntent",
|
|
103
|
+
"WalletDeposit",
|
|
104
|
+
"WalletTransaction",
|
|
105
|
+
"SubscriptionPlan",
|
|
106
|
+
"SubscriptionDetail",
|
|
107
|
+
"SubscriptionCancellation",
|
|
108
|
+
"BalanceOverview",
|
|
109
|
+
"BalanceAmounts",
|
|
110
|
+
"UsageSummary",
|
|
111
|
+
"PaymentMethodUpdateResult",
|
|
112
|
+
"WhitelistClient",
|
|
113
|
+
"WhitelistError",
|
|
114
|
+
"WhitelistEntry",
|
|
115
|
+
"WhitelistCheckResult",
|
|
116
|
+
"WhitelistListResult",
|
|
117
|
+
"WhitelistMessage",
|
|
118
|
+
"Settings",
|
|
119
|
+
"create_http_client",
|
|
120
|
+
"SessionError",
|
|
121
|
+
"SessionSummary",
|
|
122
|
+
"SessionValidationResult",
|
|
123
|
+
"CryptoPaymentsClient",
|
|
124
|
+
"CryptoPaymentsError",
|
|
125
|
+
"CryptoInvoice",
|
|
126
|
+
"CryptoInvoiceStatus",
|
|
127
|
+
"CryptoReconcileJob",
|
|
128
|
+
"verify_crypto_webhook_signature",
|
|
129
|
+
"UsageClient",
|
|
130
|
+
"UsageError",
|
|
131
|
+
"UsagePeriod",
|
|
132
|
+
"UsageFeature",
|
|
133
|
+
"UsageFeaturesResponse",
|
|
134
|
+
"UsageRecordUsage",
|
|
135
|
+
"UsageRecordResult",
|
|
136
|
+
"UsageHistoryEntry",
|
|
137
|
+
"UsageHistoryResponse",
|
|
138
|
+
"SecureMessagesClient",
|
|
139
|
+
"SecureMessagesError",
|
|
140
|
+
"SecureMessage",
|
|
141
|
+
"MetricsClient",
|
|
142
|
+
"SpapsClient",
|
|
143
|
+
"AsyncSpapsClient",
|
|
144
|
+
"AsyncAuthClient",
|
|
145
|
+
"AsyncSessionsClient",
|
|
146
|
+
"AsyncPaymentsClient",
|
|
147
|
+
"AsyncUsageClient",
|
|
148
|
+
"AsyncWhitelistClient",
|
|
149
|
+
"AsyncSecureMessagesClient",
|
|
150
|
+
"AsyncMetricsClient",
|
|
151
|
+
"AsyncCryptoPaymentsClient",
|
|
152
|
+
"RetryAsyncClient",
|
|
153
|
+
"StoredTokens",
|
|
154
|
+
"TokenStorage",
|
|
155
|
+
"InMemoryTokenStorage",
|
|
156
|
+
"FileTokenStorage",
|
|
157
|
+
"RetryConfig",
|
|
158
|
+
"LoggingHooks",
|
|
159
|
+
"default_logging_hooks",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
# Temporary development version; replaced during release automation.
|
|
163
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Async equivalents for the SPAPS client suite."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from typing import Optional, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .auth_async import AsyncAuthClient
|
|
11
|
+
from .sessions_async import AsyncSessionsClient
|
|
12
|
+
from .payments_async import AsyncPaymentsClient
|
|
13
|
+
from .usage_async import AsyncUsageClient
|
|
14
|
+
from .whitelist_async import AsyncWhitelistClient
|
|
15
|
+
from .secure_messages_async import AsyncSecureMessagesClient
|
|
16
|
+
from .metrics_async import AsyncMetricsClient
|
|
17
|
+
from .storage import InMemoryTokenStorage, StoredTokens, TokenStorage
|
|
18
|
+
from .config import Settings
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from .http import RetryConfig, LoggingHooks
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AsyncSpapsClient:
|
|
25
|
+
"""Async client mirroring the ergonomics of the synchronous variant."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
base_url: Optional[str] = None,
|
|
31
|
+
api_key: Optional[str] = None,
|
|
32
|
+
request_timeout: Optional[float] = None,
|
|
33
|
+
token_storage: Optional[TokenStorage] = None,
|
|
34
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
35
|
+
retry_config: Optional["RetryConfig"] = None,
|
|
36
|
+
logging_hooks: Optional["LoggingHooks"] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
from .http_async import RetryAsyncClient
|
|
39
|
+
|
|
40
|
+
self.settings = Settings(base_url=base_url, api_key=api_key, request_timeout=request_timeout)
|
|
41
|
+
self._token_storage = token_storage or InMemoryTokenStorage()
|
|
42
|
+
from .http import RetryConfig as _RetryConfig, LoggingHooks as _LoggingHooks
|
|
43
|
+
if isinstance(retry_config, dict):
|
|
44
|
+
retry_config = _RetryConfig(**retry_config)
|
|
45
|
+
if isinstance(logging_hooks, dict):
|
|
46
|
+
logging_hooks = _LoggingHooks(**logging_hooks)
|
|
47
|
+
if http_client is not None:
|
|
48
|
+
self._client = http_client
|
|
49
|
+
self._owns_client = False
|
|
50
|
+
else:
|
|
51
|
+
self._client = RetryAsyncClient(
|
|
52
|
+
base_url=self.settings.base_url.rstrip("/"),
|
|
53
|
+
timeout=self.settings.request_timeout,
|
|
54
|
+
retry_config=retry_config,
|
|
55
|
+
logging_hooks=logging_hooks,
|
|
56
|
+
)
|
|
57
|
+
self._owns_client = True
|
|
58
|
+
|
|
59
|
+
self._auth = AsyncAuthClient(
|
|
60
|
+
base_url=self.settings.base_url,
|
|
61
|
+
api_key=self.settings.api_key,
|
|
62
|
+
client=self._client,
|
|
63
|
+
token_storage=self._token_storage,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self._sessions: Optional[AsyncSessionsClient] = None
|
|
67
|
+
self._payments: Optional[AsyncPaymentsClient] = None
|
|
68
|
+
self._usage: Optional[AsyncUsageClient] = None
|
|
69
|
+
self._whitelist: Optional[AsyncWhitelistClient] = None
|
|
70
|
+
self._secure_messages: Optional[AsyncSecureMessagesClient] = None
|
|
71
|
+
self._metrics: Optional[AsyncMetricsClient] = None
|
|
72
|
+
|
|
73
|
+
# Factories --------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def auth(self) -> AsyncAuthClient:
|
|
77
|
+
return self._auth
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def sessions(self) -> AsyncSessionsClient:
|
|
81
|
+
access_token = self._require_access_token()
|
|
82
|
+
if self._sessions is None:
|
|
83
|
+
self._sessions = AsyncSessionsClient(
|
|
84
|
+
base_url=self.settings.base_url,
|
|
85
|
+
api_key=self.settings.api_key,
|
|
86
|
+
access_token=access_token,
|
|
87
|
+
client=self._client,
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
self._sessions.access_token = access_token
|
|
91
|
+
return self._sessions
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def payments(self) -> AsyncPaymentsClient:
|
|
95
|
+
access_token = self._require_access_token()
|
|
96
|
+
if self._payments is None:
|
|
97
|
+
self._payments = AsyncPaymentsClient(
|
|
98
|
+
base_url=self.settings.base_url,
|
|
99
|
+
api_key=self.settings.api_key,
|
|
100
|
+
access_token=access_token,
|
|
101
|
+
client=self._client,
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
self._payments.access_token = access_token
|
|
105
|
+
return self._payments
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def usage(self) -> AsyncUsageClient:
|
|
109
|
+
access_token = self._require_access_token()
|
|
110
|
+
if self._usage is None:
|
|
111
|
+
self._usage = AsyncUsageClient(
|
|
112
|
+
base_url=self.settings.base_url,
|
|
113
|
+
api_key=self.settings.api_key,
|
|
114
|
+
access_token=access_token,
|
|
115
|
+
client=self._client,
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
self._usage.access_token = access_token
|
|
119
|
+
return self._usage
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def whitelist(self) -> AsyncWhitelistClient:
|
|
123
|
+
access_token = self._require_access_token()
|
|
124
|
+
if self._whitelist is None:
|
|
125
|
+
self._whitelist = AsyncWhitelistClient(
|
|
126
|
+
base_url=self.settings.base_url,
|
|
127
|
+
api_key=self.settings.api_key,
|
|
128
|
+
access_token=access_token,
|
|
129
|
+
client=self._client,
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
self._whitelist.access_token = access_token
|
|
133
|
+
return self._whitelist
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def secure_messages(self) -> AsyncSecureMessagesClient:
|
|
137
|
+
access_token = self._require_access_token()
|
|
138
|
+
if self._secure_messages is None:
|
|
139
|
+
self._secure_messages = AsyncSecureMessagesClient(
|
|
140
|
+
base_url=self.settings.base_url,
|
|
141
|
+
api_key=self.settings.api_key,
|
|
142
|
+
access_token=access_token,
|
|
143
|
+
client=self._client,
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
self._secure_messages.access_token = access_token
|
|
147
|
+
return self._secure_messages
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def metrics(self) -> AsyncMetricsClient:
|
|
151
|
+
if self._metrics is None:
|
|
152
|
+
self._metrics = AsyncMetricsClient(base_url=self.settings.base_url, client=self._client)
|
|
153
|
+
return self._metrics
|
|
154
|
+
|
|
155
|
+
# Token helpers ----------------------------------------------------
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def token_storage(self) -> TokenStorage:
|
|
159
|
+
return self._token_storage
|
|
160
|
+
|
|
161
|
+
def get_tokens(self) -> Optional[StoredTokens]:
|
|
162
|
+
return self._token_storage.load()
|
|
163
|
+
|
|
164
|
+
def set_tokens(
|
|
165
|
+
self,
|
|
166
|
+
*,
|
|
167
|
+
access_token: str,
|
|
168
|
+
refresh_token: Optional[str] = None,
|
|
169
|
+
token_type: Optional[str] = "Bearer",
|
|
170
|
+
expires_in: Optional[int] = None,
|
|
171
|
+
expires_at: Optional[datetime] = None,
|
|
172
|
+
) -> None:
|
|
173
|
+
if expires_at is None and expires_in is not None:
|
|
174
|
+
expires_at = datetime.now(timezone.utc) + timedelta(seconds=expires_in)
|
|
175
|
+
tokens = StoredTokens(
|
|
176
|
+
access_token=access_token,
|
|
177
|
+
refresh_token=refresh_token,
|
|
178
|
+
expires_at=expires_at,
|
|
179
|
+
token_type=token_type,
|
|
180
|
+
)
|
|
181
|
+
self._token_storage.save(tokens)
|
|
182
|
+
|
|
183
|
+
def clear_tokens(self) -> None:
|
|
184
|
+
self._token_storage.clear()
|
|
185
|
+
|
|
186
|
+
# Lifecycle --------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
async def aclose(self) -> None:
|
|
189
|
+
await self._auth.aclose()
|
|
190
|
+
for candidate in (
|
|
191
|
+
self._sessions,
|
|
192
|
+
self._payments,
|
|
193
|
+
self._usage,
|
|
194
|
+
self._whitelist,
|
|
195
|
+
self._secure_messages,
|
|
196
|
+
self._metrics,
|
|
197
|
+
):
|
|
198
|
+
if candidate is not None:
|
|
199
|
+
await candidate.aclose()
|
|
200
|
+
if self._owns_client:
|
|
201
|
+
await self._client.aclose()
|
|
202
|
+
|
|
203
|
+
# Internal helpers -------------------------------------------------
|
|
204
|
+
|
|
205
|
+
def _require_access_token(self) -> str:
|
|
206
|
+
tokens = self._token_storage.load()
|
|
207
|
+
if not tokens or not tokens.access_token:
|
|
208
|
+
raise ValueError(
|
|
209
|
+
"Access token not found. Authenticate via auth helpers or call set_tokens()."
|
|
210
|
+
)
|
|
211
|
+
return tokens.access_token
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
__all__ = ["AsyncSpapsClient"]
|