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.
Files changed (40) hide show
  1. {quantumapi-0.1.0b0/quantumapi.egg-info → quantumapi-0.2.0b1}/PKG-INFO +24 -12
  2. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/README.md +20 -8
  3. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/pyproject.toml +4 -4
  4. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/__init__.py +4 -0
  5. quantumapi-0.2.0b1/quantumapi/client/applications.py +180 -0
  6. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/async_client.py +8 -0
  7. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/base.py +1 -1
  8. quantumapi-0.2.0b1/quantumapi/client/end_users.py +274 -0
  9. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/keys.py +161 -0
  10. quantumapi-0.2.0b1/quantumapi/client/resource_groups.py +75 -0
  11. quantumapi-0.2.0b1/quantumapi/client/roles.py +132 -0
  12. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/secrets.py +161 -3
  13. quantumapi-0.2.0b1/quantumapi/client/sync_client.py +120 -0
  14. quantumapi-0.2.0b1/quantumapi/models/__init__.py +132 -0
  15. quantumapi-0.2.0b1/quantumapi/models/applications.py +105 -0
  16. quantumapi-0.2.0b1/quantumapi/models/end_users.py +105 -0
  17. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/keys.py +56 -0
  18. quantumapi-0.2.0b1/quantumapi/models/resource_groups.py +69 -0
  19. quantumapi-0.2.0b1/quantumapi/models/roles.py +78 -0
  20. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/secrets.py +68 -0
  21. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1/quantumapi.egg-info}/PKG-INFO +24 -12
  22. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/SOURCES.txt +10 -1
  23. quantumapi-0.2.0b1/tests/test_parity.py +669 -0
  24. quantumapi-0.1.0b0/quantumapi/client/sync_client.py +0 -75
  25. quantumapi-0.1.0b0/quantumapi/models/__init__.py +0 -61
  26. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/LICENSE +0 -0
  27. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/__init__.py +0 -0
  28. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/encryption.py +0 -0
  29. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/client/health.py +0 -0
  30. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/errors/__init__.py +0 -0
  31. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/errors/exceptions.py +0 -0
  32. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/encryption.py +0 -0
  33. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/models/health.py +0 -0
  34. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi/py.typed +0 -0
  35. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/dependency_links.txt +0 -0
  36. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/requires.txt +0 -0
  37. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/quantumapi.egg-info/top_level.txt +0 -0
  38. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/setup.cfg +0 -0
  39. {quantumapi-0.1.0b0 → quantumapi-0.2.0b1}/tests/test_exceptions.py +0 -0
  40. {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.1.0b0
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/mislata
10
- Project-URL: Bug Tracker, https://github.com/victorZKov/mislata/issues
11
- Project-URL: Changelog, https://github.com/victorZKov/mislata/blob/main/src/sdk/python-quantumapi/CHANGELOG.md
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
- - [Basic encryption and decryption](./examples/01_encryption_basics.py)
428
- - [Secret management](./examples/02_secret_management.py)
429
- - [Key generation and rotation](./examples/03_key_management.py)
430
- - [User management](./examples/04_user_management.py)
431
- - [Audit log queries](./examples/05_audit_logs.py)
432
- - [Django integration](./examples/frameworks/django_integration.py)
433
- - [Flask integration](./examples/frameworks/flask_integration.py)
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 security@quantumapi.eu. Do not create public issues for security vulnerabilities.
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
- - [Basic encryption and decryption](./examples/01_encryption_basics.py)
387
- - [Secret management](./examples/02_secret_management.py)
388
- - [Key generation and rotation](./examples/03_key_management.py)
389
- - [User management](./examples/04_user_management.py)
390
- - [Audit log queries](./examples/05_audit_logs.py)
391
- - [Django integration](./examples/frameworks/django_integration.py)
392
- - [Flask integration](./examples/frameworks/flask_integration.py)
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 security@quantumapi.eu. Do not create public issues for security vulnerabilities.
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.1.0b0"
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/mislata"
64
- "Bug Tracker" = "https://github.com/victorZKov/mislata/issues"
65
- Changelog = "https://github.com/victorZKov/mislata/blob/main/src/sdk/python-quantumapi/CHANGELOG.md"
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
- "Authorization": f"Bearer {self.api_key}",
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
+ )