quantumapi 0.1.0b0__tar.gz → 0.2.0b1__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.
- {quantumapi-0.1.0b0/quantumapi.egg-info → quantumapi-0.2.0b1}/PKG-INFO +24 -12
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/README.md +20 -8
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/pyproject.toml +4 -4
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/__init__.py +4 -0
- quantumapi-0.2.0b1/quantumapi/client/applications.py +180 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/async_client.py +8 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/base.py +1 -1
- quantumapi-0.2.0b1/quantumapi/client/end_users.py +274 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/keys.py +161 -0
- quantumapi-0.2.0b1/quantumapi/client/resource_groups.py +75 -0
- quantumapi-0.2.0b1/quantumapi/client/roles.py +132 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/secrets.py +161 -3
- quantumapi-0.2.0b1/quantumapi/client/sync_client.py +120 -0
- quantumapi-0.2.0b1/quantumapi/models/__init__.py +132 -0
- quantumapi-0.2.0b1/quantumapi/models/applications.py +105 -0
- quantumapi-0.2.0b1/quantumapi/models/end_users.py +105 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/keys.py +56 -0
- quantumapi-0.2.0b1/quantumapi/models/resource_groups.py +69 -0
- quantumapi-0.2.0b1/quantumapi/models/roles.py +78 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/secrets.py +68 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1/quantumapi.egg-info}/PKG-INFO +24 -12
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/SOURCES.txt +10 -1
- quantumapi-0.2.0b1/tests/test_parity.py +669 -0
- quantumapi-0.1.0b0/quantumapi/client/sync_client.py +0 -75
- quantumapi-0.1.0b0/quantumapi/models/__init__.py +0 -61
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/LICENSE +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/__init__.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/encryption.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/health.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/errors/__init__.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/errors/exceptions.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/encryption.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/health.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/py.typed +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/dependency_links.txt +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/requires.txt +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/top_level.txt +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/setup.cfg +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/tests/test_exceptions.py +0 -0
- {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/tests/test_models.py +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quantumapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0b1
|
|
4
4
|
Summary: Official Python SDK for QuantumAPI - Quantum-safe encryption and identity platform
|
|
5
5
|
Author-email: QuantumAPI Team <developers@quantumapi.eu>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://quantumapi.eu
|
|
8
8
|
Project-URL: Documentation, https://docs.quantumapi.eu/sdk/python
|
|
9
|
-
Project-URL: Repository, https://github.com/victorZKov/
|
|
10
|
-
Project-URL: Bug Tracker, https://github.com/victorZKov/
|
|
11
|
-
Project-URL: Changelog, https://github.com/victorZKov/
|
|
9
|
+
Project-URL: Repository, https://github.com/victorZKov/quantumapi
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/victorZKov/quantumapi/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/victorZKov/quantumapi/blob/main/src/sdk/python-quantumapi/CHANGELOG.md
|
|
12
12
|
Keywords: quantum,encryption,post-quantum,cryptography,security,api,sdk,ml-kem,ml-dsa,pqc
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -424,13 +424,25 @@ client = QuantumAPIClient(
|
|
|
424
424
|
|
|
425
425
|
Check out the [examples/](./examples/) directory for more comprehensive examples:
|
|
426
426
|
|
|
427
|
-
|
|
428
|
-
- [
|
|
429
|
-
- [
|
|
430
|
-
- [
|
|
431
|
-
- [
|
|
432
|
-
- [
|
|
433
|
-
- [
|
|
427
|
+
**Basic Examples:**
|
|
428
|
+
- [01_encryption_basics.py](./examples/01_encryption_basics.py) - Basic encryption and decryption
|
|
429
|
+
- [02_secret_management.py](./examples/02_secret_management.py) - Secret CRUD operations
|
|
430
|
+
- [03_key_management.py](./examples/03_key_management.py) - Key generation and rotation
|
|
431
|
+
- [04_error_handling.py](./examples/04_error_handling.py) - Error handling patterns
|
|
432
|
+
- [05_health_monitoring.py](./examples/05_health_monitoring.py) - Health checks and monitoring
|
|
433
|
+
- [06_configuration.py](./examples/06_configuration.py) - Configuration options
|
|
434
|
+
|
|
435
|
+
**Advanced Secret Management:**
|
|
436
|
+
- [07_versioning.py](./examples/07_versioning.py) - Secret versioning and rotation
|
|
437
|
+
- [08_sharing.py](./examples/08_sharing.py) - Secure secret sharing with access controls
|
|
438
|
+
- [09_batch_operations.py](./examples/09_batch_operations.py) - Batch operations at scale
|
|
439
|
+
|
|
440
|
+
Run any example with:
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
export QUANTUMAPI_API_KEY="qapi_your_key_here"
|
|
444
|
+
python examples/01_encryption_basics.py
|
|
445
|
+
```
|
|
434
446
|
|
|
435
447
|
## Framework Integrations
|
|
436
448
|
|
|
@@ -543,7 +555,7 @@ See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this SDK.
|
|
|
543
555
|
|
|
544
556
|
## Security
|
|
545
557
|
|
|
546
|
-
For security concerns, please email
|
|
558
|
+
For security concerns, please email it@kovimatic.ie. Do not create public issues for security vulnerabilities.
|
|
547
559
|
|
|
548
560
|
## Links
|
|
549
561
|
|
|
@@ -383,13 +383,25 @@ client = QuantumAPIClient(
|
|
|
383
383
|
|
|
384
384
|
Check out the [examples/](./examples/) directory for more comprehensive examples:
|
|
385
385
|
|
|
386
|
-
|
|
387
|
-
- [
|
|
388
|
-
- [
|
|
389
|
-
- [
|
|
390
|
-
- [
|
|
391
|
-
- [
|
|
392
|
-
- [
|
|
386
|
+
**Basic Examples:**
|
|
387
|
+
- [01_encryption_basics.py](./examples/01_encryption_basics.py) - Basic encryption and decryption
|
|
388
|
+
- [02_secret_management.py](./examples/02_secret_management.py) - Secret CRUD operations
|
|
389
|
+
- [03_key_management.py](./examples/03_key_management.py) - Key generation and rotation
|
|
390
|
+
- [04_error_handling.py](./examples/04_error_handling.py) - Error handling patterns
|
|
391
|
+
- [05_health_monitoring.py](./examples/05_health_monitoring.py) - Health checks and monitoring
|
|
392
|
+
- [06_configuration.py](./examples/06_configuration.py) - Configuration options
|
|
393
|
+
|
|
394
|
+
**Advanced Secret Management:**
|
|
395
|
+
- [07_versioning.py](./examples/07_versioning.py) - Secret versioning and rotation
|
|
396
|
+
- [08_sharing.py](./examples/08_sharing.py) - Secure secret sharing with access controls
|
|
397
|
+
- [09_batch_operations.py](./examples/09_batch_operations.py) - Batch operations at scale
|
|
398
|
+
|
|
399
|
+
Run any example with:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
export QUANTUMAPI_API_KEY="qapi_your_key_here"
|
|
403
|
+
python examples/01_encryption_basics.py
|
|
404
|
+
```
|
|
393
405
|
|
|
394
406
|
## Framework Integrations
|
|
395
407
|
|
|
@@ -502,7 +514,7 @@ See [CHANGELOG.md](CHANGELOG.md) for a history of changes to this SDK.
|
|
|
502
514
|
|
|
503
515
|
## Security
|
|
504
516
|
|
|
505
|
-
For security concerns, please email
|
|
517
|
+
For security concerns, please email it@kovimatic.ie. Do not create public issues for security vulnerabilities.
|
|
506
518
|
|
|
507
519
|
## Links
|
|
508
520
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quantumapi"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0b1"
|
|
8
8
|
description = "Official Python SDK for QuantumAPI - Quantum-safe encryption and identity platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -60,9 +60,9 @@ dev = [
|
|
|
60
60
|
[project.urls]
|
|
61
61
|
Homepage = "https://quantumapi.eu"
|
|
62
62
|
Documentation = "https://docs.quantumapi.eu/sdk/python"
|
|
63
|
-
Repository = "https://github.com/victorZKov/
|
|
64
|
-
"Bug Tracker" = "https://github.com/victorZKov/
|
|
65
|
-
Changelog = "https://github.com/victorZKov/
|
|
63
|
+
Repository = "https://github.com/victorZKov/quantumapi"
|
|
64
|
+
"Bug Tracker" = "https://github.com/victorZKov/quantumapi/issues"
|
|
65
|
+
Changelog = "https://github.com/victorZKov/quantumapi/blob/main/src/sdk/python-quantumapi/CHANGELOG.md"
|
|
66
66
|
|
|
67
67
|
[tool.setuptools.packages.find]
|
|
68
68
|
include = ["quantumapi*"]
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Client package for QuantumAPI SDK."""
|
|
2
2
|
|
|
3
|
+
from quantumapi.client.applications import ApplicationsClient
|
|
3
4
|
from quantumapi.client.async_client import QuantumAPIClient
|
|
4
5
|
from quantumapi.client.encryption import EncryptionClient
|
|
6
|
+
from quantumapi.client.end_users import EndUsersClient
|
|
5
7
|
from quantumapi.client.health import HealthClient
|
|
6
8
|
from quantumapi.client.keys import KeysClient
|
|
7
9
|
from quantumapi.client.secrets import SecretsClient
|
|
@@ -10,6 +12,8 @@ from quantumapi.client.sync_client import QuantumAPISyncClient
|
|
|
10
12
|
__all__ = [
|
|
11
13
|
"QuantumAPIClient",
|
|
12
14
|
"QuantumAPISyncClient",
|
|
15
|
+
"ApplicationsClient",
|
|
16
|
+
"EndUsersClient",
|
|
13
17
|
"EncryptionClient",
|
|
14
18
|
"KeysClient",
|
|
15
19
|
"SecretsClient",
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Applications client for QuantumAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from quantumapi.client.base import BaseClient
|
|
7
|
+
from quantumapi.models.applications import (
|
|
8
|
+
Application,
|
|
9
|
+
ApplicationEnvironment,
|
|
10
|
+
ApplicationType,
|
|
11
|
+
CreateApplicationRequest,
|
|
12
|
+
CreateApplicationResponse,
|
|
13
|
+
RotateSecretResponse,
|
|
14
|
+
UpdateApplicationRequest,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ApplicationsClient:
|
|
19
|
+
"""Client for OIDC application management operations."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, base_client: BaseClient) -> None:
|
|
22
|
+
self._client = base_client
|
|
23
|
+
|
|
24
|
+
async def create(
|
|
25
|
+
self,
|
|
26
|
+
name: str,
|
|
27
|
+
application_type: ApplicationType,
|
|
28
|
+
redirect_uris: Optional[List[str]] = None,
|
|
29
|
+
description: Optional[str] = None,
|
|
30
|
+
environment: ApplicationEnvironment = ApplicationEnvironment.DEVELOPMENT,
|
|
31
|
+
post_logout_redirect_uris: Optional[List[str]] = None,
|
|
32
|
+
allowed_scopes: Optional[List[str]] = None,
|
|
33
|
+
) -> CreateApplicationResponse:
|
|
34
|
+
"""
|
|
35
|
+
Create a new OIDC application.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
name: Application name
|
|
39
|
+
application_type: Type (SPA, WebApp, NativeApp, MachineToMachine, Agent)
|
|
40
|
+
redirect_uris: Allowed redirect URIs for the OIDC flow
|
|
41
|
+
description: Optional description
|
|
42
|
+
environment: Target environment (Development, Staging, Production)
|
|
43
|
+
post_logout_redirect_uris: Allowed post-logout redirect URIs
|
|
44
|
+
allowed_scopes: Allowed OAuth2 scopes
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Created application with client secret (shown once for confidential apps)
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> result = await client.applications.create(
|
|
51
|
+
... name="My SPA",
|
|
52
|
+
... application_type=ApplicationType.SPA,
|
|
53
|
+
... redirect_uris=["http://localhost:3000/callback"],
|
|
54
|
+
... )
|
|
55
|
+
>>> print(result.application.client_id)
|
|
56
|
+
"""
|
|
57
|
+
request = CreateApplicationRequest(
|
|
58
|
+
name=name,
|
|
59
|
+
application_type=application_type,
|
|
60
|
+
redirect_uris=redirect_uris or [],
|
|
61
|
+
description=description,
|
|
62
|
+
environment=environment,
|
|
63
|
+
post_logout_redirect_uris=post_logout_redirect_uris,
|
|
64
|
+
allowed_scopes=allowed_scopes,
|
|
65
|
+
)
|
|
66
|
+
response = await self._client.post(
|
|
67
|
+
"/api/v1/applications",
|
|
68
|
+
json_data=request.model_dump(by_alias=True, exclude_none=True),
|
|
69
|
+
)
|
|
70
|
+
return CreateApplicationResponse(**response)
|
|
71
|
+
|
|
72
|
+
async def list(self) -> List[Application]:
|
|
73
|
+
"""
|
|
74
|
+
List all applications for the current tenant.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of applications
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> apps = await client.applications.list()
|
|
81
|
+
>>> for app in apps:
|
|
82
|
+
... print(app.display_name)
|
|
83
|
+
"""
|
|
84
|
+
response = await self._client.get("/api/v1/applications")
|
|
85
|
+
return [Application(**item) for item in response]
|
|
86
|
+
|
|
87
|
+
async def get(self, application_id: UUID) -> Application:
|
|
88
|
+
"""
|
|
89
|
+
Get application details by ID.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
application_id: Application ID
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Application details
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
>>> app = await client.applications.get(app_id)
|
|
99
|
+
>>> print(app.display_name)
|
|
100
|
+
"""
|
|
101
|
+
response = await self._client.get(f"/api/v1/applications/{application_id}")
|
|
102
|
+
return Application(**response)
|
|
103
|
+
|
|
104
|
+
async def update(
|
|
105
|
+
self,
|
|
106
|
+
application_id: UUID,
|
|
107
|
+
display_name: str,
|
|
108
|
+
environment: ApplicationEnvironment,
|
|
109
|
+
redirect_uris: List[str],
|
|
110
|
+
description: Optional[str] = None,
|
|
111
|
+
post_logout_redirect_uris: Optional[List[str]] = None,
|
|
112
|
+
allowed_scopes: Optional[List[str]] = None,
|
|
113
|
+
) -> Application:
|
|
114
|
+
"""
|
|
115
|
+
Update an application.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
application_id: Application ID
|
|
119
|
+
display_name: Updated display name
|
|
120
|
+
environment: Updated environment
|
|
121
|
+
redirect_uris: Updated redirect URIs
|
|
122
|
+
description: Updated description
|
|
123
|
+
post_logout_redirect_uris: Updated post-logout redirect URIs
|
|
124
|
+
allowed_scopes: Updated allowed scopes
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Updated application
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
>>> app = await client.applications.update(
|
|
131
|
+
... app_id,
|
|
132
|
+
... display_name="Updated Name",
|
|
133
|
+
... environment=ApplicationEnvironment.PRODUCTION,
|
|
134
|
+
... redirect_uris=["https://myapp.com/callback"],
|
|
135
|
+
... )
|
|
136
|
+
"""
|
|
137
|
+
request = UpdateApplicationRequest(
|
|
138
|
+
display_name=display_name,
|
|
139
|
+
description=description,
|
|
140
|
+
environment=environment,
|
|
141
|
+
redirect_uris=redirect_uris,
|
|
142
|
+
post_logout_redirect_uris=post_logout_redirect_uris,
|
|
143
|
+
allowed_scopes=allowed_scopes,
|
|
144
|
+
)
|
|
145
|
+
response = await self._client.put(
|
|
146
|
+
f"/api/v1/applications/{application_id}",
|
|
147
|
+
json_data=request.model_dump(by_alias=True, exclude_none=True),
|
|
148
|
+
)
|
|
149
|
+
return Application(**response)
|
|
150
|
+
|
|
151
|
+
async def delete(self, application_id: UUID) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Delete an application.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
application_id: Application ID
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> await client.applications.delete(app_id)
|
|
160
|
+
"""
|
|
161
|
+
await self._client.delete(f"/api/v1/applications/{application_id}")
|
|
162
|
+
|
|
163
|
+
async def rotate_secret(self, application_id: UUID) -> RotateSecretResponse:
|
|
164
|
+
"""
|
|
165
|
+
Rotate the client secret for a confidential application.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
application_id: Application ID
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
New client credentials
|
|
172
|
+
|
|
173
|
+
Example:
|
|
174
|
+
>>> creds = await client.applications.rotate_secret(app_id)
|
|
175
|
+
>>> print(creds.client_secret)
|
|
176
|
+
"""
|
|
177
|
+
response = await self._client.post(
|
|
178
|
+
f"/api/v1/applications/{application_id}/rotate-secret"
|
|
179
|
+
)
|
|
180
|
+
return RotateSecretResponse(**response)
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
+
from quantumapi.client.applications import ApplicationsClient
|
|
6
7
|
from quantumapi.client.base import BaseClient
|
|
7
8
|
from quantumapi.client.encryption import EncryptionClient
|
|
9
|
+
from quantumapi.client.end_users import EndUsersClient
|
|
8
10
|
from quantumapi.client.health import HealthClient
|
|
9
11
|
from quantumapi.client.keys import KeysClient
|
|
12
|
+
from quantumapi.client.resource_groups import ResourceGroupsClient
|
|
13
|
+
from quantumapi.client.roles import RolesClient
|
|
10
14
|
from quantumapi.client.secrets import SecretsClient
|
|
11
15
|
|
|
12
16
|
|
|
@@ -78,6 +82,10 @@ class QuantumAPIClient:
|
|
|
78
82
|
self.encryption = EncryptionClient(self._base_client)
|
|
79
83
|
self.keys = KeysClient(self._base_client)
|
|
80
84
|
self.secrets = SecretsClient(self._base_client)
|
|
85
|
+
self.resource_groups = ResourceGroupsClient(self._base_client)
|
|
86
|
+
self.applications = ApplicationsClient(self._base_client)
|
|
87
|
+
self.end_users = EndUsersClient(self._base_client)
|
|
88
|
+
self.roles = RolesClient(self._base_client)
|
|
81
89
|
self.health = HealthClient(self._base_client)
|
|
82
90
|
|
|
83
91
|
async def close(self) -> None:
|
|
@@ -62,7 +62,7 @@ class BaseClient:
|
|
|
62
62
|
def _get_default_headers(self) -> Dict[str, str]:
|
|
63
63
|
"""Get default headers for all requests."""
|
|
64
64
|
return {
|
|
65
|
-
"
|
|
65
|
+
"X-Api-Key": self.api_key,
|
|
66
66
|
"Content-Type": "application/json",
|
|
67
67
|
"Accept": "application/json",
|
|
68
68
|
"User-Agent": "quantumapi-python/0.1.0-beta",
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""End-users client for QuantumAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from quantumapi.client.base import BaseClient
|
|
7
|
+
from quantumapi.models.end_users import (
|
|
8
|
+
CreateEndUserRequest,
|
|
9
|
+
EndUser,
|
|
10
|
+
EndUserListResponse,
|
|
11
|
+
EndUserRole,
|
|
12
|
+
UpdateEndUserRequest,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EndUsersClient:
|
|
17
|
+
"""Client for end-user management operations scoped to an application."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, base_client: BaseClient) -> None:
|
|
20
|
+
self._client = base_client
|
|
21
|
+
|
|
22
|
+
def _base_url(self, app_id: UUID) -> str:
|
|
23
|
+
return f"/api/v1/applications/{app_id}/end-users"
|
|
24
|
+
|
|
25
|
+
async def create(
|
|
26
|
+
self,
|
|
27
|
+
app_id: UUID,
|
|
28
|
+
email: str,
|
|
29
|
+
display_name: str,
|
|
30
|
+
first_name: Optional[str] = None,
|
|
31
|
+
last_name: Optional[str] = None,
|
|
32
|
+
phone_number: Optional[str] = None,
|
|
33
|
+
password: Optional[str] = None,
|
|
34
|
+
email_verified: bool = False,
|
|
35
|
+
metadata: Optional[str] = None,
|
|
36
|
+
role_ids: Optional[List[UUID]] = None,
|
|
37
|
+
) -> EndUser:
|
|
38
|
+
"""
|
|
39
|
+
Create a new end-user for an application.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
app_id: Application ID
|
|
43
|
+
email: User's email address
|
|
44
|
+
display_name: Display name
|
|
45
|
+
first_name: First name
|
|
46
|
+
last_name: Last name
|
|
47
|
+
phone_number: Phone number
|
|
48
|
+
password: Initial password (if None, user must reset via email)
|
|
49
|
+
email_verified: Mark email as already verified
|
|
50
|
+
metadata: Custom metadata (JSON string)
|
|
51
|
+
role_ids: Role IDs to assign
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Created end-user
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> user = await client.end_users.create(
|
|
58
|
+
... app_id=app_id,
|
|
59
|
+
... email="john@example.com",
|
|
60
|
+
... display_name="John Doe",
|
|
61
|
+
... email_verified=True,
|
|
62
|
+
... )
|
|
63
|
+
"""
|
|
64
|
+
request = CreateEndUserRequest(
|
|
65
|
+
email=email,
|
|
66
|
+
display_name=display_name,
|
|
67
|
+
first_name=first_name,
|
|
68
|
+
last_name=last_name,
|
|
69
|
+
phone_number=phone_number,
|
|
70
|
+
password=password,
|
|
71
|
+
email_verified=email_verified,
|
|
72
|
+
metadata=metadata,
|
|
73
|
+
role_ids=role_ids,
|
|
74
|
+
)
|
|
75
|
+
response = await self._client.post(
|
|
76
|
+
self._base_url(app_id),
|
|
77
|
+
json_data=request.model_dump(by_alias=True, exclude_none=True),
|
|
78
|
+
)
|
|
79
|
+
return EndUser(**response)
|
|
80
|
+
|
|
81
|
+
async def list(
|
|
82
|
+
self,
|
|
83
|
+
app_id: UUID,
|
|
84
|
+
page: int = 1,
|
|
85
|
+
page_size: int = 20,
|
|
86
|
+
search: Optional[str] = None,
|
|
87
|
+
blocked: Optional[bool] = None,
|
|
88
|
+
email_verified: Optional[bool] = None,
|
|
89
|
+
role_id: Optional[UUID] = None,
|
|
90
|
+
) -> EndUserListResponse:
|
|
91
|
+
"""
|
|
92
|
+
List end-users for an application (paginated).
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
app_id: Application ID
|
|
96
|
+
page: Page number (default 1)
|
|
97
|
+
page_size: Items per page (default 20, max 100)
|
|
98
|
+
search: Search by email or name
|
|
99
|
+
blocked: Filter by blocked status
|
|
100
|
+
email_verified: Filter by email verification status
|
|
101
|
+
role_id: Filter by role ID
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Paginated list of end-users
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
>>> result = await client.end_users.list(app_id, search="john")
|
|
108
|
+
>>> for user in result.items:
|
|
109
|
+
... print(user.email)
|
|
110
|
+
"""
|
|
111
|
+
params: dict = {"page": page, "pageSize": page_size}
|
|
112
|
+
if search is not None:
|
|
113
|
+
params["search"] = search
|
|
114
|
+
if blocked is not None:
|
|
115
|
+
params["blocked"] = str(blocked).lower()
|
|
116
|
+
if email_verified is not None:
|
|
117
|
+
params["emailVerified"] = str(email_verified).lower()
|
|
118
|
+
if role_id is not None:
|
|
119
|
+
params["roleId"] = str(role_id)
|
|
120
|
+
|
|
121
|
+
response = await self._client.get(self._base_url(app_id), params=params)
|
|
122
|
+
return EndUserListResponse(**response)
|
|
123
|
+
|
|
124
|
+
async def get(self, app_id: UUID, user_id: UUID) -> EndUser:
|
|
125
|
+
"""
|
|
126
|
+
Get end-user details.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
app_id: Application ID
|
|
130
|
+
user_id: End-user ID
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
End-user details
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
>>> user = await client.end_users.get(app_id, user_id)
|
|
137
|
+
>>> print(user.display_name)
|
|
138
|
+
"""
|
|
139
|
+
response = await self._client.get(f"{self._base_url(app_id)}/{user_id}")
|
|
140
|
+
return EndUser(**response)
|
|
141
|
+
|
|
142
|
+
async def update(
|
|
143
|
+
self,
|
|
144
|
+
app_id: UUID,
|
|
145
|
+
user_id: UUID,
|
|
146
|
+
display_name: Optional[str] = None,
|
|
147
|
+
first_name: Optional[str] = None,
|
|
148
|
+
last_name: Optional[str] = None,
|
|
149
|
+
phone_number: Optional[str] = None,
|
|
150
|
+
profile_picture_url: Optional[str] = None,
|
|
151
|
+
metadata: Optional[str] = None,
|
|
152
|
+
) -> EndUser:
|
|
153
|
+
"""
|
|
154
|
+
Update an end-user.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
app_id: Application ID
|
|
158
|
+
user_id: End-user ID
|
|
159
|
+
display_name: Updated display name
|
|
160
|
+
first_name: Updated first name
|
|
161
|
+
last_name: Updated last name
|
|
162
|
+
phone_number: Updated phone number
|
|
163
|
+
profile_picture_url: Updated profile picture URL
|
|
164
|
+
metadata: Updated custom metadata (JSON string)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Updated end-user
|
|
168
|
+
|
|
169
|
+
Example:
|
|
170
|
+
>>> user = await client.end_users.update(
|
|
171
|
+
... app_id, user_id, display_name="Jane Doe"
|
|
172
|
+
... )
|
|
173
|
+
"""
|
|
174
|
+
request = UpdateEndUserRequest(
|
|
175
|
+
display_name=display_name,
|
|
176
|
+
first_name=first_name,
|
|
177
|
+
last_name=last_name,
|
|
178
|
+
phone_number=phone_number,
|
|
179
|
+
profile_picture_url=profile_picture_url,
|
|
180
|
+
metadata=metadata,
|
|
181
|
+
)
|
|
182
|
+
response = await self._client.put(
|
|
183
|
+
f"{self._base_url(app_id)}/{user_id}",
|
|
184
|
+
json_data=request.model_dump(by_alias=True, exclude_none=True),
|
|
185
|
+
)
|
|
186
|
+
return EndUser(**response)
|
|
187
|
+
|
|
188
|
+
async def block(
|
|
189
|
+
self, app_id: UUID, user_id: UUID, reason: Optional[str] = None
|
|
190
|
+
) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Block an end-user.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
app_id: Application ID
|
|
196
|
+
user_id: End-user ID
|
|
197
|
+
reason: Optional reason for blocking
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
>>> await client.end_users.block(app_id, user_id, reason="Suspicious activity")
|
|
201
|
+
"""
|
|
202
|
+
json_data = {"reason": reason} if reason else {}
|
|
203
|
+
await self._client.post(
|
|
204
|
+
f"{self._base_url(app_id)}/{user_id}/block",
|
|
205
|
+
json_data=json_data if json_data else None,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
async def unblock(self, app_id: UUID, user_id: UUID) -> None:
|
|
209
|
+
"""
|
|
210
|
+
Unblock an end-user.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
app_id: Application ID
|
|
214
|
+
user_id: End-user ID
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
>>> await client.end_users.unblock(app_id, user_id)
|
|
218
|
+
"""
|
|
219
|
+
await self._client.post(f"{self._base_url(app_id)}/{user_id}/unblock")
|
|
220
|
+
|
|
221
|
+
async def delete(self, app_id: UUID, user_id: UUID) -> None:
|
|
222
|
+
"""
|
|
223
|
+
Delete an end-user permanently.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
app_id: Application ID
|
|
227
|
+
user_id: End-user ID
|
|
228
|
+
|
|
229
|
+
Example:
|
|
230
|
+
>>> await client.end_users.delete(app_id, user_id)
|
|
231
|
+
"""
|
|
232
|
+
await self._client.delete(f"{self._base_url(app_id)}/{user_id}")
|
|
233
|
+
|
|
234
|
+
async def list_roles(self, app_id: UUID, user_id: UUID) -> List[EndUserRole]:
|
|
235
|
+
"""
|
|
236
|
+
List the roles currently assigned to an end-user.
|
|
237
|
+
|
|
238
|
+
Example:
|
|
239
|
+
>>> roles = await client.end_users.list_roles(app_id, user_id)
|
|
240
|
+
>>> for r in roles:
|
|
241
|
+
... print(r.role_name)
|
|
242
|
+
"""
|
|
243
|
+
response = await self._client.get(f"{self._base_url(app_id)}/{user_id}/roles")
|
|
244
|
+
return [EndUserRole(**r) for r in response]
|
|
245
|
+
|
|
246
|
+
async def assign_role(
|
|
247
|
+
self, app_id: UUID, user_id: UUID, role_id: UUID
|
|
248
|
+
) -> EndUserRole:
|
|
249
|
+
"""
|
|
250
|
+
Assign a role to an end-user.
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> assignment = await client.end_users.assign_role(
|
|
254
|
+
... app_id, user_id, role_id
|
|
255
|
+
... )
|
|
256
|
+
"""
|
|
257
|
+
response = await self._client.post(
|
|
258
|
+
f"{self._base_url(app_id)}/{user_id}/roles",
|
|
259
|
+
json_data={"roleId": str(role_id)},
|
|
260
|
+
)
|
|
261
|
+
return EndUserRole(**response)
|
|
262
|
+
|
|
263
|
+
async def remove_role(
|
|
264
|
+
self, app_id: UUID, user_id: UUID, role_id: UUID
|
|
265
|
+
) -> None:
|
|
266
|
+
"""
|
|
267
|
+
Remove a role from an end-user.
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> await client.end_users.remove_role(app_id, user_id, role_id)
|
|
271
|
+
"""
|
|
272
|
+
await self._client.delete(
|
|
273
|
+
f"{self._base_url(app_id)}/{user_id}/roles/{role_id}"
|
|
274
|
+
)
|