valentina-python-client 2.1.0__tar.gz → 2.3.0__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.
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/PKG-INFO +1 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/pyproject.toml +1 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/__init__.py +1 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/registry.py +1 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/base.py +9 -2
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/global_admin.py +262 -22
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/constants.py +8 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/endpoints.py +4 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/__init__.py +10 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/characters.py +6 -2
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/companies.py +4 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/global_admin.py +33 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/users.py +35 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/registry.py +1 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/base.py +3 -1
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/global_admin.py +277 -19
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/testing/__init__.py +4 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/testing/_factories.py +15 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/testing/_router.py +15 -2
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/testing/_routes.py +14 -2
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/validate_constants.py +1 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/LICENSE +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/README.md +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_codegen.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/__init__.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/client.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/__init__.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/_audit_params.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/campaign_book_chapters.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/campaign_books.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/campaigns.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/character_autogen.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/character_blueprint.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/character_traits.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/characters.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/companies.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/developers.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/dicerolls.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/dictionary.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/options.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/system.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/user_lookup.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/user_self_registration.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/users.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/testing/__init__.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/testing/_client.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/client.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/config.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/exceptions.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/audit_logs.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/books.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/campaigns.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/chapters.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/character_autogen.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/character_blueprint.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/character_trait.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/developers.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/diceroll.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/dictionary.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/full_sheet.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/pagination.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/shared.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/system.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/user_lookup.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/py.typed +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/__init__.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/_audit_params.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/campaign_book_chapters.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/campaign_books.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/campaigns.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/character_autogen.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/character_blueprint.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/character_traits.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/characters.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/companies.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/developers.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/dicerolls.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/dictionary.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/options.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/system.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/user_lookup.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/user_self_registration.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/services/users.py +0 -0
- {valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/testing/_client.py +0 -0
{valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/registry.py
RENAMED
|
@@ -160,7 +160,7 @@ def sync_global_admin_service() -> "SyncGlobalAdminService":
|
|
|
160
160
|
Example:
|
|
161
161
|
```python
|
|
162
162
|
admins = sync_global_admin_service()
|
|
163
|
-
developers = await admins.
|
|
163
|
+
developers = await admins.list_all_developers()
|
|
164
164
|
```
|
|
165
165
|
"""
|
|
166
166
|
from vclient._sync.services.global_admin import SyncGlobalAdminService
|
{valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/_sync/services/base.py
RENAMED
|
@@ -381,17 +381,24 @@ class SyncBaseService:
|
|
|
381
381
|
return None
|
|
382
382
|
return SyncBaseService._parse_rate_limit_header_value(rate_limit_header, "r")
|
|
383
383
|
|
|
384
|
-
def _get(
|
|
384
|
+
def _get(
|
|
385
|
+
self,
|
|
386
|
+
path: str,
|
|
387
|
+
*,
|
|
388
|
+
params: dict[str, Any] | None = None,
|
|
389
|
+
headers: dict[str, str] | None = None,
|
|
390
|
+
) -> httpx.Response:
|
|
385
391
|
"""Make a GET request.
|
|
386
392
|
|
|
387
393
|
Args:
|
|
388
394
|
path: API endpoint path.
|
|
389
395
|
params: Query parameters.
|
|
396
|
+
headers: Additional headers (e.g. an Accept override for binary downloads).
|
|
390
397
|
|
|
391
398
|
Returns:
|
|
392
399
|
The HTTP response.
|
|
393
400
|
"""
|
|
394
|
-
return self._request("GET", path, params=params)
|
|
401
|
+
return self._request("GET", path, params=params, headers=headers)
|
|
395
402
|
|
|
396
403
|
def _merge_on_behalf_of_header(self, headers: dict[str, str] | None) -> dict[str, str] | None:
|
|
397
404
|
"""Merge the On-Behalf-Of header into headers when _on_behalf_of is set.
|
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
# AUTO-GENERATED — do not edit. Run 'uv run duty generate_sync' to regenerate.
|
|
2
2
|
"""Service for interacting with the Global Admin API."""
|
|
3
3
|
|
|
4
|
+
import re
|
|
4
5
|
from collections.abc import Iterator, Sequence
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
|
|
7
8
|
from vclient._sync.services.base import SyncBaseService
|
|
8
|
-
from vclient.constants import
|
|
9
|
+
from vclient.constants import (
|
|
10
|
+
DEFAULT_LOG_TAIL_LIMIT,
|
|
11
|
+
DEFAULT_PAGE_LIMIT,
|
|
12
|
+
MAX_LOG_TAIL_LIMIT,
|
|
13
|
+
MIN_LOG_TAIL_LIMIT,
|
|
14
|
+
AuditEntityType,
|
|
15
|
+
AuditLogInclude,
|
|
16
|
+
AuditOperation,
|
|
17
|
+
LogLevel,
|
|
18
|
+
UserRole,
|
|
19
|
+
)
|
|
9
20
|
from vclient.endpoints import Endpoints
|
|
10
21
|
from vclient.models import (
|
|
22
|
+
AdminUser,
|
|
23
|
+
AdminUserCreate,
|
|
24
|
+
AdminUserUpdate,
|
|
11
25
|
AuditLog,
|
|
12
26
|
AuditLogDetail,
|
|
13
27
|
Developer,
|
|
@@ -15,23 +29,48 @@ from vclient.models import (
|
|
|
15
29
|
DeveloperUpdate,
|
|
16
30
|
DeveloperWithApiKey,
|
|
17
31
|
PaginatedResponse,
|
|
32
|
+
ServerLogArchive,
|
|
33
|
+
ServerLogEntry,
|
|
18
34
|
)
|
|
19
35
|
from vclient.services._audit_params import _build_audit_params
|
|
20
36
|
|
|
37
|
+
_CONTENT_DISPOSITION_FILENAME = re.compile('filename=(?:"([^"]+)"|([^;]+))', re.IGNORECASE)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _filename_from_content_disposition(header: str | None, *, fallback: str) -> str:
|
|
41
|
+
"""Extract the attachment filename from a Content-Disposition header.
|
|
42
|
+
|
|
43
|
+
Return ``fallback`` when the header is absent or contains no filename so callers
|
|
44
|
+
always get a usable name for the downloaded archive.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
header: The raw Content-Disposition header value, or None.
|
|
48
|
+
fallback: Filename to return when none can be parsed.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The parsed filename, or the fallback.
|
|
52
|
+
"""
|
|
53
|
+
if not header:
|
|
54
|
+
return fallback
|
|
55
|
+
match = _CONTENT_DISPOSITION_FILENAME.search(header)
|
|
56
|
+
if not match:
|
|
57
|
+
return fallback
|
|
58
|
+
return (match.group(1) or match.group(2)).strip()
|
|
59
|
+
|
|
21
60
|
|
|
22
61
|
class SyncGlobalAdminService(SyncBaseService):
|
|
23
62
|
"""Service for global admin operations in the Valentina API.
|
|
24
63
|
|
|
25
|
-
Provides
|
|
26
|
-
|
|
64
|
+
Provides cross-company management of developer accounts and their API keys,
|
|
65
|
+
plus server log access. Requires global admin privileges.
|
|
27
66
|
|
|
28
67
|
Example:
|
|
29
68
|
>>> async with SyncVClient() as client:
|
|
30
|
-
... developers = await client.global_admin.
|
|
31
|
-
... developer = await client.global_admin.
|
|
69
|
+
... developers = await client.global_admin.list_all_developers()
|
|
70
|
+
... developer = await client.global_admin.get_developer("developer_id")
|
|
32
71
|
"""
|
|
33
72
|
|
|
34
|
-
def
|
|
73
|
+
def get_developer_page(
|
|
35
74
|
self,
|
|
36
75
|
*,
|
|
37
76
|
limit: int = DEFAULT_PAGE_LIMIT,
|
|
@@ -55,11 +94,11 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
55
94
|
Endpoints.ADMIN_DEVELOPERS, Developer, limit=limit, offset=offset, params=params
|
|
56
95
|
)
|
|
57
96
|
|
|
58
|
-
def
|
|
97
|
+
def list_all_developers(self, *, is_global_admin: bool | None = None) -> list[Developer]:
|
|
59
98
|
"""Retrieve all developer accounts.
|
|
60
99
|
|
|
61
|
-
Automatically paginates through all results. Use `
|
|
62
|
-
or `
|
|
100
|
+
Automatically paginates through all results. Use `get_developer_page()` for paginated
|
|
101
|
+
access or `iter_all_developers()` for memory-efficient streaming of large datasets.
|
|
63
102
|
|
|
64
103
|
Args:
|
|
65
104
|
is_global_admin: Optional filter by global admin status.
|
|
@@ -67,9 +106,11 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
67
106
|
Returns:
|
|
68
107
|
A list of all Developer objects.
|
|
69
108
|
"""
|
|
70
|
-
return [
|
|
109
|
+
return [
|
|
110
|
+
developer for developer in self.iter_all_developers(is_global_admin=is_global_admin)
|
|
111
|
+
]
|
|
71
112
|
|
|
72
|
-
def
|
|
113
|
+
def iter_all_developers(
|
|
73
114
|
self, *, limit: int = 100, is_global_admin: bool | None = None
|
|
74
115
|
) -> Iterator[Developer]:
|
|
75
116
|
"""Iterate through all developer accounts.
|
|
@@ -85,18 +126,14 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
85
126
|
Individual Developer objects.
|
|
86
127
|
|
|
87
128
|
Example:
|
|
88
|
-
>>> async for developer in client.global_admin.
|
|
129
|
+
>>> async for developer in client.global_admin.iter_all_developers():
|
|
89
130
|
... print(developer.username)
|
|
90
131
|
"""
|
|
91
|
-
params =
|
|
92
|
-
|
|
93
|
-
params["is_global_admin"] = is_global_admin
|
|
94
|
-
for item in self._iter_all_pages(
|
|
95
|
-
Endpoints.ADMIN_DEVELOPERS, limit=limit, params=params or None
|
|
96
|
-
):
|
|
132
|
+
params = self._build_params(is_global_admin=is_global_admin)
|
|
133
|
+
for item in self._iter_all_pages(Endpoints.ADMIN_DEVELOPERS, limit=limit, params=params):
|
|
97
134
|
yield Developer.model_validate(item)
|
|
98
135
|
|
|
99
|
-
def
|
|
136
|
+
def get_developer(self, developer_id: str) -> Developer:
|
|
100
137
|
"""Retrieve detailed information about a specific developer.
|
|
101
138
|
|
|
102
139
|
Args:
|
|
@@ -112,7 +149,7 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
112
149
|
response = self._get(Endpoints.ADMIN_DEVELOPER.format(developer_id=developer_id))
|
|
113
150
|
return Developer.model_validate(response.json())
|
|
114
151
|
|
|
115
|
-
def
|
|
152
|
+
def create_developer(self, request: DeveloperCreate | None = None, **kwargs) -> Developer:
|
|
116
153
|
"""Create a new developer account.
|
|
117
154
|
|
|
118
155
|
This creates the account but does not create an API key or grant access to any
|
|
@@ -139,7 +176,7 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
139
176
|
)
|
|
140
177
|
return Developer.model_validate(response.json())
|
|
141
178
|
|
|
142
|
-
def
|
|
179
|
+
def update_developer(
|
|
143
180
|
self, developer_id: str, request: DeveloperUpdate | None = None, **kwargs
|
|
144
181
|
) -> Developer:
|
|
145
182
|
"""Modify a developer account's properties.
|
|
@@ -169,7 +206,7 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
169
206
|
)
|
|
170
207
|
return Developer.model_validate(response.json())
|
|
171
208
|
|
|
172
|
-
def
|
|
209
|
+
def delete_developer(self, developer_id: str) -> None:
|
|
173
210
|
"""Remove a developer account from the system.
|
|
174
211
|
|
|
175
212
|
The developer's API key will be invalidated immediately.
|
|
@@ -183,6 +220,165 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
183
220
|
"""
|
|
184
221
|
self._delete(Endpoints.ADMIN_DEVELOPER.format(developer_id=developer_id))
|
|
185
222
|
|
|
223
|
+
def get_user_page(
|
|
224
|
+
self,
|
|
225
|
+
*,
|
|
226
|
+
company_id: str | None = None,
|
|
227
|
+
role: UserRole | None = None,
|
|
228
|
+
email: str | None = None,
|
|
229
|
+
is_archived: bool | None = None,
|
|
230
|
+
limit: int = DEFAULT_PAGE_LIMIT,
|
|
231
|
+
offset: int = 0,
|
|
232
|
+
) -> PaginatedResponse[AdminUser]:
|
|
233
|
+
"""Retrieve a paginated page of users across all companies.
|
|
234
|
+
|
|
235
|
+
Authenticates with the global-admin API key only; no On-Behalf-Of header
|
|
236
|
+
is sent. Archived (soft-deleted) users are included when
|
|
237
|
+
``is_archived=True``.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
company_id: Filter to a single company.
|
|
241
|
+
role: Filter by user role.
|
|
242
|
+
email: Filter by exact email match.
|
|
243
|
+
is_archived: Filter by archived state.
|
|
244
|
+
limit: Maximum number of items to return (0-100, default 10).
|
|
245
|
+
offset: Number of items to skip from the beginning (default 0).
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
A PaginatedResponse containing AdminUser objects and pagination metadata.
|
|
249
|
+
"""
|
|
250
|
+
params = self._build_params(
|
|
251
|
+
company_id=company_id, role=role, email=email, is_archived=is_archived
|
|
252
|
+
)
|
|
253
|
+
return self._get_paginated_as(
|
|
254
|
+
Endpoints.ADMIN_USERS, AdminUser, limit=limit, offset=offset, params=params
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def list_all_users(
|
|
258
|
+
self,
|
|
259
|
+
*,
|
|
260
|
+
company_id: str | None = None,
|
|
261
|
+
role: UserRole | None = None,
|
|
262
|
+
email: str | None = None,
|
|
263
|
+
is_archived: bool | None = None,
|
|
264
|
+
) -> list[AdminUser]:
|
|
265
|
+
"""Retrieve all users across all companies.
|
|
266
|
+
|
|
267
|
+
Automatically paginates through all results. Use ``get_user_page()`` for
|
|
268
|
+
paginated access or ``iter_all_users()`` for memory-efficient streaming.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
company_id: Filter to a single company.
|
|
272
|
+
role: Filter by user role.
|
|
273
|
+
email: Filter by exact email match.
|
|
274
|
+
is_archived: Filter by archived state.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
A list of all matching AdminUser objects.
|
|
278
|
+
"""
|
|
279
|
+
return [
|
|
280
|
+
user
|
|
281
|
+
for user in self.iter_all_users(
|
|
282
|
+
company_id=company_id, role=role, email=email, is_archived=is_archived
|
|
283
|
+
)
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
def iter_all_users(
|
|
287
|
+
self,
|
|
288
|
+
*,
|
|
289
|
+
limit: int = 100,
|
|
290
|
+
company_id: str | None = None,
|
|
291
|
+
role: UserRole | None = None,
|
|
292
|
+
email: str | None = None,
|
|
293
|
+
is_archived: bool | None = None,
|
|
294
|
+
) -> Iterator[AdminUser]:
|
|
295
|
+
"""Iterate through all users across all companies.
|
|
296
|
+
|
|
297
|
+
Yields individual users, automatically fetching subsequent pages until all
|
|
298
|
+
matching users have been retrieved.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
limit: Items per page (default 100 for efficiency).
|
|
302
|
+
company_id: Filter to a single company.
|
|
303
|
+
role: Filter by user role.
|
|
304
|
+
email: Filter by exact email match.
|
|
305
|
+
is_archived: Filter by archived state.
|
|
306
|
+
|
|
307
|
+
Yields:
|
|
308
|
+
Individual AdminUser objects.
|
|
309
|
+
"""
|
|
310
|
+
params = self._build_params(
|
|
311
|
+
company_id=company_id, role=role, email=email, is_archived=is_archived
|
|
312
|
+
)
|
|
313
|
+
for item in self._iter_all_pages(Endpoints.ADMIN_USERS, limit=limit, params=params):
|
|
314
|
+
yield AdminUser.model_validate(item)
|
|
315
|
+
|
|
316
|
+
def get_user(self, user_id: str) -> AdminUser:
|
|
317
|
+
"""Retrieve a single user by ID, including archived users.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
user_id: The ID of the user to retrieve.
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
The AdminUser object, with ``is_archived`` reflecting soft-delete state.
|
|
324
|
+
"""
|
|
325
|
+
response = self._get(Endpoints.ADMIN_USER.format(user_id=user_id))
|
|
326
|
+
return AdminUser.model_validate(response.json())
|
|
327
|
+
|
|
328
|
+
def create_user(self, request: AdminUserCreate | None = None, **kwargs) -> AdminUser:
|
|
329
|
+
"""Create a user in a target company.
|
|
330
|
+
|
|
331
|
+
The role assignment matrix enforced on company-scoped endpoints does not
|
|
332
|
+
apply here, but the server still rejects ``UNAPPROVED``/``DEACTIVATED`` on
|
|
333
|
+
create.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
request: An AdminUserCreate model, OR pass fields as keyword arguments.
|
|
337
|
+
**kwargs: Fields for AdminUserCreate if request is not provided.
|
|
338
|
+
Requires: company_id, username, email, role.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
The newly created AdminUser object.
|
|
342
|
+
"""
|
|
343
|
+
body = request if request is not None else self._validate_request(AdminUserCreate, **kwargs)
|
|
344
|
+
response = self._post(
|
|
345
|
+
Endpoints.ADMIN_USERS,
|
|
346
|
+
json=body.model_dump(exclude_none=True, exclude_unset=True, mode="json"),
|
|
347
|
+
)
|
|
348
|
+
return AdminUser.model_validate(response.json())
|
|
349
|
+
|
|
350
|
+
def update_user(
|
|
351
|
+
self, user_id: str, request: AdminUserUpdate | None = None, **kwargs
|
|
352
|
+
) -> AdminUser:
|
|
353
|
+
"""Update any user by ID, bypassing the company-scoped role hierarchy.
|
|
354
|
+
|
|
355
|
+
Set ``is_archived=False`` to restore a soft-deleted user.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
user_id: The ID of the user to update.
|
|
359
|
+
request: An AdminUserUpdate model, OR pass fields as keyword arguments.
|
|
360
|
+
**kwargs: Fields for AdminUserUpdate if request is not provided.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
The updated AdminUser object.
|
|
364
|
+
"""
|
|
365
|
+
body = request if request is not None else self._validate_request(AdminUserUpdate, **kwargs)
|
|
366
|
+
response = self._patch(
|
|
367
|
+
Endpoints.ADMIN_USER.format(user_id=user_id),
|
|
368
|
+
json=body.model_dump(exclude_none=True, exclude_unset=True, mode="json"),
|
|
369
|
+
)
|
|
370
|
+
return AdminUser.model_validate(response.json())
|
|
371
|
+
|
|
372
|
+
def delete_user(self, user_id: str) -> None:
|
|
373
|
+
"""Soft-delete a user by ID.
|
|
374
|
+
|
|
375
|
+
The deletion is reversible via ``update_user(user_id, is_archived=False)``.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
user_id: The ID of the user to soft-delete.
|
|
379
|
+
"""
|
|
380
|
+
self._delete(Endpoints.ADMIN_USER.format(user_id=user_id))
|
|
381
|
+
|
|
186
382
|
def create_api_key(self, developer_id: str) -> DeveloperWithApiKey:
|
|
187
383
|
"""Generate a new API key for a developer.
|
|
188
384
|
|
|
@@ -404,3 +600,47 @@ class SyncGlobalAdminService(SyncBaseService):
|
|
|
404
600
|
params=params,
|
|
405
601
|
):
|
|
406
602
|
yield model.model_validate(item)
|
|
603
|
+
|
|
604
|
+
def tail_logs(
|
|
605
|
+
self, *, level: LogLevel | None = None, limit: int = DEFAULT_LOG_TAIL_LIMIT
|
|
606
|
+
) -> list[ServerLogEntry]:
|
|
607
|
+
"""Tail the most recent server log entries, newest first.
|
|
608
|
+
|
|
609
|
+
Inspect on-disk server logs without shelling into the host. Requires global
|
|
610
|
+
admin privileges and that file logging is enabled on the server.
|
|
611
|
+
|
|
612
|
+
Args:
|
|
613
|
+
level: Minimum log level to include. Defaults to the server's configured
|
|
614
|
+
level when omitted.
|
|
615
|
+
limit: Maximum number of entries to return. Clamped to 1-500 (default 100).
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
A list of ServerLogEntry objects, newest first.
|
|
619
|
+
|
|
620
|
+
Raises:
|
|
621
|
+
AuthorizationError: If you don't have global admin privileges.
|
|
622
|
+
ConflictError: If file logging is not enabled on the server.
|
|
623
|
+
"""
|
|
624
|
+
clamped_limit = min(max(limit, MIN_LOG_TAIL_LIMIT), MAX_LOG_TAIL_LIMIT)
|
|
625
|
+
params = self._build_params(level=level, limit=clamped_limit)
|
|
626
|
+
response = self._get(Endpoints.ADMIN_LOGS, params=params)
|
|
627
|
+
return [ServerLogEntry.model_validate(item) for item in response.json()]
|
|
628
|
+
|
|
629
|
+
def download_logs(self) -> ServerLogArchive:
|
|
630
|
+
"""Download a zip archive of the server log files.
|
|
631
|
+
|
|
632
|
+
Stream the active log file plus rotated backups as a single zip. Requires
|
|
633
|
+
global admin privileges and that file logging is enabled on the server.
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
A ServerLogArchive with the server-provided filename and raw zip bytes.
|
|
637
|
+
|
|
638
|
+
Raises:
|
|
639
|
+
AuthorizationError: If you don't have global admin privileges.
|
|
640
|
+
ConflictError: If file logging is not enabled or no log files exist.
|
|
641
|
+
"""
|
|
642
|
+
response = self._get(Endpoints.ADMIN_LOGS_DOWNLOAD, headers={"Accept": "application/zip"})
|
|
643
|
+
filename = _filename_from_content_disposition(
|
|
644
|
+
response.headers.get("Content-Disposition"), fallback="vapi-logs.zip"
|
|
645
|
+
)
|
|
646
|
+
return ServerLogArchive(filename=filename, content=response.content)
|
|
@@ -21,6 +21,11 @@ IDEMPOTENT_HTTP_METHODS: frozenset[str] = frozenset({"GET", "PUT", "DELETE"})
|
|
|
21
21
|
DEFAULT_PAGE_LIMIT = 10
|
|
22
22
|
MAX_PAGE_LIMIT = 100
|
|
23
23
|
|
|
24
|
+
# Server log tail defaults
|
|
25
|
+
DEFAULT_LOG_TAIL_LIMIT = 100
|
|
26
|
+
MIN_LOG_TAIL_LIMIT = 1
|
|
27
|
+
MAX_LOG_TAIL_LIMIT = 500
|
|
28
|
+
|
|
24
29
|
# HTTP Status Code Ranges (5xx Server Errors)
|
|
25
30
|
HTTP_500_INTERNAL_SERVER_ERROR = 500
|
|
26
31
|
HTTP_600_UPPER_BOUND = 600
|
|
@@ -70,14 +75,16 @@ CharacterInventoryType = Literal[
|
|
|
70
75
|
"WEAPON",
|
|
71
76
|
]
|
|
72
77
|
CharacterStatus = Literal["ALIVE", "DEAD"]
|
|
73
|
-
CharacterType = Literal["PLAYER", "NPC", "STORYTELLER"
|
|
78
|
+
CharacterType = Literal["PLAYER", "NPC", "STORYTELLER"]
|
|
74
79
|
DiceSize = Literal[4, 6, 8, 10, 20, 100]
|
|
75
80
|
FreeTraitChangesPermission = Literal["UNRESTRICTED", "WITHIN_24_HOURS", "STORYTELLER"]
|
|
76
81
|
GameVersion = Literal["V4", "V5"]
|
|
77
82
|
GrantXPPermission = Literal["UNRESTRICTED", "PLAYER", "STORYTELLER"]
|
|
78
83
|
HunterCreed = Literal["ENTREPRENEURIAL", "FAITHFUL", "INQUISITIVE", "MARTIAL", "UNDERGROUND"]
|
|
79
84
|
HunterEdgeType = Literal["ASSETS", "APTITUDES", "ENDOWMENTS"]
|
|
85
|
+
LogLevel = Literal["TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
80
86
|
ManageCampaignPermission = Literal["UNRESTRICTED", "STORYTELLER"]
|
|
87
|
+
ManageNPCPermission = Literal["UNRESTRICTED", "STORYTELLER"]
|
|
81
88
|
PermissionLevel = Literal["USER", "ADMIN", "OWNER", "REVOKE"]
|
|
82
89
|
RecoupXPPermission = Literal["UNRESTRICTED", "DENIED", "WITHIN_SESSION"]
|
|
83
90
|
RollResultType = Literal["SUCCESS", "FAILURE", "BOTCH", "CRITICAL", "OTHER"]
|
|
@@ -27,6 +27,10 @@ class Endpoints:
|
|
|
27
27
|
ADMIN_DEVELOPER = f"{_BASE}/admin/developers/{{developer_id}}"
|
|
28
28
|
ADMIN_DEVELOPER_NEW_KEY = f"{_BASE}/admin/developers/{{developer_id}}/new-key"
|
|
29
29
|
ADMIN_DEVELOPER_AUDIT_LOGS = f"{ADMIN_DEVELOPER}/audit-logs"
|
|
30
|
+
ADMIN_LOGS = f"{_BASE}/admin/logs"
|
|
31
|
+
ADMIN_LOGS_DOWNLOAD = f"{ADMIN_LOGS}/download"
|
|
32
|
+
ADMIN_USERS = f"{_BASE}/admin/users"
|
|
33
|
+
ADMIN_USER = f"{_BASE}/admin/users/{{user_id}}"
|
|
30
34
|
|
|
31
35
|
# Developer endpoints (self-service)
|
|
32
36
|
DEVELOPER_ME = f"{_BASE}/developers/me"
|
{valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/__init__.py
RENAMED
|
@@ -107,6 +107,8 @@ from .global_admin import (
|
|
|
107
107
|
DeveloperCreate,
|
|
108
108
|
DeveloperUpdate,
|
|
109
109
|
DeveloperWithApiKey,
|
|
110
|
+
ServerLogArchive,
|
|
111
|
+
ServerLogEntry,
|
|
110
112
|
)
|
|
111
113
|
from .pagination import PaginatedResponse
|
|
112
114
|
from .shared import (
|
|
@@ -125,6 +127,9 @@ from .user_lookup import (
|
|
|
125
127
|
UserLookupResult,
|
|
126
128
|
)
|
|
127
129
|
from .users import (
|
|
130
|
+
AdminUser,
|
|
131
|
+
AdminUserCreate,
|
|
132
|
+
AdminUserUpdate,
|
|
128
133
|
CampaignExperience,
|
|
129
134
|
DiscordProfile,
|
|
130
135
|
DiscordProfileUpdate,
|
|
@@ -144,6 +149,9 @@ from .users import (
|
|
|
144
149
|
)
|
|
145
150
|
|
|
146
151
|
__all__ = [
|
|
152
|
+
"AdminUser",
|
|
153
|
+
"AdminUserCreate",
|
|
154
|
+
"AdminUserUpdate",
|
|
147
155
|
"Asset",
|
|
148
156
|
"AuditLog",
|
|
149
157
|
"AuditLogDetail",
|
|
@@ -222,6 +230,8 @@ __all__ = [
|
|
|
222
230
|
"QuickrollCreate",
|
|
223
231
|
"QuickrollUpdate",
|
|
224
232
|
"RollStatistics",
|
|
233
|
+
"ServerLogArchive",
|
|
234
|
+
"ServerLogEntry",
|
|
225
235
|
"SheetSection",
|
|
226
236
|
"SystemHealth",
|
|
227
237
|
"Trait",
|
{valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/characters.py
RENAMED
|
@@ -190,8 +190,12 @@ class Character(BaseModel):
|
|
|
190
190
|
concept_name: str | None = Field(default=None, description="Name of the character concept.")
|
|
191
191
|
|
|
192
192
|
# Relationships
|
|
193
|
-
user_creator_id: str = Field(
|
|
194
|
-
|
|
193
|
+
user_creator_id: str | None = Field(
|
|
194
|
+
default=None, description="ID of the user who created the character."
|
|
195
|
+
)
|
|
196
|
+
user_player_id: str | None = Field(
|
|
197
|
+
default=None, description="ID of the user who plays the character."
|
|
198
|
+
)
|
|
195
199
|
company_id: str = Field(..., description="ID of the company.")
|
|
196
200
|
campaign_id: str = Field(..., description="ID of the campaign.")
|
|
197
201
|
|
{valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/companies.py
RENAMED
|
@@ -9,6 +9,7 @@ from vclient.constants import (
|
|
|
9
9
|
FreeTraitChangesPermission,
|
|
10
10
|
GrantXPPermission,
|
|
11
11
|
ManageCampaignPermission,
|
|
12
|
+
ManageNPCPermission,
|
|
12
13
|
PermissionLevel,
|
|
13
14
|
RecoupXPPermission,
|
|
14
15
|
)
|
|
@@ -27,6 +28,7 @@ class CompanySettings(BaseModel):
|
|
|
27
28
|
character_autogen_num_choices: int
|
|
28
29
|
character_autogen_starting_points: int
|
|
29
30
|
permission_manage_campaign: ManageCampaignPermission
|
|
31
|
+
permission_manage_npc: ManageNPCPermission
|
|
30
32
|
permission_grant_xp: GrantXPPermission
|
|
31
33
|
permission_free_trait_changes: FreeTraitChangesPermission
|
|
32
34
|
permission_recoup_xp: RecoupXPPermission
|
|
@@ -42,6 +44,7 @@ class CompanySettingsCreate(BaseModel):
|
|
|
42
44
|
character_autogen_num_choices: int | None = None
|
|
43
45
|
character_autogen_starting_points: int | None = None
|
|
44
46
|
permission_manage_campaign: ManageCampaignPermission | None = None
|
|
47
|
+
permission_manage_npc: ManageNPCPermission | None = None
|
|
45
48
|
permission_grant_xp: GrantXPPermission | None = None
|
|
46
49
|
permission_free_trait_changes: FreeTraitChangesPermission | None = None
|
|
47
50
|
permission_recoup_xp: RecoupXPPermission | None = None
|
|
@@ -57,6 +60,7 @@ class CompanySettingsUpdate(BaseModel):
|
|
|
57
60
|
character_autogen_num_choices: int | None = None
|
|
58
61
|
character_autogen_starting_points: int | None = None
|
|
59
62
|
permission_manage_campaign: ManageCampaignPermission | None = None
|
|
63
|
+
permission_manage_npc: ManageNPCPermission | None = None
|
|
60
64
|
permission_grant_xp: GrantXPPermission | None = None
|
|
61
65
|
permission_free_trait_changes: FreeTraitChangesPermission | None = None
|
|
62
66
|
permission_recoup_xp: RecoupXPPermission | None = None
|
{valentina_python_client-2.1.0 → valentina_python_client-2.3.0}/src/vclient/models/global_admin.py
RENAMED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Pydantic models for Global Admin API responses."""
|
|
2
2
|
|
|
3
|
+
from dataclasses import dataclass
|
|
3
4
|
from datetime import datetime
|
|
5
|
+
from typing import Any
|
|
4
6
|
|
|
5
|
-
from pydantic import BaseModel
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
6
8
|
|
|
7
9
|
from vclient.constants import PermissionLevel
|
|
8
10
|
|
|
@@ -70,10 +72,40 @@ class DeveloperUpdate(BaseModel):
|
|
|
70
72
|
is_global_admin: bool | None = None
|
|
71
73
|
|
|
72
74
|
|
|
75
|
+
class ServerLogEntry(BaseModel):
|
|
76
|
+
"""A single parsed server log entry from the admin logs tail endpoint.
|
|
77
|
+
|
|
78
|
+
Every field is nullable because individual log lines may omit values or fail
|
|
79
|
+
to parse as structured JSON (in which case ``raw`` holds the original line).
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
timestamp: str | None = None
|
|
83
|
+
level: str | None = None
|
|
84
|
+
name: str | None = None
|
|
85
|
+
message: str | None = None
|
|
86
|
+
exception: str | None = None
|
|
87
|
+
extra: dict[str, Any] = Field(default_factory=dict)
|
|
88
|
+
raw: str | None = None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class ServerLogArchive:
|
|
93
|
+
"""A downloaded server-log zip archive.
|
|
94
|
+
|
|
95
|
+
Pairs the server-provided ``Content-Disposition`` filename with the raw zip
|
|
96
|
+
bytes so callers can write the archive straight to disk.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
filename: str
|
|
100
|
+
content: bytes
|
|
101
|
+
|
|
102
|
+
|
|
73
103
|
__all__ = [
|
|
74
104
|
"Developer",
|
|
75
105
|
"DeveloperCompanyPermission",
|
|
76
106
|
"DeveloperCreate",
|
|
77
107
|
"DeveloperUpdate",
|
|
78
108
|
"DeveloperWithApiKey",
|
|
109
|
+
"ServerLogArchive",
|
|
110
|
+
"ServerLogEntry",
|
|
79
111
|
]
|
|
@@ -122,6 +122,17 @@ class UserDetail(User):
|
|
|
122
122
|
)
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
class AdminUser(User):
|
|
126
|
+
"""Response model for a user returned by the global-admin user endpoints.
|
|
127
|
+
|
|
128
|
+
Extends the tenant-scoped ``User`` with ``is_archived``, which is always
|
|
129
|
+
present on the admin endpoints so callers can identify soft-deleted users
|
|
130
|
+
directly from the response body.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
is_archived: bool
|
|
134
|
+
|
|
135
|
+
|
|
125
136
|
# -----------------------------------------------------------------------------
|
|
126
137
|
# User Request Models
|
|
127
138
|
# -----------------------------------------------------------------------------
|
|
@@ -194,6 +205,27 @@ class UserUpdate(BaseModel):
|
|
|
194
205
|
github_profile: GitHubProfile | None = None
|
|
195
206
|
|
|
196
207
|
|
|
208
|
+
class AdminUserCreate(UserCreate):
|
|
209
|
+
"""Request body for creating a user as a global admin.
|
|
210
|
+
|
|
211
|
+
Extends the tenant-scoped ``UserCreate`` with an explicit ``company_id`` for
|
|
212
|
+
the target company. The server rejects ``UNAPPROVED``/``DEACTIVATED`` roles
|
|
213
|
+
on create, so no client-side role restriction is applied here.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
company_id: str
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class AdminUserUpdate(UserUpdate):
|
|
220
|
+
"""Request body for updating any user as a global admin.
|
|
221
|
+
|
|
222
|
+
Extends the tenant-scoped ``UserUpdate`` with ``is_archived``. Set it to
|
|
223
|
+
``False`` to restore a soft-deleted user.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
is_archived: bool | None = None
|
|
227
|
+
|
|
228
|
+
|
|
197
229
|
class UserApproveDTO(BaseModel):
|
|
198
230
|
"""Approve an unapproved user and assign a role."""
|
|
199
231
|
|
|
@@ -262,6 +294,9 @@ class _ExperienceAddRemove(BaseModel):
|
|
|
262
294
|
|
|
263
295
|
|
|
264
296
|
__all__ = [
|
|
297
|
+
"AdminUser",
|
|
298
|
+
"AdminUserCreate",
|
|
299
|
+
"AdminUserUpdate",
|
|
265
300
|
"CampaignExperience",
|
|
266
301
|
"DiscordProfile",
|
|
267
302
|
"GitHubProfile",
|