valentina-python-client 1.20.0__tar.gz → 1.21.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-1.20.0 → valentina_python_client-1.21.0}/PKG-INFO +1 -1
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/pyproject.toml +1 -1
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/__init__.py +5 -1
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_codegen.py +5 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/__init__.py +2 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/client.py +15 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/registry.py +24 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/__init__.py +2 -0
- valentina_python_client-1.21.0/src/vclient/_sync/services/user_lookup.py +68 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/users.py +38 -8
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/client.py +15 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/constants.py +1 -1
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/endpoints.py +3 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/__init__.py +4 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/characters.py +2 -6
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/companies.py +1 -1
- valentina_python_client-1.21.0/src/vclient/models/user_lookup.py +22 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/registry.py +24 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/__init__.py +2 -0
- valentina_python_client-1.21.0/src/vclient/services/user_lookup.py +67 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/users.py +32 -4
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/__init__.py +2 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_factories.py +8 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_router.py +3 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_routes.py +3 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/LICENSE +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/README.md +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/base.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/campaign_book_chapters.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/campaign_books.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/campaigns.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/character_autogen.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/character_blueprint.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/character_traits.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/characters.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/companies.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/developers.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/dicerolls.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/dictionary.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/global_admin.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/options.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/system.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/testing/__init__.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/testing/_client.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/config.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/exceptions.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/books.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/campaigns.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/chapters.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/character_autogen.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/character_blueprint.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/character_trait.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/developers.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/diceroll.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/dictionary.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/full_sheet.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/global_admin.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/pagination.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/shared.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/system.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/users.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/py.typed +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/base.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/campaign_book_chapters.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/campaign_books.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/campaigns.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/character_autogen.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/character_blueprint.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/character_traits.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/characters.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/companies.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/developers.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/dicerolls.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/dictionary.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/global_admin.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/options.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/system.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_client.py +0 -0
- {valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/validate_constants.py +0 -0
|
@@ -49,6 +49,7 @@ from vclient._sync import ( # noqa: E402
|
|
|
49
49
|
sync_global_admin_service,
|
|
50
50
|
sync_options_service,
|
|
51
51
|
sync_system_service,
|
|
52
|
+
sync_user_lookup_service,
|
|
52
53
|
sync_users_service,
|
|
53
54
|
)
|
|
54
55
|
from vclient.client import VClient # noqa: E402
|
|
@@ -67,6 +68,7 @@ from vclient.registry import ( # noqa: E402
|
|
|
67
68
|
global_admin_service,
|
|
68
69
|
options_service,
|
|
69
70
|
system_service,
|
|
71
|
+
user_lookup_service,
|
|
70
72
|
users_service,
|
|
71
73
|
)
|
|
72
74
|
|
|
@@ -100,9 +102,11 @@ __all__ = (
|
|
|
100
102
|
"sync_global_admin_service",
|
|
101
103
|
"sync_options_service",
|
|
102
104
|
"sync_system_service",
|
|
105
|
+
"sync_user_lookup_service",
|
|
103
106
|
"sync_users_service",
|
|
104
107
|
"system_service",
|
|
108
|
+
"user_lookup_service",
|
|
105
109
|
"users_service",
|
|
106
110
|
)
|
|
107
111
|
|
|
108
|
-
__version__ = "1.
|
|
112
|
+
__version__ = "1.21.0"
|
|
@@ -28,6 +28,7 @@ RENAME_CLASSES: dict[str, str] = {
|
|
|
28
28
|
"GlobalAdminService": "SyncGlobalAdminService",
|
|
29
29
|
"OptionsService": "SyncOptionsService",
|
|
30
30
|
"SystemService": "SyncSystemService",
|
|
31
|
+
"UserLookupService": "SyncUserLookupService",
|
|
31
32
|
"UsersService": "SyncUsersService",
|
|
32
33
|
"VClient": "SyncVClient",
|
|
33
34
|
"FakeVClient": "SyncFakeVClient",
|
|
@@ -49,6 +50,7 @@ FACTORY_RENAMES: dict[str, str] = {
|
|
|
49
50
|
"dicerolls_service": "sync_dicerolls_service",
|
|
50
51
|
"options_service": "sync_options_service",
|
|
51
52
|
"character_autogen_service": "sync_character_autogen_service",
|
|
53
|
+
"user_lookup_service": "sync_user_lookup_service",
|
|
52
54
|
"configure_default_client": "sync_configure_default_client",
|
|
53
55
|
"clear_default_client": "sync_clear_default_client",
|
|
54
56
|
"default_client": "sync_default_client",
|
|
@@ -73,6 +75,7 @@ IMPORT_REWRITES: dict[str, str] = {
|
|
|
73
75
|
"vclient.services.dicerolls": "vclient._sync.services.dicerolls",
|
|
74
76
|
"vclient.services.options": "vclient._sync.services.options",
|
|
75
77
|
"vclient.services.character_autogen": "vclient._sync.services.character_autogen",
|
|
78
|
+
"vclient.services.user_lookup": "vclient._sync.services.user_lookup",
|
|
76
79
|
"vclient.registry": "vclient._sync.registry",
|
|
77
80
|
"vclient.testing._client": "vclient._sync.testing._client",
|
|
78
81
|
}
|
|
@@ -307,6 +310,7 @@ def _write_sync_init(path: Path) -> None:
|
|
|
307
310
|
" sync_global_admin_service,",
|
|
308
311
|
" sync_options_service,",
|
|
309
312
|
" sync_system_service,",
|
|
313
|
+
" sync_user_lookup_service,",
|
|
310
314
|
" sync_users_service,",
|
|
311
315
|
")",
|
|
312
316
|
"",
|
|
@@ -329,6 +333,7 @@ def _write_sync_init(path: Path) -> None:
|
|
|
329
333
|
' "sync_global_admin_service",',
|
|
330
334
|
' "sync_options_service",',
|
|
331
335
|
' "sync_system_service",',
|
|
336
|
+
' "sync_user_lookup_service",',
|
|
332
337
|
' "sync_users_service",',
|
|
333
338
|
"]",
|
|
334
339
|
"",
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/__init__.py
RENAMED
|
@@ -19,6 +19,7 @@ from vclient._sync.registry import (
|
|
|
19
19
|
sync_global_admin_service,
|
|
20
20
|
sync_options_service,
|
|
21
21
|
sync_system_service,
|
|
22
|
+
sync_user_lookup_service,
|
|
22
23
|
sync_users_service,
|
|
23
24
|
)
|
|
24
25
|
|
|
@@ -41,5 +42,6 @@ __all__ = [
|
|
|
41
42
|
"sync_global_admin_service",
|
|
42
43
|
"sync_options_service",
|
|
43
44
|
"sync_system_service",
|
|
45
|
+
"sync_user_lookup_service",
|
|
44
46
|
"sync_users_service",
|
|
45
47
|
]
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/client.py
RENAMED
|
@@ -37,6 +37,7 @@ if TYPE_CHECKING:
|
|
|
37
37
|
SyncGlobalAdminService,
|
|
38
38
|
SyncOptionsService,
|
|
39
39
|
SyncSystemService,
|
|
40
|
+
SyncUserLookupService,
|
|
40
41
|
SyncUsersService,
|
|
41
42
|
)
|
|
42
43
|
|
|
@@ -147,6 +148,7 @@ class SyncVClient:
|
|
|
147
148
|
self._developer: SyncDeveloperService | None = None
|
|
148
149
|
self._global_admin: SyncGlobalAdminService | None = None
|
|
149
150
|
self._system: SyncSystemService | None = None
|
|
151
|
+
self._user_lookup: SyncUserLookupService | None = None
|
|
150
152
|
if set_as_default:
|
|
151
153
|
from vclient._sync.registry import sync_configure_default_client
|
|
152
154
|
|
|
@@ -278,6 +280,19 @@ class SyncVClient:
|
|
|
278
280
|
self._system = SyncSystemService(self)
|
|
279
281
|
return self._system
|
|
280
282
|
|
|
283
|
+
@property
|
|
284
|
+
def user_lookup(self) -> "SyncUserLookupService":
|
|
285
|
+
"""Access the User Lookup service for cross-company user discovery.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
The SyncUserLookupService instance for user lookup operations.
|
|
289
|
+
"""
|
|
290
|
+
if self._user_lookup is None:
|
|
291
|
+
from vclient._sync.services.user_lookup import SyncUserLookupService
|
|
292
|
+
|
|
293
|
+
self._user_lookup = SyncUserLookupService(self)
|
|
294
|
+
return self._user_lookup
|
|
295
|
+
|
|
281
296
|
def users(self, company_id: str | None = None) -> "SyncUsersService":
|
|
282
297
|
"""Get a SyncUsersService scoped to a specific company.
|
|
283
298
|
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/registry.py
RENAMED
|
@@ -38,6 +38,7 @@ if TYPE_CHECKING:
|
|
|
38
38
|
SyncGlobalAdminService,
|
|
39
39
|
SyncOptionsService,
|
|
40
40
|
SyncSystemService,
|
|
41
|
+
SyncUserLookupService,
|
|
41
42
|
SyncUsersService,
|
|
42
43
|
)
|
|
43
44
|
_default_client: "SyncVClient | None" = None
|
|
@@ -189,6 +190,29 @@ def sync_system_service() -> "SyncSystemService":
|
|
|
189
190
|
return SyncSystemService(sync_default_client())
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
def sync_user_lookup_service() -> "SyncUserLookupService":
|
|
194
|
+
"""Create a SyncUserLookupService using the default client.
|
|
195
|
+
|
|
196
|
+
Discover which companies a person has a user record in by searching
|
|
197
|
+
via email, Discord ID, Google ID, or GitHub ID.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
SyncUserLookupService: A service instance for cross-company user lookup.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
RuntimeError: If no default client has been configured.
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
```python
|
|
207
|
+
lookup = sync_user_lookup_service()
|
|
208
|
+
results = await lookup.by_email("alice@example.com")
|
|
209
|
+
```
|
|
210
|
+
"""
|
|
211
|
+
from vclient._sync.services.user_lookup import SyncUserLookupService
|
|
212
|
+
|
|
213
|
+
return SyncUserLookupService(sync_default_client())
|
|
214
|
+
|
|
215
|
+
|
|
192
216
|
def sync_users_service(company_id: str | None = None) -> "SyncUsersService":
|
|
193
217
|
"""Create a SyncUsersService scoped to a specific company using the default client.
|
|
194
218
|
|
|
@@ -16,6 +16,7 @@ from .dictionary import SyncDictionaryService
|
|
|
16
16
|
from .global_admin import SyncGlobalAdminService
|
|
17
17
|
from .options import SyncOptionsService
|
|
18
18
|
from .system import SyncSystemService
|
|
19
|
+
from .user_lookup import SyncUserLookupService
|
|
19
20
|
from .users import SyncUsersService
|
|
20
21
|
|
|
21
22
|
__all__ = [
|
|
@@ -34,5 +35,6 @@ __all__ = [
|
|
|
34
35
|
"SyncGlobalAdminService",
|
|
35
36
|
"SyncOptionsService",
|
|
36
37
|
"SyncSystemService",
|
|
38
|
+
"SyncUserLookupService",
|
|
37
39
|
"SyncUsersService",
|
|
38
40
|
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# AUTO-GENERATED — do not edit. Run 'uv run duty generate_sync' to regenerate.
|
|
2
|
+
"""Service for cross-company user lookup."""
|
|
3
|
+
|
|
4
|
+
from vclient._sync.services.base import SyncBaseService
|
|
5
|
+
from vclient.endpoints import Endpoints
|
|
6
|
+
from vclient.models import UserLookupResult
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SyncUserLookupService(SyncBaseService):
|
|
10
|
+
"""Service for looking up users across companies.
|
|
11
|
+
|
|
12
|
+
Discover which companies a person has a user record in by searching
|
|
13
|
+
via email, Discord ID, Google ID, or GitHub ID.
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> async with SyncVClient() as client:
|
|
17
|
+
... results = await client.user_lookup.by_email("alice@example.com")
|
|
18
|
+
... for r in results:
|
|
19
|
+
... print(f"{r.company_name}: {r.role}")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def by_email(self, email: str) -> list[UserLookupResult]:
|
|
23
|
+
"""Look up a user by email address.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
email: Exact email address to search for.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
30
|
+
"""
|
|
31
|
+
response = self._get(Endpoints.USERS_LOOKUP, params={"email": email})
|
|
32
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
33
|
+
|
|
34
|
+
def by_discord_id(self, discord_id: str) -> list[UserLookupResult]:
|
|
35
|
+
"""Look up a user by Discord profile ID.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
discord_id: Discord profile ID to search for.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
42
|
+
"""
|
|
43
|
+
response = self._get(Endpoints.USERS_LOOKUP, params={"discord_id": discord_id})
|
|
44
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
45
|
+
|
|
46
|
+
def by_google_id(self, google_id: str) -> list[UserLookupResult]:
|
|
47
|
+
"""Look up a user by Google profile ID.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
google_id: Google profile ID to search for.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
54
|
+
"""
|
|
55
|
+
response = self._get(Endpoints.USERS_LOOKUP, params={"google_id": google_id})
|
|
56
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
57
|
+
|
|
58
|
+
def by_github_id(self, github_id: str) -> list[UserLookupResult]:
|
|
59
|
+
"""Look up a user by GitHub profile ID.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
github_id: GitHub profile ID to search for.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
66
|
+
"""
|
|
67
|
+
response = self._get(Endpoints.USERS_LOOKUP, params={"github_id": github_id})
|
|
68
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
@@ -65,13 +65,14 @@ class SyncUsersService(SyncBaseService):
|
|
|
65
65
|
return endpoint.format(company_id=self._company_id, **kwargs)
|
|
66
66
|
|
|
67
67
|
def get_unapproved_page(
|
|
68
|
-
self, *, limit: int = DEFAULT_PAGE_LIMIT, offset: int = 0
|
|
68
|
+
self, requesting_user_id: str, *, limit: int = DEFAULT_PAGE_LIMIT, offset: int = 0
|
|
69
69
|
) -> PaginatedResponse[User]:
|
|
70
70
|
"""Retrieve a paginated page of unapproved users within a company.
|
|
71
71
|
|
|
72
72
|
Unapproved users have registered but have not yet been approved by an admin.
|
|
73
73
|
|
|
74
74
|
Args:
|
|
75
|
+
requesting_user_id: ID of the user making the request (for permissions).
|
|
75
76
|
limit: Maximum number of items to return (0-100, default 10).
|
|
76
77
|
offset: Number of items to skip from the beginning (default 0).
|
|
77
78
|
|
|
@@ -79,40 +80,54 @@ class SyncUsersService(SyncBaseService):
|
|
|
79
80
|
A PaginatedResponse containing User objects and pagination metadata.
|
|
80
81
|
"""
|
|
81
82
|
return self._get_paginated_as(
|
|
82
|
-
self._format_endpoint(Endpoints.USERS_UNAPPROVED_LIST),
|
|
83
|
+
self._format_endpoint(Endpoints.USERS_UNAPPROVED_LIST),
|
|
84
|
+
User,
|
|
85
|
+
limit=limit,
|
|
86
|
+
offset=offset,
|
|
87
|
+
params=self._build_params(requesting_user_id=requesting_user_id),
|
|
83
88
|
)
|
|
84
89
|
|
|
85
|
-
def list_all_unapproved(self) -> list[User]:
|
|
90
|
+
def list_all_unapproved(self, requesting_user_id: str) -> list[User]:
|
|
86
91
|
"""Retrieve all unapproved users within a company.
|
|
87
92
|
|
|
88
93
|
Automatically paginates through all results. Use `get_unapproved_page()` for
|
|
89
94
|
paginated access or `iter_all_unapproved()` for memory-efficient streaming.
|
|
90
95
|
|
|
96
|
+
Args:
|
|
97
|
+
requesting_user_id: ID of the user making the request (for permissions).
|
|
98
|
+
|
|
91
99
|
Returns:
|
|
92
100
|
A list of all unapproved User objects.
|
|
93
101
|
"""
|
|
94
|
-
return [user for user in self.iter_all_unapproved()]
|
|
102
|
+
return [user for user in self.iter_all_unapproved(requesting_user_id)]
|
|
95
103
|
|
|
96
|
-
def iter_all_unapproved(self, *, limit: int = 100) -> Iterator[User]:
|
|
104
|
+
def iter_all_unapproved(self, requesting_user_id: str, *, limit: int = 100) -> Iterator[User]:
|
|
97
105
|
"""Iterate through all unapproved users within a company.
|
|
98
106
|
|
|
99
107
|
Yields individual unapproved users, automatically fetching subsequent pages
|
|
100
108
|
until all items have been retrieved.
|
|
101
109
|
|
|
102
110
|
Args:
|
|
111
|
+
requesting_user_id: ID of the user making the request (for permissions).
|
|
103
112
|
limit: Items per page (default 100 for efficiency).
|
|
104
113
|
|
|
105
114
|
Yields:
|
|
106
115
|
Individual User objects.
|
|
107
116
|
"""
|
|
108
117
|
for item in self._iter_all_pages(
|
|
109
|
-
self._format_endpoint(Endpoints.USERS_UNAPPROVED_LIST),
|
|
118
|
+
self._format_endpoint(Endpoints.USERS_UNAPPROVED_LIST),
|
|
119
|
+
limit=limit,
|
|
120
|
+
params=self._build_params(requesting_user_id=requesting_user_id),
|
|
110
121
|
):
|
|
111
122
|
yield User.model_validate(item)
|
|
112
123
|
|
|
113
124
|
def approve_user(self, user_id: str, role: UserRole, requesting_user_id: str) -> User:
|
|
114
125
|
"""Approve an unapproved user and assign them a role.
|
|
115
126
|
|
|
127
|
+
The assigned ``role`` is validated through the server-side role-assignment
|
|
128
|
+
hierarchy — for example, a STORYTELLER cannot approve a user directly to
|
|
129
|
+
ADMIN, and only ADMIN may approve a user to ADMIN or DEACTIVATED.
|
|
130
|
+
|
|
116
131
|
Args:
|
|
117
132
|
user_id: The ID of the unapproved user to approve.
|
|
118
133
|
role: The role to assign to the approved user.
|
|
@@ -123,7 +138,8 @@ class SyncUsersService(SyncBaseService):
|
|
|
123
138
|
|
|
124
139
|
Raises:
|
|
125
140
|
NotFoundError: If the user does not exist.
|
|
126
|
-
AuthorizationError: If
|
|
141
|
+
AuthorizationError: If the requesting user lacks permission to assign
|
|
142
|
+
the requested role under the hierarchy.
|
|
127
143
|
"""
|
|
128
144
|
body = UserApproveDTO(role=role, requesting_user_id=requesting_user_id)
|
|
129
145
|
response = self._post(
|
|
@@ -285,6 +301,10 @@ class SyncUsersService(SyncBaseService):
|
|
|
285
301
|
is optional and is not used for authentication but is included for Discord bot
|
|
286
302
|
integration.
|
|
287
303
|
|
|
304
|
+
The initial ``role`` cannot be ``UNAPPROVED`` (use :meth:`register` for SSO
|
|
305
|
+
onboarding) or ``DEACTIVATED`` (not a creation path); either will surface as
|
|
306
|
+
``ValidationError``.
|
|
307
|
+
|
|
288
308
|
Args:
|
|
289
309
|
request: A UserCreate model, OR pass fields as keyword arguments.
|
|
290
310
|
**kwargs: Fields for UserCreate if request is not provided.
|
|
@@ -341,6 +361,14 @@ class SyncUsersService(SyncBaseService):
|
|
|
341
361
|
|
|
342
362
|
Only include fields that need to be changed; omitted fields remain unchanged.
|
|
343
363
|
|
|
364
|
+
Setting ``role="DEACTIVATED"`` is the canonical way to deactivate a user:
|
|
365
|
+
the account can no longer log in or act, but their characters, assets, XP,
|
|
366
|
+
and notes remain intact and manageable by other users. Reactivate by calling
|
|
367
|
+
this same endpoint with any other valid role. Role changes are subject to a
|
|
368
|
+
server-side role-assignment hierarchy (e.g. only ADMIN may assign or remove
|
|
369
|
+
ADMIN/DEACTIVATED) and the server refuses to demote or deactivate the last
|
|
370
|
+
remaining active admin.
|
|
371
|
+
|
|
344
372
|
Args:
|
|
345
373
|
user_id: The ID of the user to update.
|
|
346
374
|
request: A UserUpdate model, OR pass fields as keyword arguments.
|
|
@@ -354,7 +382,9 @@ class SyncUsersService(SyncBaseService):
|
|
|
354
382
|
|
|
355
383
|
Raises:
|
|
356
384
|
NotFoundError: If the user does not exist.
|
|
357
|
-
AuthorizationError: If
|
|
385
|
+
AuthorizationError: If the requesting user lacks permission to make
|
|
386
|
+
the change or to assign the requested role under the hierarchy.
|
|
387
|
+
ConflictError: If the change would remove the last active admin.
|
|
358
388
|
RequestValidationError: If the input parameters fail client-side validation.
|
|
359
389
|
ValidationError: If the request data is invalid.
|
|
360
390
|
"""
|
|
@@ -36,6 +36,7 @@ if TYPE_CHECKING:
|
|
|
36
36
|
GlobalAdminService,
|
|
37
37
|
OptionsService,
|
|
38
38
|
SystemService,
|
|
39
|
+
UserLookupService,
|
|
39
40
|
UsersService,
|
|
40
41
|
)
|
|
41
42
|
|
|
@@ -150,6 +151,7 @@ class VClient:
|
|
|
150
151
|
self._developer: DeveloperService | None = None
|
|
151
152
|
self._global_admin: GlobalAdminService | None = None
|
|
152
153
|
self._system: SystemService | None = None
|
|
154
|
+
self._user_lookup: UserLookupService | None = None
|
|
153
155
|
|
|
154
156
|
if set_as_default:
|
|
155
157
|
from vclient.registry import configure_default_client
|
|
@@ -290,6 +292,19 @@ class VClient:
|
|
|
290
292
|
self._system = SystemService(self)
|
|
291
293
|
return self._system
|
|
292
294
|
|
|
295
|
+
@property
|
|
296
|
+
def user_lookup(self) -> "UserLookupService":
|
|
297
|
+
"""Access the User Lookup service for cross-company user discovery.
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
The UserLookupService instance for user lookup operations.
|
|
301
|
+
"""
|
|
302
|
+
if self._user_lookup is None:
|
|
303
|
+
from vclient.services.user_lookup import UserLookupService
|
|
304
|
+
|
|
305
|
+
self._user_lookup = UserLookupService(self)
|
|
306
|
+
return self._user_lookup
|
|
307
|
+
|
|
293
308
|
def users(self, company_id: str | None = None) -> "UsersService":
|
|
294
309
|
"""Get a UsersService scoped to a specific company.
|
|
295
310
|
|
|
@@ -63,7 +63,7 @@ RecoupXPPermission = Literal["UNRESTRICTED", "DENIED", "WITHIN_SESSION"]
|
|
|
63
63
|
RollResultType = Literal["SUCCESS", "FAILURE", "BOTCH", "CRITICAL", "OTHER"]
|
|
64
64
|
AssetType = Literal["image", "text", "audio", "video", "document", "archive", "other"]
|
|
65
65
|
SpecialtyType = Literal["ACTION", "OTHER", "PASSIVE", "RITUAL", "SPELL"]
|
|
66
|
-
UserRole = Literal["ADMIN", "STORYTELLER", "PLAYER", "UNAPPROVED"]
|
|
66
|
+
UserRole = Literal["ADMIN", "STORYTELLER", "PLAYER", "UNAPPROVED", "DEACTIVATED"]
|
|
67
67
|
WerewolfRenown = Literal["HONOR", "GLORY", "WISDOM"]
|
|
68
68
|
BlueprintTraitOrderBy = Literal["NAME", "SHEET"]
|
|
69
69
|
TraitModifyCurrency = Literal["XP", "STARTING_POINTS", "NO_COST"]
|
|
@@ -19,6 +19,9 @@ class Endpoints:
|
|
|
19
19
|
# System endpoints
|
|
20
20
|
HEALTH = f"{_BASE}/health"
|
|
21
21
|
|
|
22
|
+
# User lookup (cross-company, not scoped)
|
|
23
|
+
USERS_LOOKUP = f"{_BASE}/users/lookup"
|
|
24
|
+
|
|
22
25
|
# Global Admin endpoints
|
|
23
26
|
ADMIN_DEVELOPERS = f"{_BASE}/admin/developers"
|
|
24
27
|
ADMIN_DEVELOPER = f"{_BASE}/admin/developers/{{developer_id}}"
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/__init__.py
RENAMED
|
@@ -115,6 +115,9 @@ from .shared import (
|
|
|
115
115
|
Trait,
|
|
116
116
|
)
|
|
117
117
|
from .system import SystemHealth
|
|
118
|
+
from .user_lookup import (
|
|
119
|
+
UserLookupResult,
|
|
120
|
+
)
|
|
118
121
|
from .users import (
|
|
119
122
|
CampaignExperience,
|
|
120
123
|
DiscordProfile,
|
|
@@ -221,6 +224,7 @@ __all__ = [
|
|
|
221
224
|
"UserCreate",
|
|
222
225
|
"UserDenyDTO",
|
|
223
226
|
"UserDetail",
|
|
227
|
+
"UserLookupResult",
|
|
224
228
|
"UserMergeDTO",
|
|
225
229
|
"UserRegisterDTO",
|
|
226
230
|
"UserUpdate",
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/characters.py
RENAMED
|
@@ -152,12 +152,8 @@ class Character(BaseModel):
|
|
|
152
152
|
"""
|
|
153
153
|
|
|
154
154
|
id: str = Field(..., description="MongoDB document ObjectID.")
|
|
155
|
-
date_created: datetime
|
|
156
|
-
|
|
157
|
-
)
|
|
158
|
-
date_modified: datetime | None = Field(
|
|
159
|
-
default=None, description="Timestamp when the character was last modified."
|
|
160
|
-
)
|
|
155
|
+
date_created: datetime
|
|
156
|
+
date_modified: datetime
|
|
161
157
|
date_killed: datetime | None = Field(
|
|
162
158
|
default=None, description="Timestamp when the character was killed."
|
|
163
159
|
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Pydantic models for User Lookup API responses."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from vclient.constants import UserRole
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserLookupResult(BaseModel):
|
|
9
|
+
"""A single result from a cross-company user lookup.
|
|
10
|
+
|
|
11
|
+
Each result represents a company where the looked-up person has a user record.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
company_id: str
|
|
15
|
+
company_name: str
|
|
16
|
+
user_id: str
|
|
17
|
+
role: UserRole
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"UserLookupResult",
|
|
22
|
+
]
|
|
@@ -37,6 +37,7 @@ if TYPE_CHECKING:
|
|
|
37
37
|
GlobalAdminService,
|
|
38
38
|
OptionsService,
|
|
39
39
|
SystemService,
|
|
40
|
+
UserLookupService,
|
|
40
41
|
UsersService,
|
|
41
42
|
)
|
|
42
43
|
|
|
@@ -189,6 +190,29 @@ def system_service() -> "SystemService":
|
|
|
189
190
|
return SystemService(default_client())
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
def user_lookup_service() -> "UserLookupService":
|
|
194
|
+
"""Create a UserLookupService using the default client.
|
|
195
|
+
|
|
196
|
+
Discover which companies a person has a user record in by searching
|
|
197
|
+
via email, Discord ID, Google ID, or GitHub ID.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
UserLookupService: A service instance for cross-company user lookup.
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
RuntimeError: If no default client has been configured.
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
```python
|
|
207
|
+
lookup = user_lookup_service()
|
|
208
|
+
results = await lookup.by_email("alice@example.com")
|
|
209
|
+
```
|
|
210
|
+
"""
|
|
211
|
+
from vclient.services.user_lookup import UserLookupService
|
|
212
|
+
|
|
213
|
+
return UserLookupService(default_client())
|
|
214
|
+
|
|
215
|
+
|
|
192
216
|
def users_service(company_id: str | None = None) -> "UsersService":
|
|
193
217
|
"""Create a UsersService scoped to a specific company using the default client.
|
|
194
218
|
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/__init__.py
RENAMED
|
@@ -15,6 +15,7 @@ from .dictionary import DictionaryService
|
|
|
15
15
|
from .global_admin import GlobalAdminService
|
|
16
16
|
from .options import OptionsService
|
|
17
17
|
from .system import SystemService
|
|
18
|
+
from .user_lookup import UserLookupService
|
|
18
19
|
from .users import UsersService
|
|
19
20
|
|
|
20
21
|
__all__ = [
|
|
@@ -33,5 +34,6 @@ __all__ = [
|
|
|
33
34
|
"GlobalAdminService",
|
|
34
35
|
"OptionsService",
|
|
35
36
|
"SystemService",
|
|
37
|
+
"UserLookupService",
|
|
36
38
|
"UsersService",
|
|
37
39
|
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Service for cross-company user lookup."""
|
|
2
|
+
|
|
3
|
+
from vclient.endpoints import Endpoints
|
|
4
|
+
from vclient.models import UserLookupResult
|
|
5
|
+
from vclient.services.base import BaseService
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserLookupService(BaseService):
|
|
9
|
+
"""Service for looking up users across companies.
|
|
10
|
+
|
|
11
|
+
Discover which companies a person has a user record in by searching
|
|
12
|
+
via email, Discord ID, Google ID, or GitHub ID.
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> async with VClient() as client:
|
|
16
|
+
... results = await client.user_lookup.by_email("alice@example.com")
|
|
17
|
+
... for r in results:
|
|
18
|
+
... print(f"{r.company_name}: {r.role}")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
async def by_email(self, email: str) -> list[UserLookupResult]:
|
|
22
|
+
"""Look up a user by email address.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
email: Exact email address to search for.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
29
|
+
"""
|
|
30
|
+
response = await self._get(Endpoints.USERS_LOOKUP, params={"email": email})
|
|
31
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
32
|
+
|
|
33
|
+
async def by_discord_id(self, discord_id: str) -> list[UserLookupResult]:
|
|
34
|
+
"""Look up a user by Discord profile ID.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
discord_id: Discord profile ID to search for.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
41
|
+
"""
|
|
42
|
+
response = await self._get(Endpoints.USERS_LOOKUP, params={"discord_id": discord_id})
|
|
43
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
44
|
+
|
|
45
|
+
async def by_google_id(self, google_id: str) -> list[UserLookupResult]:
|
|
46
|
+
"""Look up a user by Google profile ID.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
google_id: Google profile ID to search for.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
53
|
+
"""
|
|
54
|
+
response = await self._get(Endpoints.USERS_LOOKUP, params={"google_id": google_id})
|
|
55
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
|
56
|
+
|
|
57
|
+
async def by_github_id(self, github_id: str) -> list[UserLookupResult]:
|
|
58
|
+
"""Look up a user by GitHub profile ID.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
github_id: GitHub profile ID to search for.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
List of companies where a matching user was found. Empty list if no matches.
|
|
65
|
+
"""
|
|
66
|
+
response = await self._get(Endpoints.USERS_LOOKUP, params={"github_id": github_id})
|
|
67
|
+
return [UserLookupResult.model_validate(item) for item in response.json()]
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/users.py
RENAMED
|
@@ -69,6 +69,7 @@ class UsersService(BaseService):
|
|
|
69
69
|
|
|
70
70
|
async def get_unapproved_page(
|
|
71
71
|
self,
|
|
72
|
+
requesting_user_id: str,
|
|
72
73
|
*,
|
|
73
74
|
limit: int = DEFAULT_PAGE_LIMIT,
|
|
74
75
|
offset: int = 0,
|
|
@@ -78,6 +79,7 @@ class UsersService(BaseService):
|
|
|
78
79
|
Unapproved users have registered but have not yet been approved by an admin.
|
|
79
80
|
|
|
80
81
|
Args:
|
|
82
|
+
requesting_user_id: ID of the user making the request (for permissions).
|
|
81
83
|
limit: Maximum number of items to return (0-100, default 10).
|
|
82
84
|
offset: Number of items to skip from the beginning (default 0).
|
|
83
85
|
|
|
@@ -89,21 +91,26 @@ class UsersService(BaseService):
|
|
|
89
91
|
User,
|
|
90
92
|
limit=limit,
|
|
91
93
|
offset=offset,
|
|
94
|
+
params=self._build_params(requesting_user_id=requesting_user_id),
|
|
92
95
|
)
|
|
93
96
|
|
|
94
|
-
async def list_all_unapproved(self) -> list[User]:
|
|
97
|
+
async def list_all_unapproved(self, requesting_user_id: str) -> list[User]:
|
|
95
98
|
"""Retrieve all unapproved users within a company.
|
|
96
99
|
|
|
97
100
|
Automatically paginates through all results. Use `get_unapproved_page()` for
|
|
98
101
|
paginated access or `iter_all_unapproved()` for memory-efficient streaming.
|
|
99
102
|
|
|
103
|
+
Args:
|
|
104
|
+
requesting_user_id: ID of the user making the request (for permissions).
|
|
105
|
+
|
|
100
106
|
Returns:
|
|
101
107
|
A list of all unapproved User objects.
|
|
102
108
|
"""
|
|
103
|
-
return [user async for user in self.iter_all_unapproved()]
|
|
109
|
+
return [user async for user in self.iter_all_unapproved(requesting_user_id)]
|
|
104
110
|
|
|
105
111
|
async def iter_all_unapproved(
|
|
106
112
|
self,
|
|
113
|
+
requesting_user_id: str,
|
|
107
114
|
*,
|
|
108
115
|
limit: int = 100,
|
|
109
116
|
) -> AsyncIterator[User]:
|
|
@@ -113,6 +120,7 @@ class UsersService(BaseService):
|
|
|
113
120
|
until all items have been retrieved.
|
|
114
121
|
|
|
115
122
|
Args:
|
|
123
|
+
requesting_user_id: ID of the user making the request (for permissions).
|
|
116
124
|
limit: Items per page (default 100 for efficiency).
|
|
117
125
|
|
|
118
126
|
Yields:
|
|
@@ -121,6 +129,7 @@ class UsersService(BaseService):
|
|
|
121
129
|
async for item in self._iter_all_pages(
|
|
122
130
|
self._format_endpoint(Endpoints.USERS_UNAPPROVED_LIST),
|
|
123
131
|
limit=limit,
|
|
132
|
+
params=self._build_params(requesting_user_id=requesting_user_id),
|
|
124
133
|
):
|
|
125
134
|
yield User.model_validate(item)
|
|
126
135
|
|
|
@@ -132,6 +141,10 @@ class UsersService(BaseService):
|
|
|
132
141
|
) -> User:
|
|
133
142
|
"""Approve an unapproved user and assign them a role.
|
|
134
143
|
|
|
144
|
+
The assigned ``role`` is validated through the server-side role-assignment
|
|
145
|
+
hierarchy — for example, a STORYTELLER cannot approve a user directly to
|
|
146
|
+
ADMIN, and only ADMIN may approve a user to ADMIN or DEACTIVATED.
|
|
147
|
+
|
|
135
148
|
Args:
|
|
136
149
|
user_id: The ID of the unapproved user to approve.
|
|
137
150
|
role: The role to assign to the approved user.
|
|
@@ -142,7 +155,8 @@ class UsersService(BaseService):
|
|
|
142
155
|
|
|
143
156
|
Raises:
|
|
144
157
|
NotFoundError: If the user does not exist.
|
|
145
|
-
AuthorizationError: If
|
|
158
|
+
AuthorizationError: If the requesting user lacks permission to assign
|
|
159
|
+
the requested role under the hierarchy.
|
|
146
160
|
"""
|
|
147
161
|
body = UserApproveDTO(role=role, requesting_user_id=requesting_user_id)
|
|
148
162
|
response = await self._post(
|
|
@@ -333,6 +347,10 @@ class UsersService(BaseService):
|
|
|
333
347
|
is optional and is not used for authentication but is included for Discord bot
|
|
334
348
|
integration.
|
|
335
349
|
|
|
350
|
+
The initial ``role`` cannot be ``UNAPPROVED`` (use :meth:`register` for SSO
|
|
351
|
+
onboarding) or ``DEACTIVATED`` (not a creation path); either will surface as
|
|
352
|
+
``ValidationError``.
|
|
353
|
+
|
|
336
354
|
Args:
|
|
337
355
|
request: A UserCreate model, OR pass fields as keyword arguments.
|
|
338
356
|
**kwargs: Fields for UserCreate if request is not provided.
|
|
@@ -398,6 +416,14 @@ class UsersService(BaseService):
|
|
|
398
416
|
|
|
399
417
|
Only include fields that need to be changed; omitted fields remain unchanged.
|
|
400
418
|
|
|
419
|
+
Setting ``role="DEACTIVATED"`` is the canonical way to deactivate a user:
|
|
420
|
+
the account can no longer log in or act, but their characters, assets, XP,
|
|
421
|
+
and notes remain intact and manageable by other users. Reactivate by calling
|
|
422
|
+
this same endpoint with any other valid role. Role changes are subject to a
|
|
423
|
+
server-side role-assignment hierarchy (e.g. only ADMIN may assign or remove
|
|
424
|
+
ADMIN/DEACTIVATED) and the server refuses to demote or deactivate the last
|
|
425
|
+
remaining active admin.
|
|
426
|
+
|
|
401
427
|
Args:
|
|
402
428
|
user_id: The ID of the user to update.
|
|
403
429
|
request: A UserUpdate model, OR pass fields as keyword arguments.
|
|
@@ -411,7 +437,9 @@ class UsersService(BaseService):
|
|
|
411
437
|
|
|
412
438
|
Raises:
|
|
413
439
|
NotFoundError: If the user does not exist.
|
|
414
|
-
AuthorizationError: If
|
|
440
|
+
AuthorizationError: If the requesting user lacks permission to make
|
|
441
|
+
the change or to assign the requested role under the hierarchy.
|
|
442
|
+
ConflictError: If the change would remove the last active admin.
|
|
415
443
|
RequestValidationError: If the input parameters fail client-side validation.
|
|
416
444
|
ValidationError: If the request data is invalid.
|
|
417
445
|
"""
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/__init__.py
RENAMED
|
@@ -57,6 +57,7 @@ from vclient.testing._factories import (
|
|
|
57
57
|
TraitSubcategoryFactory,
|
|
58
58
|
UserDetailFactory,
|
|
59
59
|
UserFactory,
|
|
60
|
+
UserLookupResultFactory,
|
|
60
61
|
VampireClanFactory,
|
|
61
62
|
WerewolfAuspiceFactory,
|
|
62
63
|
WerewolfTribeFactory,
|
|
@@ -110,6 +111,7 @@ __all__ = [
|
|
|
110
111
|
"TraitSubcategoryFactory",
|
|
111
112
|
"UserDetailFactory",
|
|
112
113
|
"UserFactory",
|
|
114
|
+
"UserLookupResultFactory",
|
|
113
115
|
"VampireClanFactory",
|
|
114
116
|
"WerewolfAuspiceFactory",
|
|
115
117
|
"WerewolfTribeFactory",
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_factories.py
RENAMED
|
@@ -43,6 +43,7 @@ from vclient.models import (
|
|
|
43
43
|
TraitSubcategory,
|
|
44
44
|
User,
|
|
45
45
|
UserDetail,
|
|
46
|
+
UserLookupResult,
|
|
46
47
|
VampireClan,
|
|
47
48
|
WerewolfAuspice,
|
|
48
49
|
WerewolfTribe,
|
|
@@ -259,6 +260,12 @@ class UserFactory(ModelFactory[User]):
|
|
|
259
260
|
role = "PLAYER"
|
|
260
261
|
|
|
261
262
|
|
|
263
|
+
class UserLookupResultFactory(ModelFactory[UserLookupResult]):
|
|
264
|
+
__model__ = UserLookupResult
|
|
265
|
+
__use_defaults__ = True
|
|
266
|
+
role = "PLAYER"
|
|
267
|
+
|
|
268
|
+
|
|
262
269
|
class UserDetailFactory(ModelFactory[UserDetail]):
|
|
263
270
|
__model__ = UserDetail
|
|
264
271
|
__use_defaults__ = True
|
|
@@ -323,6 +330,7 @@ __all__ = [
|
|
|
323
330
|
"TraitSubcategoryFactory",
|
|
324
331
|
"UserDetailFactory",
|
|
325
332
|
"UserFactory",
|
|
333
|
+
"UserLookupResultFactory",
|
|
326
334
|
"VampireClanFactory",
|
|
327
335
|
"WerewolfAuspiceFactory",
|
|
328
336
|
"WerewolfTribeFactory",
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_router.py
RENAMED
|
@@ -48,6 +48,7 @@ from vclient.models import (
|
|
|
48
48
|
TraitSubcategory,
|
|
49
49
|
User,
|
|
50
50
|
UserDetail,
|
|
51
|
+
UserLookupResult,
|
|
51
52
|
VampireClan,
|
|
52
53
|
WerewolfAuspice,
|
|
53
54
|
WerewolfTribe,
|
|
@@ -91,6 +92,7 @@ from vclient.testing._factories import (
|
|
|
91
92
|
TraitSubcategoryFactory,
|
|
92
93
|
UserDetailFactory,
|
|
93
94
|
UserFactory,
|
|
95
|
+
UserLookupResultFactory,
|
|
94
96
|
VampireClanFactory,
|
|
95
97
|
WerewolfAuspiceFactory,
|
|
96
98
|
WerewolfTribeFactory,
|
|
@@ -133,6 +135,7 @@ _FACTORY_MAP: dict[type, type[ModelFactory]] = {
|
|
|
133
135
|
TraitCategory: TraitCategoryFactory,
|
|
134
136
|
TraitSubcategory: TraitSubcategoryFactory,
|
|
135
137
|
User: UserFactory,
|
|
138
|
+
UserLookupResult: UserLookupResultFactory,
|
|
136
139
|
UserDetail: UserDetailFactory,
|
|
137
140
|
VampireClan: VampireClanFactory,
|
|
138
141
|
WerewolfAuspice: WerewolfAuspiceFactory,
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_routes.py
RENAMED
|
@@ -40,6 +40,7 @@ from vclient.models import (
|
|
|
40
40
|
TraitSubcategory,
|
|
41
41
|
User,
|
|
42
42
|
UserDetail,
|
|
43
|
+
UserLookupResult,
|
|
43
44
|
VampireClan,
|
|
44
45
|
WerewolfAuspice,
|
|
45
46
|
WerewolfTribe,
|
|
@@ -131,6 +132,8 @@ class Routes:
|
|
|
131
132
|
USERS_REGISTER = RouteSpec("POST", Endpoints.USER_REGISTER, SINGLE, User)
|
|
132
133
|
USERS_MERGE = RouteSpec("POST", Endpoints.USER_MERGE, SINGLE, User)
|
|
133
134
|
USERS_STATISTICS = RouteSpec("GET", Endpoints.USER_STATISTICS, SINGLE, RollStatistics)
|
|
135
|
+
# User lookup (cross-company)
|
|
136
|
+
USERS_LOOKUP = RouteSpec("GET", Endpoints.USERS_LOOKUP, LIST, UserLookupResult)
|
|
134
137
|
|
|
135
138
|
# User assets
|
|
136
139
|
USERS_ASSETS_LIST = RouteSpec("GET", Endpoints.USER_ASSETS, PAGINATED, Asset)
|
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/_sync/services/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/books.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/campaigns.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/chapters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/developers.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/diceroll.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/dictionary.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/full_sheet.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/global_admin.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/pagination.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/shared.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/system.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/models/users.py
RENAMED
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/campaigns.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/characters.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/companies.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/developers.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/dicerolls.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/dictionary.py
RENAMED
|
File without changes
|
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/options.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/services/system.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/testing/_client.py
RENAMED
|
File without changes
|
{valentina_python_client-1.20.0 → valentina_python_client-1.21.0}/src/vclient/validate_constants.py
RENAMED
|
File without changes
|