cadwyn 3.15.9__py3-none-any.whl → 4.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of cadwyn might be problematic. Click here for more details.
- cadwyn/__init__.py +25 -8
- cadwyn/__main__.py +46 -90
- cadwyn/_asts.py +9 -133
- cadwyn/_importer.py +31 -0
- cadwyn/_render.py +152 -0
- cadwyn/_utils.py +7 -107
- cadwyn/applications.py +5 -34
- cadwyn/exceptions.py +11 -3
- cadwyn/middleware.py +4 -4
- cadwyn/route_generation.py +22 -450
- cadwyn/routing.py +2 -5
- cadwyn/schema_generation.py +946 -0
- cadwyn/structure/__init__.py +0 -2
- cadwyn/structure/schemas.py +50 -49
- cadwyn/structure/versions.py +24 -137
- {cadwyn-3.15.9.dist-info → cadwyn-4.0.0.dist-info}/METADATA +4 -5
- cadwyn-4.0.0.dist-info/RECORD +27 -0
- cadwyn/_compat.py +0 -151
- cadwyn/_package_utils.py +0 -45
- cadwyn/codegen/README.md +0 -10
- cadwyn/codegen/__init__.py +0 -10
- cadwyn/codegen/_common.py +0 -168
- cadwyn/codegen/_main.py +0 -279
- cadwyn/codegen/_plugins/__init__.py +0 -0
- cadwyn/codegen/_plugins/class_migrations.py +0 -423
- cadwyn/codegen/_plugins/class_rebuilding.py +0 -109
- cadwyn/codegen/_plugins/class_renaming.py +0 -49
- cadwyn/codegen/_plugins/import_auto_adding.py +0 -64
- cadwyn/codegen/_plugins/module_migrations.py +0 -15
- cadwyn/main.py +0 -11
- cadwyn/structure/modules.py +0 -39
- cadwyn-3.15.9.dist-info/RECORD +0 -38
- {cadwyn-3.15.9.dist-info → cadwyn-4.0.0.dist-info}/LICENSE +0 -0
- {cadwyn-3.15.9.dist-info → cadwyn-4.0.0.dist-info}/WHEEL +0 -0
- {cadwyn-3.15.9.dist-info → cadwyn-4.0.0.dist-info}/entry_points.txt +0 -0
cadwyn/_package_utils.py
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import ast
|
|
2
|
-
import inspect
|
|
3
|
-
from datetime import date
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from types import ModuleType
|
|
6
|
-
|
|
7
|
-
from cadwyn.exceptions import CodeGenerationError
|
|
8
|
-
|
|
9
|
-
IdentifierPythonPath = str
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def get_absolute_python_path_of_import(node: ast.ImportFrom, module_python_path: str):
|
|
13
|
-
python_path = ".".join(module_python_path.split(".")[0 : -node.level])
|
|
14
|
-
result = []
|
|
15
|
-
if node.module:
|
|
16
|
-
result.append(node.module)
|
|
17
|
-
if python_path:
|
|
18
|
-
result.append(python_path)
|
|
19
|
-
return ".".join(result)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def get_cls_pythonpath(cls: type) -> IdentifierPythonPath:
|
|
23
|
-
return f"{cls.__module__}.{cls.__name__}"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def get_version_dir_path(template_package: ModuleType, version: date) -> Path:
|
|
27
|
-
template_dir = get_package_path_from_module(template_package)
|
|
28
|
-
return template_dir.with_name(get_version_dir_name(version))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def get_package_path_from_module(template_package: ModuleType) -> Path:
|
|
32
|
-
# Can be cached in the future to gain some speedups
|
|
33
|
-
file = inspect.getsourcefile(template_package)
|
|
34
|
-
|
|
35
|
-
# I am too lazy to reproduce this error correctly
|
|
36
|
-
if file is None: # pragma: no cover
|
|
37
|
-
raise CodeGenerationError(f'Package "{template_package}" has no source file')
|
|
38
|
-
file = Path(file)
|
|
39
|
-
if not file.name == "__init__.py":
|
|
40
|
-
raise CodeGenerationError(f'"{template_package}" is not a package')
|
|
41
|
-
return file.parent
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def get_version_dir_name(version: date):
|
|
45
|
-
return "v" + version.isoformat().replace("-", "_")
|
cadwyn/codegen/README.md
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Codegen
|
|
2
|
-
|
|
3
|
-
main.py -- contains the real core of codegen
|
|
4
|
-
asts.py -- contains the majority of ast-related utilities in cadwyn
|
|
5
|
-
common.py -- contains all the data structures used by cadwyn code generation and its plugins
|
|
6
|
-
plugins/class_rebuilding.py -- plugin for building enums + schemas from their versions in context
|
|
7
|
-
plugins/class_renaming.py -- plugin for globally renaming classes such as schemas or enums
|
|
8
|
-
plugins/import_auto_adding.py -- plugin for adding missing imports from other files or libraries
|
|
9
|
-
|
|
10
|
-
You are free to overwrite any and all default plugins using the optional arguments of `generate_code_for_versioned_packages`. Please note our use of `ast_comments` module for preserving comments that we add. Please also note that codegen migrations apply strictly to the toplevel of the module. So if you want to visit some inner structure -- you'd have to find it yourself.
|
cadwyn/codegen/__init__.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
from ._common import CodegenContext, GlobalCodegenContext
|
|
2
|
-
from ._main import DEFAULT_CODEGEN_MIGRATION_PLUGINS, DEFAULT_CODEGEN_PLUGINS, generate_code_for_versioned_packages
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"generate_code_for_versioned_packages",
|
|
6
|
-
"CodegenContext",
|
|
7
|
-
"GlobalCodegenContext",
|
|
8
|
-
"DEFAULT_CODEGEN_PLUGINS",
|
|
9
|
-
"DEFAULT_CODEGEN_MIGRATION_PLUGINS",
|
|
10
|
-
]
|
cadwyn/codegen/_common.py
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import ast
|
|
2
|
-
import dataclasses
|
|
3
|
-
import inspect
|
|
4
|
-
import textwrap
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from enum import Enum
|
|
7
|
-
from functools import cache
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from types import ModuleType
|
|
10
|
-
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeAlias, TypeVar, cast
|
|
11
|
-
|
|
12
|
-
from pydantic import BaseModel
|
|
13
|
-
from typing_extensions import Self
|
|
14
|
-
|
|
15
|
-
from cadwyn._compat import PydanticFieldWrapper, model_fields
|
|
16
|
-
from cadwyn._package_utils import IdentifierPythonPath
|
|
17
|
-
from cadwyn.exceptions import CodeGenerationError
|
|
18
|
-
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
|
|
21
|
-
|
|
22
|
-
from .._asts import _ValidatorWrapper, get_validator_info_or_none
|
|
23
|
-
|
|
24
|
-
_FieldName: TypeAlias = str
|
|
25
|
-
_CodegenPluginASTType = TypeVar("_CodegenPluginASTType", bound=ast.AST)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclasses.dataclass(slots=True)
|
|
29
|
-
class PydanticModelWrapper:
|
|
30
|
-
cls: type[BaseModel]
|
|
31
|
-
name: str
|
|
32
|
-
fields: dict[_FieldName, PydanticFieldWrapper]
|
|
33
|
-
validators: dict[_FieldName, _ValidatorWrapper]
|
|
34
|
-
annotations: dict[str, Any] = dataclasses.field(init=False, repr=False)
|
|
35
|
-
_parents: list[Self] | None = dataclasses.field(init=False, default=None)
|
|
36
|
-
|
|
37
|
-
def __post_init__(self):
|
|
38
|
-
self.annotations = self.cls.__annotations__.copy()
|
|
39
|
-
|
|
40
|
-
def _get_parents(self, schemas: "dict[IdentifierPythonPath, Self]"):
|
|
41
|
-
if self._parents is not None:
|
|
42
|
-
return self._parents
|
|
43
|
-
parents = []
|
|
44
|
-
for base in self.cls.mro()[1:]:
|
|
45
|
-
schema_path = f"{base.__module__}.{base.__name__}"
|
|
46
|
-
|
|
47
|
-
if schema_path in schemas:
|
|
48
|
-
parents.append(schemas[schema_path])
|
|
49
|
-
elif issubclass(base, BaseModel):
|
|
50
|
-
fields, validators = get_fields_and_validators_from_model(base)
|
|
51
|
-
parents.append(type(self)(base, base.__name__, fields, validators))
|
|
52
|
-
self._parents = parents
|
|
53
|
-
return parents
|
|
54
|
-
|
|
55
|
-
def _get_defined_fields_through_mro(
|
|
56
|
-
self, schemas: "dict[IdentifierPythonPath, Self]"
|
|
57
|
-
) -> dict[str, PydanticFieldWrapper]:
|
|
58
|
-
fields = {}
|
|
59
|
-
|
|
60
|
-
for parent in reversed(self._get_parents(schemas)):
|
|
61
|
-
fields |= parent.fields
|
|
62
|
-
|
|
63
|
-
return fields | self.fields
|
|
64
|
-
|
|
65
|
-
def _get_defined_annotations_through_mro(self, schemas: "dict[IdentifierPythonPath, Self]") -> dict[str, Any]:
|
|
66
|
-
annotations = {}
|
|
67
|
-
|
|
68
|
-
for parent in reversed(self._get_parents(schemas)):
|
|
69
|
-
annotations |= parent.annotations
|
|
70
|
-
|
|
71
|
-
return annotations | self.annotations
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
@cache
|
|
75
|
-
def get_fields_and_validators_from_model(
|
|
76
|
-
cls: type,
|
|
77
|
-
) -> tuple[dict[_FieldName, PydanticFieldWrapper], dict[_FieldName, _ValidatorWrapper]]:
|
|
78
|
-
if not isinstance(cls, type) or not issubclass(cls, BaseModel):
|
|
79
|
-
raise CodeGenerationError(f"Model {cls} is not a subclass of BaseModel")
|
|
80
|
-
|
|
81
|
-
fields = model_fields(cls)
|
|
82
|
-
try:
|
|
83
|
-
source = inspect.getsource(cls)
|
|
84
|
-
except OSError: # pragma: no cover # It is covered by tests but not on every platform
|
|
85
|
-
return (
|
|
86
|
-
{
|
|
87
|
-
field_name: PydanticFieldWrapper(annotation=field.annotation, init_model_field=field)
|
|
88
|
-
for field_name, field in fields.items()
|
|
89
|
-
},
|
|
90
|
-
{},
|
|
91
|
-
)
|
|
92
|
-
else:
|
|
93
|
-
cls_ast = cast(ast.ClassDef, ast.parse(textwrap.dedent(source)).body[0])
|
|
94
|
-
validators: dict[str, _ValidatorWrapper] = {}
|
|
95
|
-
|
|
96
|
-
validators_and_nones = (
|
|
97
|
-
get_validator_info_or_none(node)
|
|
98
|
-
for node in cls_ast.body
|
|
99
|
-
if isinstance(node, ast.FunctionDef) and node.decorator_list
|
|
100
|
-
)
|
|
101
|
-
validators = {validator.func_ast.name: validator for validator in validators_and_nones if validator is not None}
|
|
102
|
-
|
|
103
|
-
return (
|
|
104
|
-
{
|
|
105
|
-
node.target.id: PydanticFieldWrapper(
|
|
106
|
-
annotation=fields[node.target.id].annotation,
|
|
107
|
-
init_model_field=fields[node.target.id],
|
|
108
|
-
annotation_ast=node.annotation,
|
|
109
|
-
value_ast=node.value,
|
|
110
|
-
)
|
|
111
|
-
for node in cls_ast.body
|
|
112
|
-
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.target.id in fields
|
|
113
|
-
},
|
|
114
|
-
validators,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@dataclass(slots=True)
|
|
119
|
-
class _EnumWrapper:
|
|
120
|
-
cls: type[Enum]
|
|
121
|
-
members: dict[_FieldName, Any]
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@dataclass(slots=True)
|
|
125
|
-
class _ModuleWrapper:
|
|
126
|
-
value: ModuleType
|
|
127
|
-
extra_imports: list[ast.Import | ast.ImportFrom] = dataclasses.field(default_factory=list)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@dataclasses.dataclass(slots=True, kw_only=True)
|
|
131
|
-
class GlobalCodegenContext:
|
|
132
|
-
current_version: "Version | HeadVersion"
|
|
133
|
-
latest_version: "Version" = dataclasses.field(init=False)
|
|
134
|
-
versions: "list[Version]"
|
|
135
|
-
schemas: dict[IdentifierPythonPath, PydanticModelWrapper] = dataclasses.field(repr=False)
|
|
136
|
-
enums: dict[IdentifierPythonPath, _EnumWrapper] = dataclasses.field(repr=False)
|
|
137
|
-
modules: dict[IdentifierPythonPath, _ModuleWrapper] = dataclasses.field(repr=False)
|
|
138
|
-
version_bundle: "VersionBundle"
|
|
139
|
-
extra: dict[str, Any]
|
|
140
|
-
|
|
141
|
-
def __post_init__(self):
|
|
142
|
-
self.latest_version = max(self.versions, key=lambda v: v.value)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
@dataclasses.dataclass(slots=True, kw_only=True)
|
|
146
|
-
class CodegenContext(GlobalCodegenContext):
|
|
147
|
-
# This attribute is extremely useful for calculating relative imports
|
|
148
|
-
index_of_head_package_dir_in_module_python_path: int
|
|
149
|
-
module_python_path: str
|
|
150
|
-
module_path: Path
|
|
151
|
-
template_module: ModuleType
|
|
152
|
-
all_names_defined_on_toplevel_of_file: dict[IdentifierPythonPath, str]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
class CodegenPlugin(Protocol, Generic[_CodegenPluginASTType]):
|
|
156
|
-
@property
|
|
157
|
-
def node_type(self) -> type[_CodegenPluginASTType]:
|
|
158
|
-
raise NotImplementedError
|
|
159
|
-
|
|
160
|
-
@staticmethod
|
|
161
|
-
def __call__(node: _CodegenPluginASTType, context: CodegenContext) -> _CodegenPluginASTType:
|
|
162
|
-
raise NotImplementedError
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
class MigrationPlugin(Protocol):
|
|
166
|
-
@staticmethod
|
|
167
|
-
def __call__(context: GlobalCodegenContext) -> None:
|
|
168
|
-
raise NotImplementedError
|
cadwyn/codegen/_main.py
DELETED
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
import ast
|
|
2
|
-
import importlib
|
|
3
|
-
import os
|
|
4
|
-
import shutil
|
|
5
|
-
from collections.abc import Collection, Generator, Sequence
|
|
6
|
-
from copy import deepcopy
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from types import ModuleType
|
|
9
|
-
from typing import Any, overload
|
|
10
|
-
|
|
11
|
-
import ast_comments
|
|
12
|
-
from typing_extensions import deprecated
|
|
13
|
-
|
|
14
|
-
from cadwyn._asts import get_all_names_defined_at_toplevel_of_module, read_python_module
|
|
15
|
-
from cadwyn._package_utils import IdentifierPythonPath, get_package_path_from_module, get_version_dir_path
|
|
16
|
-
from cadwyn._utils import get_index_of_head_schema_dir_in_module_python_path
|
|
17
|
-
from cadwyn.codegen._common import (
|
|
18
|
-
CodegenContext,
|
|
19
|
-
CodegenPlugin,
|
|
20
|
-
GlobalCodegenContext,
|
|
21
|
-
MigrationPlugin,
|
|
22
|
-
PydanticModelWrapper,
|
|
23
|
-
_EnumWrapper,
|
|
24
|
-
_ModuleWrapper,
|
|
25
|
-
get_fields_and_validators_from_model,
|
|
26
|
-
)
|
|
27
|
-
from cadwyn.codegen._plugins.class_migrations import class_migration_plugin
|
|
28
|
-
from cadwyn.codegen._plugins.class_rebuilding import ClassRebuildingPlugin
|
|
29
|
-
from cadwyn.codegen._plugins.class_renaming import ClassRenamingPlugin
|
|
30
|
-
from cadwyn.codegen._plugins.import_auto_adding import ImportAutoAddingPlugin
|
|
31
|
-
from cadwyn.codegen._plugins.module_migrations import module_migration_plugin
|
|
32
|
-
from cadwyn.structure.versions import Version, VersionBundle
|
|
33
|
-
|
|
34
|
-
_AUTO_GENERATION_WARNING = "# THIS FILE WAS AUTO-GENERATED BY CADWYN. DO NOT EVER TRY TO EDIT IT BY HAND\n\n"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
DEFAULT_CODEGEN_PLUGINS: tuple[CodegenPlugin, ...] = (
|
|
38
|
-
ClassRebuildingPlugin(),
|
|
39
|
-
ClassRenamingPlugin(),
|
|
40
|
-
ImportAutoAddingPlugin(),
|
|
41
|
-
)
|
|
42
|
-
DEFAULT_CODEGEN_MIGRATION_PLUGINS: tuple[MigrationPlugin, ...] = (
|
|
43
|
-
module_migration_plugin,
|
|
44
|
-
class_migration_plugin,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@overload
|
|
49
|
-
def generate_code_for_versioned_packages(
|
|
50
|
-
head_package: ModuleType,
|
|
51
|
-
versions: VersionBundle,
|
|
52
|
-
*,
|
|
53
|
-
codegen_plugins: Sequence[CodegenPlugin] = DEFAULT_CODEGEN_PLUGINS,
|
|
54
|
-
migration_plugins: Sequence[MigrationPlugin] = DEFAULT_CODEGEN_MIGRATION_PLUGINS,
|
|
55
|
-
extra_context: dict[str, Any] | None = None,
|
|
56
|
-
): ...
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@overload
|
|
60
|
-
@deprecated(
|
|
61
|
-
"ignore_coverage_for_latest_aliases is deprecated. "
|
|
62
|
-
"You do not need to pass it any longer and it is going to be deleted in the future."
|
|
63
|
-
)
|
|
64
|
-
def generate_code_for_versioned_packages(
|
|
65
|
-
head_package: ModuleType,
|
|
66
|
-
versions: VersionBundle,
|
|
67
|
-
*,
|
|
68
|
-
ignore_coverage_for_latest_aliases: bool | None = None,
|
|
69
|
-
codegen_plugins: Sequence[CodegenPlugin] = DEFAULT_CODEGEN_PLUGINS,
|
|
70
|
-
migration_plugins: Sequence[MigrationPlugin] = DEFAULT_CODEGEN_MIGRATION_PLUGINS,
|
|
71
|
-
extra_context: dict[str, Any] | None = None,
|
|
72
|
-
): ...
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def generate_code_for_versioned_packages(
|
|
76
|
-
head_package: ModuleType,
|
|
77
|
-
versions: VersionBundle,
|
|
78
|
-
*,
|
|
79
|
-
ignore_coverage_for_latest_aliases: bool | None = None,
|
|
80
|
-
codegen_plugins: Sequence[CodegenPlugin] = DEFAULT_CODEGEN_PLUGINS,
|
|
81
|
-
migration_plugins: Sequence[MigrationPlugin] = DEFAULT_CODEGEN_MIGRATION_PLUGINS,
|
|
82
|
-
extra_context: dict[str, Any] | None = None,
|
|
83
|
-
):
|
|
84
|
-
"""
|
|
85
|
-
Args:
|
|
86
|
-
head_package: The head package from which we will generate the versioned packages
|
|
87
|
-
versions: Version bundle to generate versions from the head package.
|
|
88
|
-
"""
|
|
89
|
-
extra_context = extra_context or {}
|
|
90
|
-
schemas = {}
|
|
91
|
-
for k, v in deepcopy(versions.versioned_schemas).items():
|
|
92
|
-
fields, validators = get_fields_and_validators_from_model(v)
|
|
93
|
-
schemas[k] = PydanticModelWrapper(v, v.__name__, fields, validators)
|
|
94
|
-
|
|
95
|
-
_generate_versioned_directories(
|
|
96
|
-
head_package,
|
|
97
|
-
versions=list(versions),
|
|
98
|
-
schemas=schemas,
|
|
99
|
-
enums={
|
|
100
|
-
k: _EnumWrapper(v, {member.name: member.value for member in v})
|
|
101
|
-
for k, v in deepcopy(versions.versioned_enums).items()
|
|
102
|
-
},
|
|
103
|
-
modules={k: _ModuleWrapper(module) for k, module in versions.versioned_modules.items()},
|
|
104
|
-
version_bundle=versions,
|
|
105
|
-
extra_context=extra_context | {"ignore_coverage_for_latest_aliases": ignore_coverage_for_latest_aliases},
|
|
106
|
-
codegen_plugins=codegen_plugins,
|
|
107
|
-
migration_plugins=migration_plugins,
|
|
108
|
-
)
|
|
109
|
-
# This should not affect real use cases at all but is rather useful for testing
|
|
110
|
-
importlib.invalidate_caches()
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _generate_versioned_directories(
|
|
114
|
-
template_package: ModuleType,
|
|
115
|
-
versions: list[Version],
|
|
116
|
-
schemas: dict[IdentifierPythonPath, PydanticModelWrapper],
|
|
117
|
-
enums: dict[IdentifierPythonPath, _EnumWrapper],
|
|
118
|
-
modules: dict[IdentifierPythonPath, _ModuleWrapper],
|
|
119
|
-
version_bundle: VersionBundle,
|
|
120
|
-
extra_context: dict[str, Any],
|
|
121
|
-
codegen_plugins: Collection[CodegenPlugin],
|
|
122
|
-
migration_plugins: Collection[MigrationPlugin],
|
|
123
|
-
):
|
|
124
|
-
global_context = GlobalCodegenContext(
|
|
125
|
-
current_version=version_bundle.head_version,
|
|
126
|
-
versions=versions,
|
|
127
|
-
schemas=schemas,
|
|
128
|
-
enums=enums,
|
|
129
|
-
modules=modules,
|
|
130
|
-
extra=extra_context,
|
|
131
|
-
version_bundle=version_bundle,
|
|
132
|
-
)
|
|
133
|
-
for plugin in migration_plugins:
|
|
134
|
-
plugin(global_context)
|
|
135
|
-
for version in versions:
|
|
136
|
-
print(f"Generating code for version={version.value!s}") # noqa: T201
|
|
137
|
-
global_context = GlobalCodegenContext(
|
|
138
|
-
current_version=version,
|
|
139
|
-
versions=versions,
|
|
140
|
-
schemas=schemas,
|
|
141
|
-
enums=enums,
|
|
142
|
-
modules=modules,
|
|
143
|
-
extra=extra_context,
|
|
144
|
-
version_bundle=version_bundle,
|
|
145
|
-
)
|
|
146
|
-
_generate_directory_for_version(template_package, codegen_plugins, version, global_context)
|
|
147
|
-
for plugin in migration_plugins:
|
|
148
|
-
plugin(global_context)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _generate_directory_for_version(
|
|
152
|
-
template_package: ModuleType,
|
|
153
|
-
plugins: Collection[CodegenPlugin],
|
|
154
|
-
version: Version,
|
|
155
|
-
global_context: GlobalCodegenContext,
|
|
156
|
-
):
|
|
157
|
-
template_dir = get_package_path_from_module(template_package)
|
|
158
|
-
version_dir = get_version_dir_path(template_package, version.value)
|
|
159
|
-
|
|
160
|
-
for (
|
|
161
|
-
_relative_path_to_file,
|
|
162
|
-
template_module,
|
|
163
|
-
parallel_file,
|
|
164
|
-
) in _generate_parallel_directory(
|
|
165
|
-
template_package,
|
|
166
|
-
version_dir,
|
|
167
|
-
):
|
|
168
|
-
file_source = read_python_module(template_module)
|
|
169
|
-
parsed_file = ast.parse(file_source)
|
|
170
|
-
context = _build_context(global_context, template_dir, version_dir, template_module, parallel_file, parsed_file)
|
|
171
|
-
|
|
172
|
-
parsed_file = _apply_module_level_plugins(plugins, parsed_file, context)
|
|
173
|
-
new_module = _apply_per_node_plugins(plugins, parsed_file, context)
|
|
174
|
-
parallel_file.write_text(_AUTO_GENERATION_WARNING + ast_comments.unparse(new_module))
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _apply_module_level_plugins(
|
|
178
|
-
plugins: Collection[CodegenPlugin],
|
|
179
|
-
parsed_file: ast.Module,
|
|
180
|
-
context: CodegenContext,
|
|
181
|
-
) -> ast.Module:
|
|
182
|
-
node_type = type(parsed_file)
|
|
183
|
-
for plugin in plugins:
|
|
184
|
-
if issubclass(node_type, plugin.node_type):
|
|
185
|
-
parsed_file = plugin(parsed_file, context)
|
|
186
|
-
return parsed_file
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def _apply_per_node_plugins(
|
|
190
|
-
plugins: Collection[CodegenPlugin],
|
|
191
|
-
parsed_file: ast.Module,
|
|
192
|
-
context: CodegenContext,
|
|
193
|
-
) -> ast.Module:
|
|
194
|
-
new_body = []
|
|
195
|
-
|
|
196
|
-
for node in parsed_file.body:
|
|
197
|
-
node_type = type(node)
|
|
198
|
-
for plugin in plugins:
|
|
199
|
-
if issubclass(node_type, plugin.node_type):
|
|
200
|
-
node = plugin(node, context) # noqa: PLW2901
|
|
201
|
-
new_body.append(node)
|
|
202
|
-
|
|
203
|
-
return ast.Module(body=new_body, type_ignores=[])
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _build_context(
|
|
207
|
-
global_context: GlobalCodegenContext,
|
|
208
|
-
template_dir: Path,
|
|
209
|
-
version_dir: Path,
|
|
210
|
-
template_module: ModuleType,
|
|
211
|
-
parallel_file: Path,
|
|
212
|
-
parsed_file: ast.Module,
|
|
213
|
-
):
|
|
214
|
-
if template_module.__name__.endswith(".__init__"):
|
|
215
|
-
module_python_path = template_module.__name__.removesuffix(".__init__")
|
|
216
|
-
else:
|
|
217
|
-
module_python_path = template_module.__name__
|
|
218
|
-
all_names_defined_at_toplevel_of_file = get_all_names_defined_at_toplevel_of_module(
|
|
219
|
-
parsed_file,
|
|
220
|
-
module_python_path,
|
|
221
|
-
)
|
|
222
|
-
index_of_head_package_dir_in_module_python_path = get_index_of_head_schema_dir_in_module_python_path(
|
|
223
|
-
template_module,
|
|
224
|
-
template_dir.with_name(version_dir.name),
|
|
225
|
-
)
|
|
226
|
-
return CodegenContext(
|
|
227
|
-
current_version=global_context.current_version,
|
|
228
|
-
versions=global_context.versions,
|
|
229
|
-
schemas=global_context.schemas,
|
|
230
|
-
enums=global_context.enums,
|
|
231
|
-
modules=global_context.modules,
|
|
232
|
-
extra=global_context.extra,
|
|
233
|
-
index_of_head_package_dir_in_module_python_path=index_of_head_package_dir_in_module_python_path,
|
|
234
|
-
module_python_path=module_python_path,
|
|
235
|
-
all_names_defined_on_toplevel_of_file=all_names_defined_at_toplevel_of_file,
|
|
236
|
-
template_module=template_module,
|
|
237
|
-
module_path=parallel_file,
|
|
238
|
-
version_bundle=global_context.version_bundle,
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _generate_parallel_directory(
|
|
243
|
-
template_module: ModuleType,
|
|
244
|
-
parallel_dir: Path,
|
|
245
|
-
) -> Generator[tuple[Path, ModuleType, Path], Any, None]:
|
|
246
|
-
if template_module.__file__ is None: # pragma: no cover
|
|
247
|
-
raise ValueError(
|
|
248
|
-
f"You passed a {template_module=} but it doesn't have a file "
|
|
249
|
-
"so it is impossible to generate its counterpart.",
|
|
250
|
-
)
|
|
251
|
-
dir = get_package_path_from_module(template_module)
|
|
252
|
-
parallel_dir.mkdir(exist_ok=True)
|
|
253
|
-
# >>> [cadwyn, structure, schemas]
|
|
254
|
-
template_module_python_path_parts = template_module.__name__.split(".")
|
|
255
|
-
# >>> [home, foo, bar, cadwyn, structure, schemas]
|
|
256
|
-
template_module_path_parts = Path(template_module.__file__).parent.parts
|
|
257
|
-
# >>> [home, foo, bar] = [home, foo, bar, cadwyn, structure, schemas][:-3]
|
|
258
|
-
root_module_path = Path(
|
|
259
|
-
*template_module_path_parts[: -len(template_module_python_path_parts)],
|
|
260
|
-
)
|
|
261
|
-
for subroot, dirnames, filenames in os.walk(dir):
|
|
262
|
-
original_subroot = Path(subroot)
|
|
263
|
-
parallel_subroot = parallel_dir / original_subroot.relative_to(dir)
|
|
264
|
-
if "__pycache__" in dirnames:
|
|
265
|
-
dirnames.remove("__pycache__")
|
|
266
|
-
for dirname in dirnames:
|
|
267
|
-
(parallel_subroot / dirname).mkdir(exist_ok=True)
|
|
268
|
-
for filename in filenames:
|
|
269
|
-
original_file = (original_subroot / filename).absolute()
|
|
270
|
-
parallel_file = (parallel_subroot / filename).absolute()
|
|
271
|
-
|
|
272
|
-
if filename.endswith(".py"):
|
|
273
|
-
original_module_path = ".".join(
|
|
274
|
-
original_file.relative_to(root_module_path).with_suffix("").parts,
|
|
275
|
-
)
|
|
276
|
-
original_module = importlib.import_module(original_module_path)
|
|
277
|
-
yield original_subroot.relative_to(dir), original_module, parallel_file
|
|
278
|
-
else:
|
|
279
|
-
shutil.copyfile(original_file, parallel_file)
|
|
File without changes
|