valentina-python-client 1.7.4__tar.gz → 1.9.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.
Files changed (74) hide show
  1. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/PKG-INFO +3 -1
  2. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/pyproject.toml +7 -4
  3. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/__init__.py +1 -1
  4. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_codegen.py +29 -0
  5. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/character_traits.py +22 -5
  6. valentina_python_client-1.9.0/src/vclient/_sync/testing/__init__.py +5 -0
  7. valentina_python_client-1.9.0/src/vclient/_sync/testing/_client.py +70 -0
  8. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/__init__.py +2 -0
  9. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/character_trait.py +8 -0
  10. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/character_traits.py +7 -3
  11. valentina_python_client-1.9.0/src/vclient/testing/__init__.py +97 -0
  12. valentina_python_client-1.9.0/src/vclient/testing/_client.py +74 -0
  13. valentina_python_client-1.9.0/src/vclient/testing/_factories.py +271 -0
  14. valentina_python_client-1.9.0/src/vclient/testing/_router.py +446 -0
  15. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/LICENSE +0 -0
  16. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/README.md +0 -0
  17. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/__init__.py +0 -0
  18. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/client.py +0 -0
  19. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/registry.py +0 -0
  20. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/__init__.py +0 -0
  21. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/base.py +0 -0
  22. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/campaign_book_chapters.py +0 -0
  23. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/campaign_books.py +0 -0
  24. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/campaigns.py +0 -0
  25. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/character_autogen.py +0 -0
  26. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/character_blueprint.py +0 -0
  27. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/characters.py +0 -0
  28. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/companies.py +0 -0
  29. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/developers.py +0 -0
  30. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/dicerolls.py +0 -0
  31. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/dictionary.py +0 -0
  32. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/global_admin.py +0 -0
  33. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/options.py +0 -0
  34. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/system.py +0 -0
  35. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/_sync/services/users.py +0 -0
  36. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/client.py +0 -0
  37. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/config.py +0 -0
  38. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/constants.py +0 -0
  39. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/endpoints.py +0 -0
  40. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/exceptions.py +0 -0
  41. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/books.py +0 -0
  42. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/campaigns.py +0 -0
  43. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/chapters.py +0 -0
  44. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/character_autogen.py +0 -0
  45. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/character_blueprint.py +0 -0
  46. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/characters.py +0 -0
  47. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/companies.py +0 -0
  48. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/developers.py +0 -0
  49. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/diceroll.py +0 -0
  50. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/dictionary.py +0 -0
  51. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/global_admin.py +0 -0
  52. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/pagination.py +0 -0
  53. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/shared.py +0 -0
  54. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/system.py +0 -0
  55. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/models/users.py +0 -0
  56. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/py.typed +0 -0
  57. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/registry.py +0 -0
  58. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/__init__.py +0 -0
  59. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/base.py +0 -0
  60. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/campaign_book_chapters.py +0 -0
  61. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/campaign_books.py +0 -0
  62. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/campaigns.py +0 -0
  63. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/character_autogen.py +0 -0
  64. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/character_blueprint.py +0 -0
  65. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/characters.py +0 -0
  66. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/companies.py +0 -0
  67. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/developers.py +0 -0
  68. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/dicerolls.py +0 -0
  69. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/dictionary.py +0 -0
  70. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/global_admin.py +0 -0
  71. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/options.py +0 -0
  72. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/system.py +0 -0
  73. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/src/vclient/services/users.py +0 -0
  74. {valentina_python_client-1.7.4 → valentina_python_client-1.9.0}/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.7.4
3
+ Version: 1.9.0
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>
@@ -19,9 +19,11 @@ Requires-Dist: anyio>=4.12.1
19
19
  Requires-Dist: httpx>=0.28.1
20
20
  Requires-Dist: loguru>=0.7.3
21
21
  Requires-Dist: pydantic[email]>=2.12.5
22
+ Requires-Dist: polyfactory>=2.21.0 ; extra == 'testing'
22
23
  Requires-Python: >=3.13
23
24
  Project-URL: Homepage, https://docs.valentina-noir.com/python-api-client/
24
25
  Project-URL: Repository, https://github.com/natelandau/valentina-python-client
26
+ Provides-Extra: testing
25
27
  Description-Content-Type: text/markdown
26
28
 
27
29
  # Valentina Python Client
@@ -10,7 +10,10 @@
10
10
  name = "valentina-python-client"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.13"
13
- version = "1.7.4"
13
+ version = "1.9.0"
14
+
15
+ [project.optional-dependencies]
16
+ testing = ["polyfactory>=2.21.0"]
14
17
 
15
18
  [project.urls]
16
19
  Homepage = "https://docs.valentina-noir.com/python-api-client/"
@@ -39,11 +42,11 @@
39
42
  "pytest-xdist>=3.8.0",
40
43
  "pytest>=9.0.2",
41
44
  "respx>=0.22.0",
42
- "ruff>=0.15.4",
45
+ "ruff>=0.15.5",
43
46
  "shellcheck-py>=0.11.0.1",
44
- "ty>=0.0.19",
47
+ "ty>=0.0.21",
45
48
  "typos>=1.44.0",
46
- "vulture>=2.14",
49
+ "vulture>=2.15",
47
50
  "yamllint>=1.38.0",
48
51
  ]
49
52
  docs = ["zensical>=0.0.24"]
@@ -105,4 +105,4 @@ __all__ = (
105
105
  "users_service",
106
106
  )
107
107
 
108
- __version__ = "1.7.4"
108
+ __version__ = "1.9.0"
@@ -30,6 +30,7 @@ RENAME_CLASSES: dict[str, str] = {
30
30
  "SystemService": "SyncSystemService",
31
31
  "UsersService": "SyncUsersService",
32
32
  "VClient": "SyncVClient",
33
+ "FakeVClient": "SyncFakeVClient",
33
34
  }
34
35
 
35
36
  FACTORY_RENAMES: dict[str, str] = {
@@ -73,6 +74,7 @@ IMPORT_REWRITES: dict[str, str] = {
73
74
  "vclient.services.options": "vclient._sync.services.options",
74
75
  "vclient.services.character_autogen": "vclient._sync.services.character_autogen",
75
76
  "vclient.registry": "vclient._sync.registry",
77
+ "vclient.testing._client": "vclient._sync.testing._client",
76
78
  }
77
79
 
78
80
  # Combined lookup for renaming any identifier (class or factory function)
@@ -334,6 +336,22 @@ def _write_sync_init(path: Path) -> None:
334
336
  path.write_text("\n".join(lines))
335
337
 
336
338
 
339
+ def _write_sync_testing_init(path: Path) -> None:
340
+ """Write the _sync/testing/__init__.py that re-exports public names.
341
+
342
+ Args:
343
+ path: Path to the ``_sync/testing/__init__.py`` file to write.
344
+ """
345
+ lines = [
346
+ HEADER_COMMENT,
347
+ "from vclient._sync.testing._client import SyncFakeVClient",
348
+ "",
349
+ '__all__ = ["SyncFakeVClient"]',
350
+ "",
351
+ ]
352
+ path.write_text("\n".join(lines))
353
+
354
+
337
355
  def generate_sync(src_dir: Path) -> None:
338
356
  """Transform all async source files and write them into the ``_sync/`` package.
339
357
 
@@ -362,6 +380,17 @@ def generate_sync(src_dir: Path) -> None:
362
380
  # Write the _sync/__init__.py
363
381
  _write_sync_init(sync_dir / "__init__.py")
364
382
 
383
+ # Transform testing module
384
+ testing_src = src_dir / "testing"
385
+ testing_dst = sync_dir / "testing"
386
+ if testing_src.exists():
387
+ testing_dst.mkdir(exist_ok=True)
388
+ client_source = testing_src / "_client.py"
389
+ if client_source.exists():
390
+ output_path = testing_dst / "_client.py"
391
+ output_path.write_text(transform_file(client_source))
392
+ _write_sync_testing_init(testing_dst / "__init__.py")
393
+
365
394
 
366
395
  if __name__ == "__main__":
367
396
  project_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("src/vclient")
@@ -8,8 +8,8 @@ from vclient._sync.services.base import SyncBaseService
8
8
  from vclient.constants import DEFAULT_PAGE_LIMIT, TraitModifyCurrency
9
9
  from vclient.endpoints import Endpoints
10
10
  from vclient.models import (
11
- CharacterCreateTraitAssign,
12
11
  CharacterTrait,
12
+ CharacterTraitAdd,
13
13
  CharacterTraitValueOptionsResponse,
14
14
  PaginatedResponse,
15
15
  TraitCreate,
@@ -137,22 +137,37 @@ class SyncCharacterTraitsService(SyncBaseService):
137
137
  )
138
138
  return CharacterTrait.model_validate(response.json())
139
139
 
140
- def delete(self, character_trait_id: str) -> None:
140
+ def delete(self, character_trait_id: str, currency: TraitModifyCurrency | None = None) -> None:
141
141
  """Delete a character trait.
142
142
 
143
143
  Args:
144
144
  character_trait_id: The ID of the trait to delete.
145
+ currency: The currency to use to recoup the cost of the trait.
146
+
147
+ Returns:
148
+ None
149
+
150
+ Raises:
151
+ NotFoundError: If the trait does not exist.
152
+ AuthorizationError: If you don't have access to the character.
153
+ RequestValidationError: If the input parameters fail client-side validation.
154
+ ValidationError: If the request data is invalid.
145
155
  """
156
+ params: dict[str, str | int] = {}
157
+ if currency is not None:
158
+ params["currency"] = currency
146
159
  self._delete(
147
- self._format_endpoint(Endpoints.CHARACTER_TRAIT, character_trait_id=character_trait_id)
160
+ self._format_endpoint(Endpoints.CHARACTER_TRAIT, character_trait_id=character_trait_id),
161
+ params=params or None,
148
162
  )
149
163
 
150
- def assign(self, trait_id: str, value: int) -> CharacterTrait:
164
+ def assign(self, trait_id: str, value: int, currency: TraitModifyCurrency) -> CharacterTrait:
151
165
  """Assign a trait to a character.
152
166
 
153
167
  Args:
154
168
  trait_id: The ID of the trait to assign.
155
169
  value: The value of the trait to assign.
170
+ currency: The currency to use to pay for the trait.
156
171
 
157
172
  Returns:
158
173
  The newly assigned CharacterTrait object.
@@ -163,7 +178,9 @@ class SyncCharacterTraitsService(SyncBaseService):
163
178
  RequestValidationError: If the input parameters fail client-side validation.
164
179
  ValidationError: If the request data is invalid.
165
180
  """
166
- body = self._validate_request(CharacterCreateTraitAssign, trait_id=trait_id, value=value)
181
+ body = self._validate_request(
182
+ CharacterTraitAdd, trait_id=trait_id, value=value, currency=currency
183
+ )
167
184
  response = self._post(
168
185
  self._format_endpoint(Endpoints.CHARACTER_TRAIT_ASSIGN),
169
186
  json=body.model_dump(exclude_none=True, exclude_unset=True, mode="json"),
@@ -0,0 +1,5 @@
1
+ # AUTO-GENERATED — do not edit. Run 'uv run duty generate_sync' to regenerate.
2
+
3
+ from vclient._sync.testing._client import SyncFakeVClient
4
+
5
+ __all__ = ["SyncFakeVClient"]
@@ -0,0 +1,70 @@
1
+ # AUTO-GENERATED — do not edit. Run 'uv run duty generate_sync' to regenerate.
2
+ """Fake async API client for testing downstream applications.
3
+
4
+ FakeSyncVClient is a drop-in replacement for SyncVClient that uses httpx.MockTransport
5
+ instead of real HTTP. All real service classes work unmodified.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ import httpx
13
+
14
+ from vclient._sync.client import SyncVClient
15
+ from vclient.testing._router import _FakeRouter
16
+
17
+
18
+ class SyncFakeVClient(SyncVClient):
19
+ """A fake SyncVClient for testing that uses mock HTTP transport.
20
+
21
+ Drop-in replacement for SyncVClient that returns auto-generated responses
22
+ for all endpoints. Registers itself as the default client so factory
23
+ functions like sync_campaigns_service() work without configuration.
24
+
25
+ Example:
26
+ ```python
27
+ from vclient.testing import FakeSyncVClient
28
+ from vclient import sync_campaigns_service
29
+
30
+ async with FakeSyncVClient() as client:
31
+ campaigns = sync_campaigns_service("user123")
32
+ result = await campaigns.list_all()
33
+ ```
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ *,
39
+ default_company_id: str = "fake-company",
40
+ set_as_default: bool = True,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ self._router = _FakeRouter()
44
+ super().__init__(
45
+ base_url="https://fake.valentina-api.test",
46
+ api_key="fake-api-key",
47
+ default_company_id=default_company_id,
48
+ set_as_default=set_as_default,
49
+ **kwargs,
50
+ )
51
+
52
+ def _create_http_client(self) -> httpx.Client:
53
+ """Create an HTTP client backed by the fake router."""
54
+ return httpx.Client(
55
+ transport=httpx.MockTransport(self._router.handle),
56
+ base_url="https://fake.valentina-api.test",
57
+ )
58
+
59
+ def add_route(
60
+ self, method: str, pattern: str, *, json: dict[str, Any], status_code: int = 200
61
+ ) -> None:
62
+ """Add a custom route override.
63
+
64
+ Args:
65
+ method: HTTP method (GET, POST, PATCH, DELETE, PUT).
66
+ pattern: Endpoint pattern from vclient.endpoints.Endpoints.
67
+ json: The JSON response body to return.
68
+ status_code: HTTP status code to return (default 200).
69
+ """
70
+ self._router.add_route(method, pattern, json=json, status_code=status_code)
@@ -36,6 +36,7 @@ from .character_blueprint import (
36
36
  from .character_trait import (
37
37
  CharacterCreateTraitAssign,
38
38
  CharacterTrait,
39
+ CharacterTraitAdd,
39
40
  CharacterTraitValueOptionsResponse,
40
41
  TraitCreate,
41
42
  _TraitModify as _TraitModify,
@@ -138,6 +139,7 @@ __all__ = [
138
139
  "CharacterHunterEdge",
139
140
  "CharacterSpecialty",
140
141
  "CharacterTrait",
142
+ "CharacterTraitAdd",
141
143
  "CharacterTraitValueOptionsResponse",
142
144
  "CharacterUpdate",
143
145
  "ChargenSessionFinalizeDTO",
@@ -25,6 +25,14 @@ class CharacterCreateTraitAssign(BaseModel):
25
25
  value: int
26
26
 
27
27
 
28
+ class CharacterTraitAdd(BaseModel):
29
+ """Request model for adding a character trait to an already created character."""
30
+
31
+ trait_id: str
32
+ value: int
33
+ currency: TraitModifyCurrency
34
+
35
+
28
36
  class TraitCreate(BaseModel):
29
37
  """Request model for creating a character trait.
30
38
 
@@ -6,8 +6,8 @@ from typing import TYPE_CHECKING
6
6
  from vclient.constants import DEFAULT_PAGE_LIMIT, TraitModifyCurrency
7
7
  from vclient.endpoints import Endpoints
8
8
  from vclient.models import (
9
- CharacterCreateTraitAssign,
10
9
  CharacterTrait,
10
+ CharacterTraitAdd,
11
11
  CharacterTraitValueOptionsResponse,
12
12
  PaginatedResponse,
13
13
  TraitCreate,
@@ -173,12 +173,15 @@ class CharacterTraitsService(BaseService):
173
173
  params=params or None,
174
174
  )
175
175
 
176
- async def assign(self, trait_id: str, value: int) -> CharacterTrait:
176
+ async def assign(
177
+ self, trait_id: str, value: int, currency: TraitModifyCurrency
178
+ ) -> CharacterTrait:
177
179
  """Assign a trait to a character.
178
180
 
179
181
  Args:
180
182
  trait_id: The ID of the trait to assign.
181
183
  value: The value of the trait to assign.
184
+ currency: The currency to use to pay for the trait.
182
185
 
183
186
  Returns:
184
187
  The newly assigned CharacterTrait object.
@@ -190,9 +193,10 @@ class CharacterTraitsService(BaseService):
190
193
  ValidationError: If the request data is invalid.
191
194
  """
192
195
  body = self._validate_request(
193
- CharacterCreateTraitAssign,
196
+ CharacterTraitAdd,
194
197
  trait_id=trait_id,
195
198
  value=value,
199
+ currency=currency,
196
200
  )
197
201
  response = await self._post(
198
202
  self._format_endpoint(Endpoints.CHARACTER_TRAIT_ASSIGN),
@@ -0,0 +1,97 @@
1
+ """Testing utilities for downstream applications using vclient.
2
+
3
+ Requires the 'testing' extra: pip install vclient[testing]
4
+ """
5
+
6
+ try:
7
+ import polyfactory # noqa: F401
8
+ except ImportError as e:
9
+ msg = (
10
+ "vclient.testing requires the 'testing' extra. "
11
+ "Install it with: pip install vclient[testing]"
12
+ )
13
+ raise ImportError(msg) from e
14
+
15
+ from vclient._sync.testing import SyncFakeVClient
16
+ from vclient.testing._client import FakeVClient
17
+ from vclient.testing._factories import (
18
+ AssetFactory,
19
+ CampaignBookFactory,
20
+ CampaignChapterFactory,
21
+ CampaignExperienceFactory,
22
+ CampaignFactory,
23
+ CharacterConceptFactory,
24
+ CharacterFactory,
25
+ CharacterTraitFactory,
26
+ CharacterTraitValueOptionsResponseFactory,
27
+ ChargenSessionResponseFactory,
28
+ CompanyFactory,
29
+ CompanyPermissionsFactory,
30
+ DeveloperFactory,
31
+ DeveloperWithApiKeyFactory,
32
+ DicerollFactory,
33
+ DictionaryTermFactory,
34
+ EdgeAndPerksFactory,
35
+ HunterEdgeFactory,
36
+ HunterEdgePerkFactory,
37
+ InventoryItemFactory,
38
+ MeDeveloperFactory,
39
+ MeDeveloperWithApiKeyFactory,
40
+ NewCompanyResponseFactory,
41
+ NoteFactory,
42
+ PerkFactory,
43
+ QuickrollFactory,
44
+ RollStatisticsFactory,
45
+ SheetSectionFactory,
46
+ SystemHealthFactory,
47
+ TraitCategoryFactory,
48
+ TraitFactory,
49
+ UserFactory,
50
+ VampireClanFactory,
51
+ WerewolfAuspiceFactory,
52
+ WerewolfGiftFactory,
53
+ WerewolfRiteFactory,
54
+ WerewolfTribeFactory,
55
+ )
56
+
57
+ __all__ = [
58
+ "AssetFactory",
59
+ "CampaignBookFactory",
60
+ "CampaignChapterFactory",
61
+ "CampaignExperienceFactory",
62
+ "CampaignFactory",
63
+ "CharacterConceptFactory",
64
+ "CharacterFactory",
65
+ "CharacterTraitFactory",
66
+ "CharacterTraitValueOptionsResponseFactory",
67
+ "ChargenSessionResponseFactory",
68
+ "CompanyFactory",
69
+ "CompanyPermissionsFactory",
70
+ "DeveloperFactory",
71
+ "DeveloperWithApiKeyFactory",
72
+ "DicerollFactory",
73
+ "DictionaryTermFactory",
74
+ "EdgeAndPerksFactory",
75
+ "FakeVClient",
76
+ "HunterEdgeFactory",
77
+ "HunterEdgePerkFactory",
78
+ "InventoryItemFactory",
79
+ "MeDeveloperFactory",
80
+ "MeDeveloperWithApiKeyFactory",
81
+ "NewCompanyResponseFactory",
82
+ "NoteFactory",
83
+ "PerkFactory",
84
+ "QuickrollFactory",
85
+ "RollStatisticsFactory",
86
+ "SheetSectionFactory",
87
+ "SyncFakeVClient",
88
+ "SystemHealthFactory",
89
+ "TraitCategoryFactory",
90
+ "TraitFactory",
91
+ "UserFactory",
92
+ "VampireClanFactory",
93
+ "WerewolfAuspiceFactory",
94
+ "WerewolfGiftFactory",
95
+ "WerewolfRiteFactory",
96
+ "WerewolfTribeFactory",
97
+ ]
@@ -0,0 +1,74 @@
1
+ """Fake async API client for testing downstream applications.
2
+
3
+ FakeVClient is a drop-in replacement for VClient that uses httpx.MockTransport
4
+ instead of real HTTP. All real service classes work unmodified.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ import httpx
12
+
13
+ from vclient.client import VClient
14
+ from vclient.testing._router import _FakeRouter
15
+
16
+
17
+ class FakeVClient(VClient):
18
+ """A fake VClient for testing that uses mock HTTP transport.
19
+
20
+ Drop-in replacement for VClient that returns auto-generated responses
21
+ for all endpoints. Registers itself as the default client so factory
22
+ functions like campaigns_service() work without configuration.
23
+
24
+ Example:
25
+ ```python
26
+ from vclient.testing import FakeVClient
27
+ from vclient import campaigns_service
28
+
29
+ async with FakeVClient() as client:
30
+ campaigns = campaigns_service("user123")
31
+ result = await campaigns.list_all()
32
+ ```
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *,
38
+ default_company_id: str = "fake-company",
39
+ set_as_default: bool = True,
40
+ **kwargs: Any,
41
+ ) -> None:
42
+ self._router = _FakeRouter()
43
+ super().__init__(
44
+ base_url="https://fake.valentina-api.test",
45
+ api_key="fake-api-key",
46
+ default_company_id=default_company_id,
47
+ set_as_default=set_as_default,
48
+ **kwargs,
49
+ )
50
+
51
+ def _create_http_client(self) -> httpx.AsyncClient:
52
+ """Create an HTTP client backed by the fake router."""
53
+ return httpx.AsyncClient(
54
+ transport=httpx.MockTransport(self._router.handle),
55
+ base_url="https://fake.valentina-api.test",
56
+ )
57
+
58
+ def add_route(
59
+ self,
60
+ method: str,
61
+ pattern: str,
62
+ *,
63
+ json: dict[str, Any],
64
+ status_code: int = 200,
65
+ ) -> None:
66
+ """Add a custom route override.
67
+
68
+ Args:
69
+ method: HTTP method (GET, POST, PATCH, DELETE, PUT).
70
+ pattern: Endpoint pattern from vclient.endpoints.Endpoints.
71
+ json: The JSON response body to return.
72
+ status_code: HTTP status code to return (default 200).
73
+ """
74
+ self._router.add_route(method, pattern, json=json, status_code=status_code)