valentina-python-client 1.6.1__tar.gz → 1.7.2__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.6.1 → valentina_python_client-1.7.2}/PKG-INFO +3 -3
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/README.md +2 -2
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/pyproject.toml +20 -8
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/__init__.py +39 -5
- valentina_python_client-1.7.2/src/vclient/_codegen.py +368 -0
- valentina_python_client-1.7.2/src/vclient/_sync/__init__.py +45 -0
- valentina_python_client-1.7.2/src/vclient/_sync/client.py +542 -0
- valentina_python_client-1.7.2/src/vclient/_sync/registry.py +434 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/__init__.py +38 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/base.py +626 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/campaign_book_chapters.py +418 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/campaign_books.py +510 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/campaigns.py +502 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/character_autogen.py +113 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/character_blueprint.py +600 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/character_traits.py +230 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/characters.py +1219 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/companies.py +218 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/developers.py +77 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/dicerolls.py +131 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/dictionary.py +105 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/global_admin.py +199 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/options.py +38 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/system.py +37 -0
- valentina_python_client-1.7.2/src/vclient/_sync/services/users.py +818 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/character_trait.py +1 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/character_traits.py +19 -2
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/LICENSE +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/client.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/config.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/constants.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/endpoints.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/exceptions.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/__init__.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/books.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/campaigns.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/chapters.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/character_autogen.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/character_blueprint.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/characters.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/companies.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/developers.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/diceroll.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/dictionary.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/global_admin.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/pagination.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/shared.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/system.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/models/users.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/py.typed +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/registry.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/__init__.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/base.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/campaign_book_chapters.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/campaign_books.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/campaigns.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/character_autogen.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/character_blueprint.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/characters.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/companies.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/developers.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/dicerolls.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/dictionary.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/global_admin.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/options.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/system.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/src/vclient/services/users.py +0 -0
- {valentina_python_client-1.6.1 → valentina_python_client-1.7.2}/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.
|
|
3
|
+
Version: 1.7.2
|
|
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>
|
|
@@ -26,11 +26,11 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
# Valentina Python Client
|
|
28
28
|
|
|
29
|
-
Async Python client library for accessing the Valentina Noir API.
|
|
29
|
+
Async and sync Python client library for accessing the Valentina Noir API.
|
|
30
30
|
|
|
31
31
|
## Features
|
|
32
32
|
|
|
33
|
-
- **Async
|
|
33
|
+
- **Async and sync clients** - Both `VClient` (async) and `SyncVClient` (sync) built on httpx
|
|
34
34
|
- **Type-safe** - Full type hints with Pydantic models for request/response validation
|
|
35
35
|
- **Convenient factory pattern** - Create a client once, access services from anywhere
|
|
36
36
|
- **Automatic pagination** - Stream through large datasets with `iter_all()` or fetch everything with `list_all()`
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Valentina Python Client
|
|
2
2
|
|
|
3
|
-
Async Python client library for accessing the Valentina Noir API.
|
|
3
|
+
Async and sync Python client library for accessing the Valentina Noir API.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- **Async
|
|
7
|
+
- **Async and sync clients** - Both `VClient` (async) and `SyncVClient` (sync) built on httpx
|
|
8
8
|
- **Type-safe** - Full type hints with Pydantic models for request/response validation
|
|
9
9
|
- **Convenient factory pattern** - Create a client once, access services from anywhere
|
|
10
10
|
- **Automatic pagination** - Stream through large datasets with `iter_all()` or fetch everything with `list_all()`
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
name = "valentina-python-client"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
requires-python = ">=3.13"
|
|
13
|
-
version = "1.
|
|
13
|
+
version = "1.7.2"
|
|
14
14
|
|
|
15
15
|
[project.urls]
|
|
16
16
|
Homepage = "https://docs.valentina-noir.com/python-api-client/"
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
|
|
26
26
|
[dependency-groups]
|
|
27
27
|
dev = [
|
|
28
|
-
"commitizen>=4.13.
|
|
28
|
+
"commitizen>=4.13.9",
|
|
29
29
|
"coverage>=7.13.4",
|
|
30
30
|
"duty>=1.9.0",
|
|
31
|
-
"prek>=0.3.
|
|
31
|
+
"prek>=0.3.4",
|
|
32
32
|
"pytest-anyio>=0.0.0",
|
|
33
33
|
"pytest-clarity>=1.0.1",
|
|
34
34
|
"pytest-cov>=7.0.0",
|
|
@@ -39,14 +39,14 @@
|
|
|
39
39
|
"pytest-xdist>=3.8.0",
|
|
40
40
|
"pytest>=9.0.2",
|
|
41
41
|
"respx>=0.22.0",
|
|
42
|
-
"ruff>=0.15.
|
|
42
|
+
"ruff>=0.15.4",
|
|
43
43
|
"shellcheck-py>=0.11.0.1",
|
|
44
|
-
"ty>=0.0.
|
|
45
|
-
"typos>=1.
|
|
44
|
+
"ty>=0.0.19",
|
|
45
|
+
"typos>=1.44.0",
|
|
46
46
|
"vulture>=2.14",
|
|
47
47
|
"yamllint>=1.38.0",
|
|
48
48
|
]
|
|
49
|
-
docs = ["zensical>=0.0.
|
|
49
|
+
docs = ["zensical>=0.0.24"]
|
|
50
50
|
|
|
51
51
|
[tool.commitizen]
|
|
52
52
|
bump_message = "bump(release): v$current_version → v$new_version"
|
|
@@ -121,7 +121,18 @@
|
|
|
121
121
|
"TD002", # Missing author in TODO
|
|
122
122
|
"TD003", # Missing issue link on the line following this TODO
|
|
123
123
|
]
|
|
124
|
-
per-file-ignores = { "
|
|
124
|
+
per-file-ignores = { "src/vclient/_sync/**" = [
|
|
125
|
+
"C416", # Unnecessary list comprehension (AST unparser artifact)
|
|
126
|
+
"D", # Docstring formatting (AST unparser doesn't preserve style)
|
|
127
|
+
"E501", # Line too long
|
|
128
|
+
"ERA001", # Commented-out code false positives
|
|
129
|
+
"PLR0913", # Too many arguments
|
|
130
|
+
"PLR2004", # Magic value used in comparison
|
|
131
|
+
"PLW0603", # Using global statement
|
|
132
|
+
"SLF001", # Private member accessed
|
|
133
|
+
"TRY300", # Consider moving to else block
|
|
134
|
+
"UP028", # Replace yield over for loop
|
|
135
|
+
], "scripts/*.py" = [
|
|
125
136
|
"INP001", # Implicit namespace package (no __init__.py needed)
|
|
126
137
|
"T201", # print() is intentional for CLI output
|
|
127
138
|
], "src/vclient/services/*.py" = [
|
|
@@ -173,6 +184,7 @@
|
|
|
173
184
|
max-args = 6
|
|
174
185
|
|
|
175
186
|
[tool.ruff.format]
|
|
187
|
+
exclude = ["src/vclient/_sync"]
|
|
176
188
|
indent-style = "space"
|
|
177
189
|
line-ending = "auto"
|
|
178
190
|
quote-style = "double"
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""API client for the vclient service.
|
|
2
2
|
|
|
3
3
|
Primary exports:
|
|
4
|
-
VClient: The main API client class
|
|
4
|
+
VClient: The main async API client class
|
|
5
|
+
SyncVClient: The synchronous API client class
|
|
5
6
|
|
|
6
7
|
Factory functions (primary access pattern):
|
|
7
|
-
books_service, campaigns_service, chapters_service, etc.
|
|
8
|
+
Async: books_service, campaigns_service, chapters_service, etc.
|
|
9
|
+
Sync: sync_books_service, sync_campaigns_service, sync_chapters_service, etc.
|
|
8
10
|
|
|
9
11
|
For exceptions, use: from vclient.exceptions import APIError, NotFoundError, ...
|
|
10
12
|
For models, use: from vclient.models import Character, Campaign, ...
|
|
@@ -31,6 +33,24 @@ _logger.add(
|
|
|
31
33
|
|
|
32
34
|
_logger.disable("vclient")
|
|
33
35
|
|
|
36
|
+
from vclient._sync import ( # noqa: E402
|
|
37
|
+
SyncVClient,
|
|
38
|
+
sync_books_service,
|
|
39
|
+
sync_campaigns_service,
|
|
40
|
+
sync_chapters_service,
|
|
41
|
+
sync_character_autogen_service,
|
|
42
|
+
sync_character_blueprint_service,
|
|
43
|
+
sync_character_traits_service,
|
|
44
|
+
sync_characters_service,
|
|
45
|
+
sync_companies_service,
|
|
46
|
+
sync_developer_service,
|
|
47
|
+
sync_dicerolls_service,
|
|
48
|
+
sync_dictionary_service,
|
|
49
|
+
sync_global_admin_service,
|
|
50
|
+
sync_options_service,
|
|
51
|
+
sync_system_service,
|
|
52
|
+
sync_users_service,
|
|
53
|
+
)
|
|
34
54
|
from vclient.client import VClient # noqa: E402
|
|
35
55
|
from vclient.registry import ( # noqa: E402
|
|
36
56
|
books_service,
|
|
@@ -51,9 +71,8 @@ from vclient.registry import ( # noqa: E402
|
|
|
51
71
|
)
|
|
52
72
|
|
|
53
73
|
__all__ = (
|
|
54
|
-
|
|
74
|
+
"SyncVClient",
|
|
55
75
|
"VClient",
|
|
56
|
-
# Factory functions
|
|
57
76
|
"books_service",
|
|
58
77
|
"campaigns_service",
|
|
59
78
|
"chapters_service",
|
|
@@ -67,8 +86,23 @@ __all__ = (
|
|
|
67
86
|
"dictionary_service",
|
|
68
87
|
"global_admin_service",
|
|
69
88
|
"options_service",
|
|
89
|
+
"sync_books_service",
|
|
90
|
+
"sync_campaigns_service",
|
|
91
|
+
"sync_chapters_service",
|
|
92
|
+
"sync_character_autogen_service",
|
|
93
|
+
"sync_character_blueprint_service",
|
|
94
|
+
"sync_character_traits_service",
|
|
95
|
+
"sync_characters_service",
|
|
96
|
+
"sync_companies_service",
|
|
97
|
+
"sync_developer_service",
|
|
98
|
+
"sync_dicerolls_service",
|
|
99
|
+
"sync_dictionary_service",
|
|
100
|
+
"sync_global_admin_service",
|
|
101
|
+
"sync_options_service",
|
|
102
|
+
"sync_system_service",
|
|
103
|
+
"sync_users_service",
|
|
70
104
|
"system_service",
|
|
71
105
|
"users_service",
|
|
72
106
|
)
|
|
73
107
|
|
|
74
|
-
__version__ = "1.
|
|
108
|
+
__version__ = "1.7.2"
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""AST-based code generator that transforms async source files into synchronous equivalents.
|
|
2
|
+
|
|
3
|
+
Reads the async vclient source as the single source of truth and mechanically
|
|
4
|
+
produces a ``vclient._sync`` package with all async constructs removed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
HEADER_COMMENT = "# AUTO-GENERATED — do not edit. Run 'uv run duty generate_sync' to regenerate.\n"
|
|
14
|
+
|
|
15
|
+
RENAME_CLASSES: dict[str, str] = {
|
|
16
|
+
"BaseService": "SyncBaseService",
|
|
17
|
+
"BooksService": "SyncBooksService",
|
|
18
|
+
"CampaignsService": "SyncCampaignsService",
|
|
19
|
+
"ChaptersService": "SyncChaptersService",
|
|
20
|
+
"CharacterAutogenService": "SyncCharacterAutogenService",
|
|
21
|
+
"CharacterBlueprintService": "SyncCharacterBlueprintService",
|
|
22
|
+
"CharacterTraitsService": "SyncCharacterTraitsService",
|
|
23
|
+
"CharactersService": "SyncCharactersService",
|
|
24
|
+
"CompaniesService": "SyncCompaniesService",
|
|
25
|
+
"DeveloperService": "SyncDeveloperService",
|
|
26
|
+
"DicerollService": "SyncDicerollService",
|
|
27
|
+
"DictionaryService": "SyncDictionaryService",
|
|
28
|
+
"GlobalAdminService": "SyncGlobalAdminService",
|
|
29
|
+
"OptionsService": "SyncOptionsService",
|
|
30
|
+
"SystemService": "SyncSystemService",
|
|
31
|
+
"UsersService": "SyncUsersService",
|
|
32
|
+
"VClient": "SyncVClient",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
FACTORY_RENAMES: dict[str, str] = {
|
|
36
|
+
"companies_service": "sync_companies_service",
|
|
37
|
+
"developer_service": "sync_developer_service",
|
|
38
|
+
"global_admin_service": "sync_global_admin_service",
|
|
39
|
+
"system_service": "sync_system_service",
|
|
40
|
+
"users_service": "sync_users_service",
|
|
41
|
+
"campaigns_service": "sync_campaigns_service",
|
|
42
|
+
"books_service": "sync_books_service",
|
|
43
|
+
"chapters_service": "sync_chapters_service",
|
|
44
|
+
"characters_service": "sync_characters_service",
|
|
45
|
+
"character_traits_service": "sync_character_traits_service",
|
|
46
|
+
"character_blueprint_service": "sync_character_blueprint_service",
|
|
47
|
+
"dictionary_service": "sync_dictionary_service",
|
|
48
|
+
"dicerolls_service": "sync_dicerolls_service",
|
|
49
|
+
"options_service": "sync_options_service",
|
|
50
|
+
"character_autogen_service": "sync_character_autogen_service",
|
|
51
|
+
"configure_default_client": "sync_configure_default_client",
|
|
52
|
+
"clear_default_client": "sync_clear_default_client",
|
|
53
|
+
"default_client": "sync_default_client",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
IMPORT_REWRITES: dict[str, str] = {
|
|
57
|
+
"vclient.client": "vclient._sync.client",
|
|
58
|
+
"vclient.services": "vclient._sync.services",
|
|
59
|
+
"vclient.services.base": "vclient._sync.services.base",
|
|
60
|
+
"vclient.services.companies": "vclient._sync.services.companies",
|
|
61
|
+
"vclient.services.developers": "vclient._sync.services.developers",
|
|
62
|
+
"vclient.services.global_admin": "vclient._sync.services.global_admin",
|
|
63
|
+
"vclient.services.system": "vclient._sync.services.system",
|
|
64
|
+
"vclient.services.users": "vclient._sync.services.users",
|
|
65
|
+
"vclient.services.campaigns": "vclient._sync.services.campaigns",
|
|
66
|
+
"vclient.services.campaign_books": "vclient._sync.services.campaign_books",
|
|
67
|
+
"vclient.services.campaign_book_chapters": "vclient._sync.services.campaign_book_chapters",
|
|
68
|
+
"vclient.services.characters": "vclient._sync.services.characters",
|
|
69
|
+
"vclient.services.character_traits": "vclient._sync.services.character_traits",
|
|
70
|
+
"vclient.services.character_blueprint": "vclient._sync.services.character_blueprint",
|
|
71
|
+
"vclient.services.dictionary": "vclient._sync.services.dictionary",
|
|
72
|
+
"vclient.services.dicerolls": "vclient._sync.services.dicerolls",
|
|
73
|
+
"vclient.services.options": "vclient._sync.services.options",
|
|
74
|
+
"vclient.services.character_autogen": "vclient._sync.services.character_autogen",
|
|
75
|
+
"vclient.registry": "vclient._sync.registry",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Combined lookup for renaming any identifier (class or factory function)
|
|
79
|
+
_ALL_RENAMES: dict[str, str] = {**RENAME_CLASSES, **FACTORY_RENAMES}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AsyncToSyncTransformer(ast.NodeTransformer):
|
|
83
|
+
"""Transform async Python source into synchronous equivalents.
|
|
84
|
+
|
|
85
|
+
Visit each relevant AST node and mechanically strip or rename async
|
|
86
|
+
constructs so the resulting tree is fully synchronous.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
# Async construct removal
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.FunctionDef:
|
|
94
|
+
"""Convert ``async def`` to ``def``, renaming dunder methods."""
|
|
95
|
+
self.generic_visit(node)
|
|
96
|
+
|
|
97
|
+
name = node.name
|
|
98
|
+
if name == "__aenter__":
|
|
99
|
+
name = "__enter__"
|
|
100
|
+
elif name == "__aexit__":
|
|
101
|
+
name = "__exit__"
|
|
102
|
+
else:
|
|
103
|
+
name = FACTORY_RENAMES.get(name, name)
|
|
104
|
+
|
|
105
|
+
return ast.FunctionDef(
|
|
106
|
+
name=name,
|
|
107
|
+
args=node.args,
|
|
108
|
+
body=node.body,
|
|
109
|
+
decorator_list=node.decorator_list,
|
|
110
|
+
returns=node.returns,
|
|
111
|
+
type_comment=node.type_comment,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def visit_Await(self, node: ast.Await) -> ast.expr:
|
|
115
|
+
"""Unwrap ``await expr`` to just ``expr``."""
|
|
116
|
+
self.generic_visit(node)
|
|
117
|
+
return node.value
|
|
118
|
+
|
|
119
|
+
def visit_AsyncWith(self, node: ast.AsyncWith) -> ast.With:
|
|
120
|
+
"""Convert ``async with`` to ``with``."""
|
|
121
|
+
self.generic_visit(node)
|
|
122
|
+
return ast.With(items=node.items, body=node.body)
|
|
123
|
+
|
|
124
|
+
def visit_AsyncFor(self, node: ast.AsyncFor) -> ast.For:
|
|
125
|
+
"""Convert ``async for`` to ``for``."""
|
|
126
|
+
self.generic_visit(node)
|
|
127
|
+
return ast.For(
|
|
128
|
+
target=node.target,
|
|
129
|
+
iter=node.iter,
|
|
130
|
+
body=node.body,
|
|
131
|
+
orelse=node.orelse,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
# Async comprehensions
|
|
136
|
+
# ------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
def _sync_generators(self, generators: list[ast.comprehension]) -> None:
|
|
139
|
+
"""Set ``is_async = 0`` on all generators in-place."""
|
|
140
|
+
for gen in generators:
|
|
141
|
+
gen.is_async = 0
|
|
142
|
+
|
|
143
|
+
def visit_ListComp(self, node: ast.ListComp) -> ast.ListComp:
|
|
144
|
+
"""Remove async from list comprehension generators."""
|
|
145
|
+
self.generic_visit(node)
|
|
146
|
+
self._sync_generators(node.generators)
|
|
147
|
+
return node
|
|
148
|
+
|
|
149
|
+
def visit_SetComp(self, node: ast.SetComp) -> ast.SetComp:
|
|
150
|
+
"""Remove async from set comprehension generators."""
|
|
151
|
+
self.generic_visit(node)
|
|
152
|
+
self._sync_generators(node.generators)
|
|
153
|
+
return node
|
|
154
|
+
|
|
155
|
+
def visit_GeneratorExp(self, node: ast.GeneratorExp) -> ast.GeneratorExp:
|
|
156
|
+
"""Remove async from generator expression generators."""
|
|
157
|
+
self.generic_visit(node)
|
|
158
|
+
self._sync_generators(node.generators)
|
|
159
|
+
return node
|
|
160
|
+
|
|
161
|
+
# ------------------------------------------------------------------
|
|
162
|
+
# Name / Attribute renaming
|
|
163
|
+
# ------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def visit_Name(self, node: ast.Name) -> ast.Name:
|
|
166
|
+
"""Rename ``AsyncIterator`` to ``Iterator`` and service class names."""
|
|
167
|
+
self.generic_visit(node)
|
|
168
|
+
if node.id == "AsyncIterator":
|
|
169
|
+
node.id = "Iterator"
|
|
170
|
+
elif node.id in _ALL_RENAMES:
|
|
171
|
+
node.id = _ALL_RENAMES[node.id]
|
|
172
|
+
return node
|
|
173
|
+
|
|
174
|
+
def visit_Attribute(self, node: ast.Attribute) -> ast.Attribute:
|
|
175
|
+
"""Rename ``AsyncClient`` to ``Client`` and ``aclose`` to ``close``."""
|
|
176
|
+
self.generic_visit(node)
|
|
177
|
+
if node.attr == "AsyncClient":
|
|
178
|
+
node.attr = "Client"
|
|
179
|
+
elif node.attr == "aclose":
|
|
180
|
+
node.attr = "close"
|
|
181
|
+
elif node.attr in _ALL_RENAMES:
|
|
182
|
+
node.attr = _ALL_RENAMES[node.attr]
|
|
183
|
+
return node
|
|
184
|
+
|
|
185
|
+
# ------------------------------------------------------------------
|
|
186
|
+
# Class / function definition renaming
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef:
|
|
190
|
+
"""Rename class definitions found in RENAME_CLASSES."""
|
|
191
|
+
self.generic_visit(node)
|
|
192
|
+
if node.name in RENAME_CLASSES:
|
|
193
|
+
node.name = RENAME_CLASSES[node.name]
|
|
194
|
+
return node
|
|
195
|
+
|
|
196
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef:
|
|
197
|
+
"""Rename factory function definitions found in FACTORY_RENAMES."""
|
|
198
|
+
self.generic_visit(node)
|
|
199
|
+
if node.name in FACTORY_RENAMES:
|
|
200
|
+
node.name = FACTORY_RENAMES[node.name]
|
|
201
|
+
return node
|
|
202
|
+
|
|
203
|
+
# ------------------------------------------------------------------
|
|
204
|
+
# String annotations (TYPE_CHECKING)
|
|
205
|
+
# ------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
def visit_Constant(self, node: ast.Constant) -> ast.Constant:
|
|
208
|
+
"""Rename class names inside string annotations."""
|
|
209
|
+
if isinstance(node.value, str):
|
|
210
|
+
value = node.value
|
|
211
|
+
for old, new in _ALL_RENAMES.items():
|
|
212
|
+
value = value.replace(old, new)
|
|
213
|
+
node.value = value
|
|
214
|
+
return node
|
|
215
|
+
|
|
216
|
+
# ------------------------------------------------------------------
|
|
217
|
+
# Import rewriting
|
|
218
|
+
# ------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> ast.ImportFrom:
|
|
221
|
+
"""Rewrite ``from vclient.x import Y`` paths and rename imported names."""
|
|
222
|
+
if node.module and node.module in IMPORT_REWRITES:
|
|
223
|
+
node.module = IMPORT_REWRITES[node.module]
|
|
224
|
+
|
|
225
|
+
if node.names:
|
|
226
|
+
for alias in node.names:
|
|
227
|
+
if alias.name == "AsyncIterator":
|
|
228
|
+
alias.name = "Iterator"
|
|
229
|
+
elif alias.name in _ALL_RENAMES:
|
|
230
|
+
alias.name = _ALL_RENAMES[alias.name]
|
|
231
|
+
|
|
232
|
+
self.generic_visit(node)
|
|
233
|
+
return node
|
|
234
|
+
|
|
235
|
+
def visit_Import(self, node: ast.Import) -> ast.Import:
|
|
236
|
+
"""Replace ``import asyncio`` with ``import time``."""
|
|
237
|
+
for alias in node.names:
|
|
238
|
+
if alias.name == "asyncio":
|
|
239
|
+
alias.name = "time"
|
|
240
|
+
return node
|
|
241
|
+
|
|
242
|
+
# ------------------------------------------------------------------
|
|
243
|
+
# Call rewriting
|
|
244
|
+
# ------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
def visit_Call(self, node: ast.Call) -> ast.Call:
|
|
247
|
+
"""Replace ``asyncio.sleep()`` with ``time.sleep()``."""
|
|
248
|
+
self.generic_visit(node)
|
|
249
|
+
if (
|
|
250
|
+
isinstance(node.func, ast.Attribute)
|
|
251
|
+
and isinstance(node.func.value, ast.Name)
|
|
252
|
+
and node.func.value.id == "asyncio"
|
|
253
|
+
and node.func.attr == "sleep"
|
|
254
|
+
):
|
|
255
|
+
node.func.value.id = "time"
|
|
256
|
+
return node
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
# File-level helpers
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def transform_file(source_path: Path) -> str:
|
|
265
|
+
"""Read an async source file, transform it, and return the sync version.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
source_path: Path to the async Python source file.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
The transformed synchronous source code with a generated header.
|
|
272
|
+
"""
|
|
273
|
+
source = source_path.read_text()
|
|
274
|
+
tree = ast.parse(source)
|
|
275
|
+
transformer = AsyncToSyncTransformer()
|
|
276
|
+
new_tree = transformer.visit(tree)
|
|
277
|
+
ast.fix_missing_locations(new_tree)
|
|
278
|
+
return HEADER_COMMENT + ast.unparse(new_tree) + "\n"
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _write_sync_init(path: Path) -> None:
|
|
282
|
+
"""Write the ``_sync/__init__.py`` that re-exports public names.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
path: Path to the ``_sync/__init__.py`` file to write.
|
|
286
|
+
"""
|
|
287
|
+
lines = [
|
|
288
|
+
HEADER_COMMENT,
|
|
289
|
+
"from vclient._sync.client import SyncVClient",
|
|
290
|
+
"from vclient._sync.registry import (",
|
|
291
|
+
" sync_books_service,",
|
|
292
|
+
" sync_campaigns_service,",
|
|
293
|
+
" sync_chapters_service,",
|
|
294
|
+
" sync_characters_service,",
|
|
295
|
+
" sync_character_autogen_service,",
|
|
296
|
+
" sync_character_blueprint_service,",
|
|
297
|
+
" sync_character_traits_service,",
|
|
298
|
+
" sync_clear_default_client,",
|
|
299
|
+
" sync_companies_service,",
|
|
300
|
+
" sync_configure_default_client,",
|
|
301
|
+
" sync_default_client,",
|
|
302
|
+
" sync_developer_service,",
|
|
303
|
+
" sync_dicerolls_service,",
|
|
304
|
+
" sync_dictionary_service,",
|
|
305
|
+
" sync_global_admin_service,",
|
|
306
|
+
" sync_options_service,",
|
|
307
|
+
" sync_system_service,",
|
|
308
|
+
" sync_users_service,",
|
|
309
|
+
")",
|
|
310
|
+
"",
|
|
311
|
+
"__all__ = [",
|
|
312
|
+
' "SyncVClient",',
|
|
313
|
+
' "sync_books_service",',
|
|
314
|
+
' "sync_campaigns_service",',
|
|
315
|
+
' "sync_chapters_service",',
|
|
316
|
+
' "sync_characters_service",',
|
|
317
|
+
' "sync_character_autogen_service",',
|
|
318
|
+
' "sync_character_blueprint_service",',
|
|
319
|
+
' "sync_character_traits_service",',
|
|
320
|
+
' "sync_clear_default_client",',
|
|
321
|
+
' "sync_companies_service",',
|
|
322
|
+
' "sync_configure_default_client",',
|
|
323
|
+
' "sync_default_client",',
|
|
324
|
+
' "sync_developer_service",',
|
|
325
|
+
' "sync_dicerolls_service",',
|
|
326
|
+
' "sync_dictionary_service",',
|
|
327
|
+
' "sync_global_admin_service",',
|
|
328
|
+
' "sync_options_service",',
|
|
329
|
+
' "sync_system_service",',
|
|
330
|
+
' "sync_users_service",',
|
|
331
|
+
"]",
|
|
332
|
+
"",
|
|
333
|
+
]
|
|
334
|
+
path.write_text("\n".join(lines))
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def generate_sync(src_dir: Path) -> None:
|
|
338
|
+
"""Transform all async source files and write them into the ``_sync/`` package.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
src_dir: Path to the ``src/vclient`` directory.
|
|
342
|
+
"""
|
|
343
|
+
sync_dir = src_dir / "_sync"
|
|
344
|
+
sync_dir.mkdir(exist_ok=True)
|
|
345
|
+
|
|
346
|
+
# Transform top-level modules
|
|
347
|
+
for source_file in ["client.py", "registry.py"]:
|
|
348
|
+
source_path = src_dir / source_file
|
|
349
|
+
if source_path.exists():
|
|
350
|
+
output_path = sync_dir / source_file
|
|
351
|
+
output_path.write_text(transform_file(source_path))
|
|
352
|
+
|
|
353
|
+
# Transform services
|
|
354
|
+
services_src = src_dir / "services"
|
|
355
|
+
services_dst = sync_dir / "services"
|
|
356
|
+
services_dst.mkdir(exist_ok=True)
|
|
357
|
+
|
|
358
|
+
for source_path in sorted(services_src.glob("*.py")):
|
|
359
|
+
output_path = services_dst / source_path.name
|
|
360
|
+
output_path.write_text(transform_file(source_path))
|
|
361
|
+
|
|
362
|
+
# Write the _sync/__init__.py
|
|
363
|
+
_write_sync_init(sync_dir / "__init__.py")
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
if __name__ == "__main__":
|
|
367
|
+
project_root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("src/vclient")
|
|
368
|
+
generate_sync(project_root)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# AUTO-GENERATED — do not edit. Run 'uv run duty generate_sync' to regenerate.
|
|
2
|
+
|
|
3
|
+
from vclient._sync.client import SyncVClient
|
|
4
|
+
from vclient._sync.registry import (
|
|
5
|
+
sync_books_service,
|
|
6
|
+
sync_campaigns_service,
|
|
7
|
+
sync_chapters_service,
|
|
8
|
+
sync_character_autogen_service,
|
|
9
|
+
sync_character_blueprint_service,
|
|
10
|
+
sync_character_traits_service,
|
|
11
|
+
sync_characters_service,
|
|
12
|
+
sync_clear_default_client,
|
|
13
|
+
sync_companies_service,
|
|
14
|
+
sync_configure_default_client,
|
|
15
|
+
sync_default_client,
|
|
16
|
+
sync_developer_service,
|
|
17
|
+
sync_dicerolls_service,
|
|
18
|
+
sync_dictionary_service,
|
|
19
|
+
sync_global_admin_service,
|
|
20
|
+
sync_options_service,
|
|
21
|
+
sync_system_service,
|
|
22
|
+
sync_users_service,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"SyncVClient",
|
|
27
|
+
"sync_books_service",
|
|
28
|
+
"sync_campaigns_service",
|
|
29
|
+
"sync_chapters_service",
|
|
30
|
+
"sync_character_autogen_service",
|
|
31
|
+
"sync_character_blueprint_service",
|
|
32
|
+
"sync_character_traits_service",
|
|
33
|
+
"sync_characters_service",
|
|
34
|
+
"sync_clear_default_client",
|
|
35
|
+
"sync_companies_service",
|
|
36
|
+
"sync_configure_default_client",
|
|
37
|
+
"sync_default_client",
|
|
38
|
+
"sync_developer_service",
|
|
39
|
+
"sync_dicerolls_service",
|
|
40
|
+
"sync_dictionary_service",
|
|
41
|
+
"sync_global_admin_service",
|
|
42
|
+
"sync_options_service",
|
|
43
|
+
"sync_system_service",
|
|
44
|
+
"sync_users_service",
|
|
45
|
+
]
|