valentina-python-client 1.20.0__tar.gz → 1.20.1__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 (76) hide show
  1. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/PKG-INFO +1 -1
  2. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/pyproject.toml +1 -1
  3. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/__init__.py +1 -1
  4. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/users.py +38 -8
  5. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/constants.py +1 -1
  6. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/characters.py +2 -6
  7. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/companies.py +1 -1
  8. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/users.py +32 -4
  9. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/LICENSE +0 -0
  10. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/README.md +0 -0
  11. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_codegen.py +0 -0
  12. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/__init__.py +0 -0
  13. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/client.py +0 -0
  14. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/registry.py +0 -0
  15. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/__init__.py +0 -0
  16. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/base.py +0 -0
  17. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/campaign_book_chapters.py +0 -0
  18. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/campaign_books.py +0 -0
  19. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/campaigns.py +0 -0
  20. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/character_autogen.py +0 -0
  21. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/character_blueprint.py +0 -0
  22. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/character_traits.py +0 -0
  23. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/characters.py +0 -0
  24. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/companies.py +0 -0
  25. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/developers.py +0 -0
  26. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/dicerolls.py +0 -0
  27. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/dictionary.py +0 -0
  28. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/global_admin.py +0 -0
  29. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/options.py +0 -0
  30. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/services/system.py +0 -0
  31. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/testing/__init__.py +0 -0
  32. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/_sync/testing/_client.py +0 -0
  33. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/client.py +0 -0
  34. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/config.py +0 -0
  35. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/endpoints.py +0 -0
  36. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/exceptions.py +0 -0
  37. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/__init__.py +0 -0
  38. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/books.py +0 -0
  39. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/campaigns.py +0 -0
  40. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/chapters.py +0 -0
  41. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/character_autogen.py +0 -0
  42. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/character_blueprint.py +0 -0
  43. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/character_trait.py +0 -0
  44. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/developers.py +0 -0
  45. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/diceroll.py +0 -0
  46. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/dictionary.py +0 -0
  47. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/full_sheet.py +0 -0
  48. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/global_admin.py +0 -0
  49. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/pagination.py +0 -0
  50. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/shared.py +0 -0
  51. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/system.py +0 -0
  52. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/models/users.py +0 -0
  53. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/py.typed +0 -0
  54. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/registry.py +0 -0
  55. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/__init__.py +0 -0
  56. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/base.py +0 -0
  57. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/campaign_book_chapters.py +0 -0
  58. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/campaign_books.py +0 -0
  59. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/campaigns.py +0 -0
  60. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/character_autogen.py +0 -0
  61. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/character_blueprint.py +0 -0
  62. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/character_traits.py +0 -0
  63. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/characters.py +0 -0
  64. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/companies.py +0 -0
  65. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/developers.py +0 -0
  66. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/dicerolls.py +0 -0
  67. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/dictionary.py +0 -0
  68. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/global_admin.py +0 -0
  69. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/options.py +0 -0
  70. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/services/system.py +0 -0
  71. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/testing/__init__.py +0 -0
  72. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/testing/_client.py +0 -0
  73. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/testing/_factories.py +0 -0
  74. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/testing/_router.py +0 -0
  75. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/testing/_routes.py +0 -0
  76. {valentina_python_client-1.20.0 → valentina_python_client-1.20.1}/src/vclient/validate_constants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valentina-python-client
3
- Version: 1.20.0
3
+ Version: 1.20.1
4
4
  Summary: Async Python client library for the Valentina Noir API
5
5
  Author: Nate Landau
6
6
  Author-email: Nate Landau <github@natenate.org>
@@ -16,7 +16,7 @@
16
16
  name = "valentina-python-client"
17
17
  readme = "README.md"
18
18
  requires-python = ">=3.13"
19
- version = "1.20.0"
19
+ version = "1.20.1"
20
20
 
21
21
  [project.optional-dependencies]
22
22
  testing = ["polyfactory>=3.3.0"]
@@ -105,4 +105,4 @@ __all__ = (
105
105
  "users_service",
106
106
  )
107
107
 
108
- __version__ = "1.20.0"
108
+ __version__ = "1.20.1"
@@ -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), User, limit=limit, offset=offset
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), limit=limit
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 you don't have appropriate access.
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 you don't have appropriate access.
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
  """
@@ -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"]
@@ -152,12 +152,8 @@ class Character(BaseModel):
152
152
  """
153
153
 
154
154
  id: str = Field(..., description="MongoDB document ObjectID.")
155
- date_created: datetime | None = Field(
156
- default=None, description="Timestamp when the character was created."
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
  )
@@ -43,7 +43,7 @@ class Company(BaseModel):
43
43
  description: str | None
44
44
  email: str
45
45
  resources_modified_at: datetime
46
- settings: CompanySettings | None
46
+ settings: CompanySettings
47
47
 
48
48
 
49
49
  class CompanyPermissions(BaseModel):
@@ -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 you don't have appropriate access.
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 you don't have appropriate access.
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
  """