cadwyn 3.15.10__py3-none-any.whl → 4.1.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/_compat.py DELETED
@@ -1,151 +0,0 @@
1
- import ast
2
- import dataclasses
3
- import inspect
4
- from typing import Any, TypeAlias
5
-
6
- import pydantic
7
- from fastapi._compat import ModelField as FastAPIModelField
8
- from pydantic import BaseModel, Field
9
-
10
- ModelField: TypeAlias = Any # pyright: ignore[reportRedeclaration]
11
- PydanticUndefined: TypeAlias = Any
12
- VALIDATOR_CONFIG_KEY = "__validators__"
13
-
14
- try:
15
- PYDANTIC_V2 = False
16
-
17
- from pydantic.fields import FieldInfo, ModelField # pyright: ignore # noqa: PGH003
18
- from pydantic.fields import Undefined as PydanticUndefined # pyright: ignore # noqa: PGH003
19
-
20
- _all_field_arg_names = []
21
- EXTRA_FIELD_NAME = "extra"
22
- except ImportError:
23
- PYDANTIC_V2 = True
24
-
25
- from pydantic.fields import FieldInfo
26
- from pydantic_core import PydanticUndefined # pyright: ignore # noqa: PGH003
27
-
28
- ModelField: TypeAlias = FieldInfo # pyright: ignore # noqa: PGH003
29
- _all_field_arg_names = sorted(
30
- [
31
- name
32
- for name, param in inspect.signature(Field).parameters.items()
33
- if param.kind in {inspect._ParameterKind.KEYWORD_ONLY, inspect._ParameterKind.POSITIONAL_OR_KEYWORD}
34
- ],
35
- )
36
- EXTRA_FIELD_NAME = "json_schema_extra"
37
-
38
-
39
- _empty_field_info = Field()
40
- dict_of_empty_field_info = {k: getattr(_empty_field_info, k) for k in FieldInfo.__slots__}
41
-
42
-
43
- def is_pydantic_1_constrained_type(value: object):
44
- """This method only works for pydanticV1. It is always False in PydanticV2"""
45
- return isinstance(value, type) and value.__name__.startswith("Constrained") and value.__name__.endswith("Value")
46
-
47
-
48
- def is_constrained_type(value: object):
49
- if PYDANTIC_V2:
50
- import annotated_types
51
-
52
- return isinstance(value, annotated_types.Len | annotated_types.Interval | pydantic.StringConstraints)
53
-
54
- else:
55
- return is_pydantic_1_constrained_type(value)
56
-
57
-
58
- @dataclasses.dataclass(slots=True)
59
- class PydanticFieldWrapper:
60
- """We DO NOT maintain field.metadata at all"""
61
-
62
- annotation: Any
63
-
64
- init_model_field: dataclasses.InitVar[ModelField]
65
- field_info: FieldInfo = dataclasses.field(init=False)
66
-
67
- annotation_ast: ast.expr | None = None
68
- # In the expressions "foo: str | None = None" and "foo: str | None = Field(default=None)"
69
- # the value_ast is "None" and "Field(default=None)" respectively
70
- value_ast: ast.expr | None = None
71
-
72
- def __post_init__(self, init_model_field: ModelField):
73
- if isinstance(init_model_field, FieldInfo):
74
- self.field_info = init_model_field
75
- else:
76
- self.field_info = init_model_field.field_info
77
-
78
- def update_attribute(self, *, name: str, value: Any):
79
- if PYDANTIC_V2:
80
- self.field_info._attributes_set[name] = value
81
- else:
82
- setattr(self.field_info, name, value)
83
-
84
- def delete_attribute(self, *, name: str) -> None:
85
- if PYDANTIC_V2:
86
- self.field_info._attributes_set.pop(name)
87
- else:
88
- setattr(self.field_info, name, PydanticUndefined)
89
-
90
- @property
91
- def passed_field_attributes(self):
92
- if PYDANTIC_V2:
93
- attributes = {
94
- attr_name: self.field_info._attributes_set[attr_name]
95
- for attr_name in _all_field_arg_names
96
- if attr_name in self.field_info._attributes_set
97
- }
98
- # PydanticV2 always adds frozen to _attributes_set but we don't want it if it wasn't explicitly set
99
- if attributes.get("frozen", ...) is None:
100
- attributes.pop("frozen")
101
- return attributes
102
-
103
- else:
104
- attributes = {
105
- attr_name: attr_val
106
- for attr_name, default_attr_val in dict_of_empty_field_info.items()
107
- if attr_name != EXTRA_FIELD_NAME
108
- and (attr_val := getattr(self.field_info, attr_name)) != default_attr_val
109
- }
110
- extras = getattr(self.field_info, EXTRA_FIELD_NAME) or {}
111
- return attributes | extras
112
-
113
-
114
- def get_annotation_from_model_field(model: ModelField) -> Any:
115
- if PYDANTIC_V2:
116
- return model.field_info.annotation
117
- else:
118
- return model.annotation
119
-
120
-
121
- def model_fields(model: type[BaseModel]) -> dict[str, FieldInfo]:
122
- if PYDANTIC_V2:
123
- return model.model_fields
124
- else:
125
- return model.__fields__ # pyright: ignore[reportDeprecated]
126
-
127
-
128
- def model_dump(model: BaseModel, by_alias: bool = False, exclude_unset: bool = False) -> dict[str, Any]:
129
- if PYDANTIC_V2:
130
- return model.model_dump(by_alias=by_alias, exclude_unset=exclude_unset)
131
- else:
132
- return model.dict(by_alias=by_alias, exclude_unset=exclude_unset) # pyright: ignore[reportDeprecated]
133
-
134
-
135
- def rebuild_fastapi_body_param(old_body_param: FastAPIModelField, new_body_param_type: type[BaseModel]):
136
- kwargs: dict[str, Any] = {"name": old_body_param.name, "field_info": old_body_param.field_info}
137
- if PYDANTIC_V2:
138
- old_body_param.field_info.annotation = new_body_param_type
139
- kwargs.update({"mode": old_body_param.mode})
140
- else:
141
- kwargs.update(
142
- {
143
- "type_": new_body_param_type,
144
- "class_validators": old_body_param.class_validators, # pyright: ignore[reportAttributeAccessIssue]
145
- "default": old_body_param.default,
146
- "required": old_body_param.required,
147
- "model_config": old_body_param.model_config, # pyright: ignore[reportAttributeAccessIssue]
148
- "alias": old_body_param.alias,
149
- },
150
- )
151
- return FastAPIModelField(**kwargs)
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.
@@ -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_comments.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_comments.Module,
180
- context: CodegenContext,
181
- ) -> ast_comments.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_comments.Module,
192
- context: CodegenContext,
193
- ) -> ast_comments.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_comments.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_comments.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