cadwyn 3.3.3__py3-none-any.whl → 3.4.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 +27 -31
- cadwyn/codegen/_asts.py +55 -28
- cadwyn/codegen/_common.py +47 -19
- cadwyn/codegen/_main.py +6 -5
- cadwyn/codegen/_plugins/class_migrations.py +269 -53
- cadwyn/codegen/_plugins/class_rebuilding.py +16 -42
- cadwyn/codegen/_plugins/import_auto_adding.py +5 -0
- cadwyn/structure/data.py +1 -1
- cadwyn/structure/schemas.py +121 -23
- cadwyn/structure/versions.py +17 -10
- {cadwyn-3.3.3.dist-info → cadwyn-3.4.0.dist-info}/METADATA +1 -1
- {cadwyn-3.3.3.dist-info → cadwyn-3.4.0.dist-info}/RECORD +15 -15
- {cadwyn-3.3.3.dist-info → cadwyn-3.4.0.dist-info}/LICENSE +0 -0
- {cadwyn-3.3.3.dist-info → cadwyn-3.4.0.dist-info}/WHEEL +0 -0
- {cadwyn-3.3.3.dist-info → cadwyn-3.4.0.dist-info}/entry_points.txt +0 -0
cadwyn/_compat.py
CHANGED
|
@@ -3,16 +3,19 @@ import dataclasses
|
|
|
3
3
|
import inspect
|
|
4
4
|
from typing import Any, TypeAlias
|
|
5
5
|
|
|
6
|
+
import pydantic
|
|
6
7
|
from fastapi._compat import ModelField as FastAPIModelField
|
|
7
8
|
from pydantic import BaseModel, Field
|
|
8
9
|
|
|
9
10
|
ModelField: TypeAlias = Any # pyright: ignore[reportGeneralTypeIssues]
|
|
10
|
-
|
|
11
|
+
PydanticUndefined: TypeAlias = Any
|
|
12
|
+
VALIDATOR_CONFIG_KEY = "__validators__"
|
|
11
13
|
|
|
12
14
|
try:
|
|
13
15
|
PYDANTIC_V2 = False
|
|
14
16
|
|
|
15
|
-
from pydantic.fields import FieldInfo, ModelField
|
|
17
|
+
from pydantic.fields import FieldInfo, ModelField # pyright: ignore # noqa: PGH003
|
|
18
|
+
from pydantic.fields import Undefined as PydanticUndefined # pyright: ignore # noqa: PGH003
|
|
16
19
|
|
|
17
20
|
_all_field_arg_names = []
|
|
18
21
|
EXTRA_FIELD_NAME = "extra"
|
|
@@ -20,10 +23,9 @@ except ImportError:
|
|
|
20
23
|
PYDANTIC_V2 = True
|
|
21
24
|
|
|
22
25
|
from pydantic.fields import FieldInfo
|
|
23
|
-
from pydantic_core import PydanticUndefined
|
|
26
|
+
from pydantic_core import PydanticUndefined # pyright: ignore # noqa: PGH003
|
|
24
27
|
|
|
25
28
|
ModelField: TypeAlias = FieldInfo # pyright: ignore # noqa: PGH003
|
|
26
|
-
Undefined = PydanticUndefined # pyright: ignore # noqa: PGH003
|
|
27
29
|
_all_field_arg_names = sorted(
|
|
28
30
|
[
|
|
29
31
|
name
|
|
@@ -38,38 +40,34 @@ _empty_field_info = Field()
|
|
|
38
40
|
dict_of_empty_field_info = {k: getattr(_empty_field_info, k) for k in FieldInfo.__slots__}
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def
|
|
43
|
+
def is_pydantic_1_constrained_type(value: object):
|
|
44
|
+
"""This method only works for pydanticV1. It is always False in PydanticV2"""
|
|
42
45
|
return isinstance(value, type) and value.__name__.startswith("Constrained") and value.__name__.endswith("Value")
|
|
43
46
|
|
|
44
47
|
|
|
45
|
-
def
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
attrs_in_value_different_from_parent_that_are_in_field_def = {
|
|
55
|
-
k: v for k, v in attrs_in_value_different_from_parent.items() if k in dict_of_empty_field_info
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
attrs_in_value_different_from_parent_that_are_not_in_field_def,
|
|
60
|
-
attrs_in_value_different_from_parent_that_are_in_field_def,
|
|
61
|
-
)
|
|
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)
|
|
62
56
|
|
|
63
57
|
|
|
64
58
|
@dataclasses.dataclass(slots=True)
|
|
65
59
|
class PydanticFieldWrapper:
|
|
60
|
+
"""We DO NOT maintain field.metadata at all"""
|
|
61
|
+
|
|
66
62
|
annotation: Any
|
|
67
63
|
|
|
68
64
|
init_model_field: dataclasses.InitVar[ModelField] # pyright: ignore[reportGeneralTypeIssues]
|
|
69
65
|
field_info: FieldInfo = dataclasses.field(init=False)
|
|
70
66
|
|
|
71
67
|
annotation_ast: ast.expr | None = None
|
|
72
|
-
|
|
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
|
|
73
71
|
|
|
74
72
|
def __post_init__(self, init_model_field: ModelField): # pyright: ignore[reportGeneralTypeIssues]
|
|
75
73
|
if isinstance(init_model_field, FieldInfo):
|
|
@@ -77,20 +75,18 @@ class PydanticFieldWrapper:
|
|
|
77
75
|
else:
|
|
78
76
|
self.field_info = init_model_field.field_info
|
|
79
77
|
|
|
80
|
-
def get_annotation_for_rendering(self):
|
|
81
|
-
if self.annotation_ast:
|
|
82
|
-
return self.annotation_ast
|
|
83
|
-
else:
|
|
84
|
-
return self.annotation
|
|
85
|
-
|
|
86
78
|
def update_attribute(self, *, name: str, value: Any):
|
|
87
79
|
if PYDANTIC_V2:
|
|
88
|
-
if name in FieldInfo.metadata_lookup:
|
|
89
|
-
self.field_info.metadata.extend(FieldInfo._collect_metadata({name: value}))
|
|
90
80
|
self.field_info._attributes_set[name] = value
|
|
91
81
|
else:
|
|
92
82
|
setattr(self.field_info, name, value)
|
|
93
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
|
+
|
|
94
90
|
@property
|
|
95
91
|
def passed_field_attributes(self):
|
|
96
92
|
if PYDANTIC_V2:
|
cadwyn/codegen/_asts.py
CHANGED
|
@@ -2,6 +2,7 @@ import ast
|
|
|
2
2
|
import inspect
|
|
3
3
|
import re
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from enum import Enum, auto
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from types import GenericAlias, LambdaType, ModuleType, NoneType
|
|
@@ -15,8 +16,7 @@ from typing import ( # noqa: UP035
|
|
|
15
16
|
|
|
16
17
|
from cadwyn._compat import (
|
|
17
18
|
PYDANTIC_V2,
|
|
18
|
-
|
|
19
|
-
is_pydantic_constrained_type,
|
|
19
|
+
is_pydantic_1_constrained_type,
|
|
20
20
|
)
|
|
21
21
|
from cadwyn._package_utils import (
|
|
22
22
|
get_absolute_python_path_of_import,
|
|
@@ -108,29 +108,22 @@ def transform_none(_: NoneType) -> Any:
|
|
|
108
108
|
|
|
109
109
|
def transform_type(value: type) -> Any:
|
|
110
110
|
# This is a hack for pydantic's Constrained types
|
|
111
|
-
if
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
],
|
|
125
|
-
)
|
|
126
|
-
+ ")"
|
|
111
|
+
if is_pydantic_1_constrained_type(value):
|
|
112
|
+
parent = value.mro()[1]
|
|
113
|
+
snake_case = _RE_CAMEL_TO_SNAKE.sub("_", value.__name__)
|
|
114
|
+
cls_name = "con" + "".join(snake_case.split("_")[1:-1])
|
|
115
|
+
return (
|
|
116
|
+
cls_name.lower()
|
|
117
|
+
+ "("
|
|
118
|
+
+ ", ".join(
|
|
119
|
+
[
|
|
120
|
+
f"{key}={get_fancy_repr(val)}"
|
|
121
|
+
for key, val in value.__dict__.items()
|
|
122
|
+
if not key.startswith("_") and val is not None and val != parent.__dict__[key]
|
|
123
|
+
],
|
|
127
124
|
)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
# MRO of constr looks like: [ConstrainedStrValue, pydantic.types.ConstrainedStr, str, object]
|
|
131
|
-
# -2 -1
|
|
132
|
-
# ^^^
|
|
133
|
-
value = value.mro()[-2]
|
|
125
|
+
+ ")"
|
|
126
|
+
)
|
|
134
127
|
|
|
135
128
|
return value.__name__
|
|
136
129
|
|
|
@@ -230,11 +223,17 @@ def add_keyword_to_call(attr_name: str, attr_value: Any, call: ast.Call):
|
|
|
230
223
|
call.keywords.append(new_keyword)
|
|
231
224
|
|
|
232
225
|
|
|
226
|
+
def delete_keyword_from_call(attr_name: str, call: ast.Call):
|
|
227
|
+
for i, keyword in enumerate(call.keywords): # pragma: no branch
|
|
228
|
+
if keyword.arg == attr_name:
|
|
229
|
+
call.keywords.pop(i)
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
|
|
233
233
|
def get_ast_keyword_from_argument_name_and_value(name: str, value: Any):
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
)
|
|
234
|
+
if not isinstance(value, ast.AST):
|
|
235
|
+
value = ast.parse(get_fancy_repr(value), mode="eval").body
|
|
236
|
+
return ast.keyword(arg=name, value=value)
|
|
238
237
|
|
|
239
238
|
|
|
240
239
|
def pop_docstring_from_cls_body(cls_body: list[ast.stmt]) -> list[ast.stmt]:
|
|
@@ -247,3 +246,31 @@ def pop_docstring_from_cls_body(cls_body: list[ast.stmt]) -> list[ast.stmt]:
|
|
|
247
246
|
return [cls_body.pop(0)]
|
|
248
247
|
else:
|
|
249
248
|
return []
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@dataclass(slots=True)
|
|
252
|
+
class _ValidatorWrapper:
|
|
253
|
+
func_ast: ast.FunctionDef
|
|
254
|
+
index_of_validator_decorator: int
|
|
255
|
+
field_names: set[str | ast.expr] | None
|
|
256
|
+
is_deleted: bool = False
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_validator_info_or_none(method: ast.FunctionDef) -> _ValidatorWrapper | None:
|
|
260
|
+
for index, decorator in enumerate(method.decorator_list):
|
|
261
|
+
# The cases we handle here:
|
|
262
|
+
# * `Name(id="root_validator")`
|
|
263
|
+
# * `Call(func=Name(id="validator"), args=[Constant(value="foo")])`
|
|
264
|
+
# * `Attribute(value=Name(id="pydantic"), attr="root_validator")`
|
|
265
|
+
# * `Call(func=Attribute(value=Name(id="pydantic"), attr="root_validator"), args=[])`
|
|
266
|
+
|
|
267
|
+
if isinstance(decorator, ast.Call) and ast.unparse(decorator.func).endswith("validator"):
|
|
268
|
+
if len(decorator.args) == 0:
|
|
269
|
+
return _ValidatorWrapper(method, index, None)
|
|
270
|
+
else:
|
|
271
|
+
return _ValidatorWrapper(
|
|
272
|
+
method, index, {arg.value if isinstance(arg, ast.Constant) else arg for arg in decorator.args}
|
|
273
|
+
)
|
|
274
|
+
elif isinstance(decorator, ast.Name | ast.Attribute) and ast.unparse(decorator).endswith("validator"):
|
|
275
|
+
return _ValidatorWrapper(method, index, None)
|
|
276
|
+
return None
|
cadwyn/codegen/_common.py
CHANGED
|
@@ -16,6 +16,8 @@ from cadwyn._package_utils import IdentifierPythonPath
|
|
|
16
16
|
from cadwyn.exceptions import CodeGenerationError
|
|
17
17
|
from cadwyn.structure.versions import Version
|
|
18
18
|
|
|
19
|
+
from ._asts import _ValidatorWrapper, get_validator_info_or_none
|
|
20
|
+
|
|
19
21
|
_FieldName: TypeAlias = str
|
|
20
22
|
_CodegenPluginASTType = TypeVar("_CodegenPluginASTType", bound=ast.AST)
|
|
21
23
|
|
|
@@ -25,6 +27,7 @@ class PydanticModelWrapper:
|
|
|
25
27
|
cls: type[BaseModel]
|
|
26
28
|
name: str
|
|
27
29
|
fields: dict[_FieldName, PydanticFieldWrapper]
|
|
30
|
+
validators: dict[_FieldName, _ValidatorWrapper]
|
|
28
31
|
_parents: list[Self] | None = dataclasses.field(init=False, default=None)
|
|
29
32
|
|
|
30
33
|
def _get_parents(self, schemas: "dict[IdentifierPythonPath, Self]"):
|
|
@@ -37,11 +40,14 @@ class PydanticModelWrapper:
|
|
|
37
40
|
if schema_path in schemas:
|
|
38
41
|
parents.append(schemas[schema_path])
|
|
39
42
|
elif issubclass(base, BaseModel):
|
|
40
|
-
|
|
43
|
+
fields, validators = get_fields_and_validators_from_model(base)
|
|
44
|
+
parents.append(type(self)(base, base.__name__, fields, validators))
|
|
41
45
|
self._parents = parents
|
|
42
46
|
return parents
|
|
43
47
|
|
|
44
|
-
def
|
|
48
|
+
def _get_defined_fields_through_mro(
|
|
49
|
+
self, schemas: "dict[IdentifierPythonPath, Self]"
|
|
50
|
+
) -> dict[str, PydanticFieldWrapper]:
|
|
45
51
|
fields = {}
|
|
46
52
|
|
|
47
53
|
for parent in reversed(self._get_parents(schemas)):
|
|
@@ -49,9 +55,19 @@ class PydanticModelWrapper:
|
|
|
49
55
|
|
|
50
56
|
return fields | self.fields
|
|
51
57
|
|
|
58
|
+
def _get_defined_annotations_through_mro(self, schemas: "dict[IdentifierPythonPath, Self]") -> dict[str, Any]:
|
|
59
|
+
annotations = {}
|
|
60
|
+
|
|
61
|
+
for parent in reversed(self._get_parents(schemas)):
|
|
62
|
+
annotations |= parent.cls.__annotations__
|
|
63
|
+
|
|
64
|
+
return annotations | self.cls.__annotations__
|
|
65
|
+
|
|
52
66
|
|
|
53
67
|
@cache
|
|
54
|
-
def
|
|
68
|
+
def get_fields_and_validators_from_model(
|
|
69
|
+
cls: type,
|
|
70
|
+
) -> tuple[dict[_FieldName, PydanticFieldWrapper], dict[_FieldName, _ValidatorWrapper]]:
|
|
55
71
|
if not isinstance(cls, type) or not issubclass(cls, BaseModel):
|
|
56
72
|
raise CodeGenerationError(f"Model {cls} is not a subclass of BaseModel")
|
|
57
73
|
|
|
@@ -59,25 +75,37 @@ def get_fields_from_model(cls: type) -> dict[str, PydanticFieldWrapper]:
|
|
|
59
75
|
try:
|
|
60
76
|
source = inspect.getsource(cls)
|
|
61
77
|
except OSError:
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
annotation=field.annotation,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
return (
|
|
79
|
+
{
|
|
80
|
+
field_name: PydanticFieldWrapper(annotation=field.annotation, init_model_field=field)
|
|
81
|
+
for field_name, field in fields.items()
|
|
82
|
+
},
|
|
83
|
+
{},
|
|
84
|
+
)
|
|
69
85
|
else:
|
|
70
86
|
cls_ast = cast(ast.ClassDef, ast.parse(source).body[0])
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
annotation_ast=node.annotation,
|
|
76
|
-
field_ast=node.value,
|
|
77
|
-
)
|
|
87
|
+
validators: dict[str, _ValidatorWrapper] = {}
|
|
88
|
+
|
|
89
|
+
validators_and_nones = (
|
|
90
|
+
get_validator_info_or_none(node)
|
|
78
91
|
for node in cls_ast.body
|
|
79
|
-
if isinstance(node, ast.
|
|
80
|
-
|
|
92
|
+
if isinstance(node, ast.FunctionDef) and node.decorator_list
|
|
93
|
+
)
|
|
94
|
+
validators = {validator.func_ast.name: validator for validator in validators_and_nones if validator is not None}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
{
|
|
98
|
+
node.target.id: PydanticFieldWrapper(
|
|
99
|
+
annotation=fields[node.target.id].annotation,
|
|
100
|
+
init_model_field=fields[node.target.id],
|
|
101
|
+
annotation_ast=node.annotation,
|
|
102
|
+
value_ast=node.value,
|
|
103
|
+
)
|
|
104
|
+
for node in cls_ast.body
|
|
105
|
+
if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.target.id in fields
|
|
106
|
+
},
|
|
107
|
+
validators,
|
|
108
|
+
)
|
|
81
109
|
|
|
82
110
|
|
|
83
111
|
@dataclass(slots=True)
|
cadwyn/codegen/_main.py
CHANGED
|
@@ -21,7 +21,7 @@ from cadwyn.codegen._common import (
|
|
|
21
21
|
PydanticModelWrapper,
|
|
22
22
|
_EnumWrapper,
|
|
23
23
|
_ModuleWrapper,
|
|
24
|
-
|
|
24
|
+
get_fields_and_validators_from_model,
|
|
25
25
|
)
|
|
26
26
|
from cadwyn.codegen._plugins.class_migrations import class_migration_plugin
|
|
27
27
|
from cadwyn.codegen._plugins.class_rebuilding import ClassRebuildingPlugin
|
|
@@ -63,14 +63,15 @@ def generate_code_for_versioned_packages(
|
|
|
63
63
|
version of the latest module.
|
|
64
64
|
"""
|
|
65
65
|
extra_context = extra_context or {}
|
|
66
|
+
schemas = {}
|
|
67
|
+
for k, v in deepcopy(versions.versioned_schemas).items():
|
|
68
|
+
fields, validators = get_fields_and_validators_from_model(v)
|
|
69
|
+
schemas[k] = PydanticModelWrapper(v, v.__name__, fields, validators)
|
|
66
70
|
|
|
67
71
|
_generate_versioned_directories(
|
|
68
72
|
template_module,
|
|
69
73
|
versions=list(versions),
|
|
70
|
-
schemas=
|
|
71
|
-
k: PydanticModelWrapper(v, v.__name__, get_fields_from_model(v))
|
|
72
|
-
for k, v in deepcopy(versions.versioned_schemas).items()
|
|
73
|
-
},
|
|
74
|
+
schemas=schemas,
|
|
74
75
|
enums={
|
|
75
76
|
k: _EnumWrapper(v, {member.name: member.value for member in v})
|
|
76
77
|
for k, v in deepcopy(versions.versioned_enums).items()
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
from collections.abc import Sequence
|
|
3
|
+
from typing import Annotated, Any, cast, get_args, get_origin
|
|
3
4
|
|
|
4
5
|
from typing_extensions import assert_never
|
|
5
6
|
|
|
6
|
-
from cadwyn._compat import
|
|
7
|
+
from cadwyn._compat import (
|
|
8
|
+
PYDANTIC_V2,
|
|
9
|
+
FieldInfo,
|
|
10
|
+
PydanticFieldWrapper,
|
|
11
|
+
dict_of_empty_field_info,
|
|
12
|
+
is_constrained_type,
|
|
13
|
+
)
|
|
7
14
|
from cadwyn._package_utils import IdentifierPythonPath, get_cls_pythonpath
|
|
8
15
|
from cadwyn._utils import Sentinel
|
|
9
|
-
from cadwyn.codegen._asts import add_keyword_to_call
|
|
16
|
+
from cadwyn.codegen._asts import add_keyword_to_call, delete_keyword_from_call, get_fancy_repr
|
|
10
17
|
from cadwyn.codegen._common import GlobalCodegenContext, PydanticModelWrapper, _EnumWrapper
|
|
11
18
|
from cadwyn.exceptions import InvalidGenerationInstructionError
|
|
12
19
|
from cadwyn.structure.enums import AlterEnumSubInstruction, EnumDidntHaveMembersInstruction, EnumHadMembersInstruction
|
|
13
20
|
from cadwyn.structure.schemas import (
|
|
14
|
-
AlterSchemaInstruction,
|
|
15
21
|
AlterSchemaSubInstruction,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
FieldDidntExistInstruction,
|
|
23
|
+
FieldDidntHaveInstruction,
|
|
24
|
+
FieldExistedAsInstruction,
|
|
25
|
+
FieldHadInstruction,
|
|
26
|
+
SchemaHadInstruction,
|
|
27
|
+
ValidatorDidntExistInstruction,
|
|
28
|
+
ValidatorExistedInstruction,
|
|
19
29
|
)
|
|
20
30
|
|
|
21
31
|
|
|
@@ -35,26 +45,41 @@ def class_migration_plugin(context: GlobalCodegenContext):
|
|
|
35
45
|
|
|
36
46
|
def _apply_alter_schema_instructions(
|
|
37
47
|
modified_schemas: dict[IdentifierPythonPath, PydanticModelWrapper],
|
|
38
|
-
alter_schema_instructions: Sequence[AlterSchemaSubInstruction |
|
|
48
|
+
alter_schema_instructions: Sequence[AlterSchemaSubInstruction | SchemaHadInstruction],
|
|
39
49
|
version_change_name: str,
|
|
40
50
|
):
|
|
41
51
|
for alter_schema_instruction in alter_schema_instructions:
|
|
42
52
|
schema = alter_schema_instruction.schema
|
|
43
53
|
schema_path = get_cls_pythonpath(schema)
|
|
44
|
-
|
|
45
|
-
if isinstance(alter_schema_instruction,
|
|
46
|
-
|
|
47
|
-
elif isinstance(alter_schema_instruction,
|
|
54
|
+
schema_info = modified_schemas[schema_path]
|
|
55
|
+
if isinstance(alter_schema_instruction, FieldExistedAsInstruction):
|
|
56
|
+
_add_field_to_model(schema_info, modified_schemas, alter_schema_instruction, version_change_name)
|
|
57
|
+
elif isinstance(alter_schema_instruction, FieldHadInstruction | FieldDidntHaveInstruction):
|
|
48
58
|
_change_field_in_model(
|
|
49
|
-
|
|
59
|
+
schema_info,
|
|
50
60
|
modified_schemas,
|
|
51
61
|
alter_schema_instruction,
|
|
52
62
|
version_change_name,
|
|
53
63
|
)
|
|
54
|
-
elif isinstance(alter_schema_instruction,
|
|
55
|
-
|
|
56
|
-
elif isinstance(alter_schema_instruction,
|
|
57
|
-
|
|
64
|
+
elif isinstance(alter_schema_instruction, FieldDidntExistInstruction):
|
|
65
|
+
_delete_field_from_model(schema_info, alter_schema_instruction.name, version_change_name)
|
|
66
|
+
elif isinstance(alter_schema_instruction, ValidatorExistedInstruction):
|
|
67
|
+
validator_name = alter_schema_instruction.validator.__name__
|
|
68
|
+
schema_info.validators[validator_name] = alter_schema_instruction.validator_info
|
|
69
|
+
elif isinstance(alter_schema_instruction, ValidatorDidntExistInstruction):
|
|
70
|
+
if alter_schema_instruction.name not in schema_info.validators:
|
|
71
|
+
raise InvalidGenerationInstructionError(
|
|
72
|
+
f'You tried to delete a validator "{alter_schema_instruction.name}" from "{schema_info.name}" '
|
|
73
|
+
f'in "{version_change_name}" but it doesn\'t have such a validator.',
|
|
74
|
+
)
|
|
75
|
+
if schema_info.validators[alter_schema_instruction.name].is_deleted:
|
|
76
|
+
raise InvalidGenerationInstructionError(
|
|
77
|
+
f'You tried to delete a validator "{alter_schema_instruction.name}" from "{schema_info.name}" '
|
|
78
|
+
f'in "{version_change_name}" but it is already deleted.',
|
|
79
|
+
)
|
|
80
|
+
schema_info.validators[alter_schema_instruction.name].is_deleted = True
|
|
81
|
+
elif isinstance(alter_schema_instruction, SchemaHadInstruction):
|
|
82
|
+
_change_model(schema_info, alter_schema_instruction, version_change_name)
|
|
58
83
|
else:
|
|
59
84
|
assert_never(alter_schema_instruction)
|
|
60
85
|
|
|
@@ -90,7 +115,7 @@ def _apply_alter_enum_instructions(
|
|
|
90
115
|
|
|
91
116
|
def _change_model(
|
|
92
117
|
model: PydanticModelWrapper,
|
|
93
|
-
alter_schema_instruction:
|
|
118
|
+
alter_schema_instruction: SchemaHadInstruction,
|
|
94
119
|
version_change_name: str,
|
|
95
120
|
):
|
|
96
121
|
# We only handle names right now so we just go ahead and check
|
|
@@ -99,69 +124,144 @@ def _change_model(
|
|
|
99
124
|
f'You tried to change the name of "{model.name}" in "{version_change_name}" '
|
|
100
125
|
"but it already has the name you tried to assign.",
|
|
101
126
|
)
|
|
127
|
+
|
|
102
128
|
model.name = alter_schema_instruction.name
|
|
103
129
|
|
|
104
130
|
|
|
105
131
|
def _add_field_to_model(
|
|
106
132
|
model: PydanticModelWrapper,
|
|
107
133
|
schemas: "dict[IdentifierPythonPath, PydanticModelWrapper]",
|
|
108
|
-
alter_schema_instruction:
|
|
134
|
+
alter_schema_instruction: FieldExistedAsInstruction,
|
|
109
135
|
version_change_name: str,
|
|
110
136
|
):
|
|
111
|
-
defined_fields = model.
|
|
112
|
-
if alter_schema_instruction.
|
|
137
|
+
defined_fields = model._get_defined_fields_through_mro(schemas)
|
|
138
|
+
if alter_schema_instruction.name in defined_fields:
|
|
113
139
|
raise InvalidGenerationInstructionError(
|
|
114
|
-
f'You tried to add a field "{alter_schema_instruction.
|
|
140
|
+
f'You tried to add a field "{alter_schema_instruction.name}" to "{model.name}" '
|
|
115
141
|
f'in "{version_change_name}" but there is already a field with that name.',
|
|
116
142
|
)
|
|
117
143
|
|
|
118
|
-
|
|
119
|
-
|
|
144
|
+
fancy_type_repr = get_fancy_repr(alter_schema_instruction.type)
|
|
145
|
+
field = PydanticFieldWrapper(
|
|
146
|
+
annotation_ast=ast.parse(fancy_type_repr, mode="eval").body,
|
|
120
147
|
annotation=alter_schema_instruction.type,
|
|
121
148
|
init_model_field=alter_schema_instruction.field,
|
|
122
|
-
|
|
149
|
+
value_ast=None,
|
|
123
150
|
)
|
|
151
|
+
model.fields[alter_schema_instruction.name] = field
|
|
152
|
+
|
|
153
|
+
passed_field_attributes = field.passed_field_attributes
|
|
154
|
+
if passed_field_attributes:
|
|
155
|
+
field_call_ast = cast(ast.Call, ast.parse("Field()", mode="eval").body)
|
|
156
|
+
for attr_name, attr_value in passed_field_attributes.items():
|
|
157
|
+
add_keyword_to_call(attr_name, attr_value, field_call_ast)
|
|
158
|
+
field.value_ast = field_call_ast
|
|
159
|
+
model.cls.__annotations__[alter_schema_instruction.name] = alter_schema_instruction.type
|
|
124
160
|
|
|
125
161
|
|
|
126
162
|
def _change_field_in_model(
|
|
127
163
|
model: PydanticModelWrapper,
|
|
128
164
|
schemas: "dict[IdentifierPythonPath, PydanticModelWrapper]",
|
|
129
|
-
alter_schema_instruction:
|
|
165
|
+
alter_schema_instruction: FieldHadInstruction | FieldDidntHaveInstruction,
|
|
130
166
|
version_change_name: str,
|
|
131
167
|
):
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
defined_annotations = model._get_defined_annotations_through_mro(schemas)
|
|
169
|
+
defined_fields = model._get_defined_fields_through_mro(schemas)
|
|
170
|
+
if alter_schema_instruction.name not in defined_fields:
|
|
134
171
|
raise InvalidGenerationInstructionError(
|
|
135
|
-
f'You tried to change the
|
|
172
|
+
f'You tried to change the field "{alter_schema_instruction.name}" from '
|
|
136
173
|
f'"{model.name}" in "{version_change_name}" but it doesn\'t have such a field.',
|
|
137
174
|
)
|
|
138
175
|
|
|
139
|
-
field = defined_fields[alter_schema_instruction.
|
|
140
|
-
model.fields[alter_schema_instruction.
|
|
176
|
+
field = defined_fields[alter_schema_instruction.name]
|
|
177
|
+
model.fields[alter_schema_instruction.name] = field
|
|
178
|
+
model.cls.__annotations__[alter_schema_instruction.name] = defined_annotations[alter_schema_instruction.name]
|
|
179
|
+
|
|
180
|
+
annotation_ast, field_call_ast, contype_is_definitely_used = _get_constraint_asts_and_field_call_ast(
|
|
181
|
+
schemas, model, alter_schema_instruction.name, field
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
constrained_type_annotation = None
|
|
185
|
+
|
|
186
|
+
if annotation_ast is not None:
|
|
187
|
+
field_type_is_annotated = True # I.e. typing.Annotated
|
|
188
|
+
# PydanticV2 changed field annotation handling so field.annotation lies to us
|
|
189
|
+
real_annotation = model._get_defined_annotations_through_mro(schemas)[alter_schema_instruction.name]
|
|
190
|
+
type_annotation = get_args(real_annotation)[0]
|
|
191
|
+
if is_constrained_type(type_annotation):
|
|
192
|
+
constrained_type_annotation = type_annotation
|
|
193
|
+
else:
|
|
194
|
+
field_type_is_annotated = False
|
|
195
|
+
type_annotation = field.annotation
|
|
196
|
+
if is_constrained_type(type_annotation):
|
|
197
|
+
constrained_type_annotation = type_annotation
|
|
198
|
+
annotation_ast = field.annotation_ast
|
|
199
|
+
if field_call_ast is None:
|
|
200
|
+
field_call_ast = field.value_ast
|
|
141
201
|
|
|
142
|
-
|
|
202
|
+
if isinstance(alter_schema_instruction, FieldHadInstruction):
|
|
203
|
+
# TODO: This naming sucks
|
|
204
|
+
_change_field(
|
|
205
|
+
model,
|
|
206
|
+
alter_schema_instruction,
|
|
207
|
+
version_change_name,
|
|
208
|
+
defined_annotations,
|
|
209
|
+
field,
|
|
210
|
+
annotation_ast,
|
|
211
|
+
field_call_ast,
|
|
212
|
+
type_annotation,
|
|
213
|
+
field_type_is_annotated,
|
|
214
|
+
constrained_type_annotation,
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
_delete_field_attributes(
|
|
218
|
+
model,
|
|
219
|
+
alter_schema_instruction,
|
|
220
|
+
version_change_name,
|
|
221
|
+
field,
|
|
222
|
+
annotation_ast,
|
|
223
|
+
field_call_ast,
|
|
224
|
+
type_annotation,
|
|
225
|
+
constrained_type_annotation,
|
|
226
|
+
contype_is_definitely_used,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _change_field( # noqa: C901
|
|
231
|
+
model: PydanticModelWrapper,
|
|
232
|
+
alter_schema_instruction: FieldHadInstruction,
|
|
233
|
+
version_change_name: str,
|
|
234
|
+
defined_annotations: dict[str, Any],
|
|
235
|
+
field: PydanticFieldWrapper,
|
|
236
|
+
annotation_ast: ast.expr | None,
|
|
237
|
+
field_call_ast: ast.expr | None,
|
|
238
|
+
type_annotation: Any,
|
|
239
|
+
field_type_is_annotated: bool,
|
|
240
|
+
constrained_type_annotation: Any | None,
|
|
241
|
+
):
|
|
143
242
|
if alter_schema_instruction.type is not Sentinel:
|
|
144
243
|
if field.annotation == alter_schema_instruction.type:
|
|
145
244
|
raise InvalidGenerationInstructionError(
|
|
146
|
-
f'You tried to change the type of field "{alter_schema_instruction.
|
|
245
|
+
f'You tried to change the type of field "{alter_schema_instruction.name}" to '
|
|
147
246
|
f'"{alter_schema_instruction.type}" from "{model.name}" in "{version_change_name}" '
|
|
148
247
|
f'but it already has type "{field.annotation}"',
|
|
149
248
|
)
|
|
150
249
|
field.annotation = alter_schema_instruction.type
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
field.field_ast = None
|
|
250
|
+
model.cls.__annotations__[alter_schema_instruction.name] = alter_schema_instruction.type
|
|
251
|
+
fancy_type_repr = get_fancy_repr(alter_schema_instruction.type)
|
|
252
|
+
field.annotation_ast = ast.parse(fancy_type_repr, mode="eval").body
|
|
155
253
|
|
|
156
254
|
if alter_schema_instruction.new_name is not Sentinel:
|
|
157
|
-
if alter_schema_instruction.new_name == alter_schema_instruction.
|
|
255
|
+
if alter_schema_instruction.new_name == alter_schema_instruction.name:
|
|
158
256
|
raise InvalidGenerationInstructionError(
|
|
159
|
-
f'You tried to change the name of field "{alter_schema_instruction.
|
|
257
|
+
f'You tried to change the name of field "{alter_schema_instruction.name}" '
|
|
160
258
|
f'from "{model.name}" in "{version_change_name}" '
|
|
161
259
|
"but it already has that name.",
|
|
162
260
|
)
|
|
163
|
-
model.fields[alter_schema_instruction.new_name] = model.fields.pop(
|
|
164
|
-
|
|
261
|
+
model.fields[alter_schema_instruction.new_name] = model.fields.pop(alter_schema_instruction.name)
|
|
262
|
+
model.cls.__annotations__[alter_schema_instruction.new_name] = model.cls.__annotations__.pop(
|
|
263
|
+
alter_schema_instruction.name,
|
|
264
|
+
defined_annotations[alter_schema_instruction.name],
|
|
165
265
|
)
|
|
166
266
|
|
|
167
267
|
field_info = field.field_info
|
|
@@ -176,26 +276,129 @@ def _change_field_in_model(
|
|
|
176
276
|
if field.passed_field_attributes.get(attr_name, Sentinel) == attr_value:
|
|
177
277
|
raise InvalidGenerationInstructionError(
|
|
178
278
|
f'You tried to change the attribute "{attr_name}" of field '
|
|
179
|
-
f'"{alter_schema_instruction.
|
|
279
|
+
f'"{alter_schema_instruction.name}" '
|
|
180
280
|
f'from "{model.name}" to {attr_value!r} in "{version_change_name}" '
|
|
181
281
|
"but it already has that value.",
|
|
182
282
|
)
|
|
283
|
+
if constrained_type_annotation is not None and hasattr(constrained_type_annotation, attr_name):
|
|
284
|
+
_setattr_on_constrained_type(constrained_type_annotation, attr_name, attr_value)
|
|
285
|
+
if isinstance(annotation_ast, ast.Call):
|
|
286
|
+
add_keyword_to_call(attr_name, attr_value, annotation_ast)
|
|
287
|
+
elif not PYDANTIC_V2: # pragma: no branch
|
|
288
|
+
field.update_attribute(name=attr_name, value=attr_value)
|
|
289
|
+
if isinstance(field_call_ast, ast.Call): # pragma: no branch
|
|
290
|
+
add_keyword_to_call(attr_name, attr_value, field_call_ast)
|
|
183
291
|
|
|
184
|
-
if hasattr(field.annotation, attr_name) and current_field_is_constrained_type:
|
|
185
|
-
setattr(field.annotation, attr_name, attr_value)
|
|
186
|
-
ann_ast = field.annotation_ast
|
|
187
|
-
if ann_ast is not None and isinstance(ann_ast, ast.Call):
|
|
188
|
-
add_keyword_to_call(attr_name, attr_value, ann_ast)
|
|
189
|
-
else:
|
|
190
|
-
field.field_ast = None
|
|
191
|
-
field.annotation_ast = None
|
|
192
292
|
else:
|
|
193
293
|
field.update_attribute(name=attr_name, value=attr_value)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
294
|
+
if field_type_is_annotated and attr_name == "default":
|
|
295
|
+
field.value_ast = ast.parse(get_fancy_repr(attr_value), mode="eval").body
|
|
296
|
+
elif isinstance(field_call_ast, ast.Call):
|
|
297
|
+
add_keyword_to_call(attr_name, attr_value, field_call_ast)
|
|
298
|
+
elif field.value_ast is not None:
|
|
299
|
+
field_call_ast = cast(ast.Call, ast.parse("Field()", mode="eval").body)
|
|
300
|
+
add_keyword_to_call("default", field.value_ast, field_call_ast)
|
|
301
|
+
add_keyword_to_call(attr_name, attr_value, field_call_ast)
|
|
302
|
+
field.value_ast = field_call_ast
|
|
197
303
|
else:
|
|
198
|
-
field.
|
|
304
|
+
field.value_ast = cast(ast.Call, ast.parse("Field()", mode="eval").body)
|
|
305
|
+
field_call_ast = field.value_ast
|
|
306
|
+
add_keyword_to_call(attr_name, attr_value, field_call_ast)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _setattr_on_constrained_type(constrained_type_annotation: Any, attr_name: str, attr_value: Any) -> None:
|
|
310
|
+
setattr(constrained_type_annotation, attr_name, attr_value)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _delete_field_attributes(
|
|
314
|
+
model: PydanticModelWrapper,
|
|
315
|
+
alter_schema_instruction: FieldDidntHaveInstruction,
|
|
316
|
+
version_change_name: str,
|
|
317
|
+
field: PydanticFieldWrapper,
|
|
318
|
+
type_annotation_ast: ast.expr | None,
|
|
319
|
+
field_call_ast: ast.expr | None,
|
|
320
|
+
type_annotation: Any,
|
|
321
|
+
constrained_type_annotation: Any,
|
|
322
|
+
contype_is_definitely_used: bool,
|
|
323
|
+
) -> None:
|
|
324
|
+
for attr_name in alter_schema_instruction.attributes:
|
|
325
|
+
if attr_name in field.passed_field_attributes:
|
|
326
|
+
field.delete_attribute(name=attr_name)
|
|
327
|
+
if isinstance(field_call_ast, ast.Call):
|
|
328
|
+
delete_keyword_from_call(attr_name, field_call_ast)
|
|
329
|
+
elif attr_name == "default": # pragma: no branch
|
|
330
|
+
field.value_ast = None
|
|
331
|
+
# In case annotation_ast is a conint/constr/etc. Notice how we do not support
|
|
332
|
+
# the same operation for **adding** constraints for simplicity.
|
|
333
|
+
elif (hasattr(constrained_type_annotation, attr_name)) or contype_is_definitely_used:
|
|
334
|
+
if hasattr(constrained_type_annotation, attr_name):
|
|
335
|
+
_setattr_on_constrained_type(constrained_type_annotation, attr_name, None)
|
|
336
|
+
if isinstance(type_annotation_ast, ast.Call): # pragma: no branch
|
|
337
|
+
delete_keyword_from_call(attr_name, type_annotation_ast)
|
|
338
|
+
else:
|
|
339
|
+
raise InvalidGenerationInstructionError(
|
|
340
|
+
f'You tried to delete the attribute "{attr_name}" of field "{alter_schema_instruction.name}" '
|
|
341
|
+
f'from "{model.name}" in "{version_change_name}" '
|
|
342
|
+
"but it already doesn't have that attribute.",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
ContypeIsDefinitelyUsed = bool
|
|
347
|
+
CONTYPES = (
|
|
348
|
+
"conbytes",
|
|
349
|
+
"condate",
|
|
350
|
+
"condecimal",
|
|
351
|
+
"confloat",
|
|
352
|
+
"conint",
|
|
353
|
+
"conlist",
|
|
354
|
+
"conset",
|
|
355
|
+
"constr",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _get_constraint_asts_and_field_call_ast(
|
|
360
|
+
schemas: dict[IdentifierPythonPath, PydanticModelWrapper],
|
|
361
|
+
model: PydanticModelWrapper,
|
|
362
|
+
field_name: str,
|
|
363
|
+
field: PydanticFieldWrapper,
|
|
364
|
+
) -> tuple[ast.expr | None, ast.Call | None, ContypeIsDefinitelyUsed]:
|
|
365
|
+
"""If the field type is Annotated and contains "Field" """
|
|
366
|
+
# We return both annotation ast and field call ast because annotation might be a constrained type such as conint
|
|
367
|
+
# and therefore contain constraints that we might want to remove.
|
|
368
|
+
|
|
369
|
+
# ContypeIsDefinitely used is used to determine whether constr/conint/etc is used in Pydantic 2
|
|
370
|
+
# because pydantic 2 changes original type hints to make sure that conint/constr/etc do not appear in annotations.
|
|
371
|
+
|
|
372
|
+
real_annotation = model._get_defined_annotations_through_mro(schemas)[field_name]
|
|
373
|
+
# typing.Annotated scenario
|
|
374
|
+
if get_origin(real_annotation) == Annotated:
|
|
375
|
+
index_of_field_info = _find_index_of_field_info_in_annotated(real_annotation)
|
|
376
|
+
if not (isinstance(field.annotation_ast, ast.Subscript) and isinstance(field.annotation_ast.slice, ast.Tuple)):
|
|
377
|
+
return (field.annotation_ast, None, True)
|
|
378
|
+
|
|
379
|
+
unparsed_annotation = ast.unparse(field.annotation_ast.slice.elts[0])
|
|
380
|
+
contype_is_definitely_used = any(contype in unparsed_annotation for contype in CONTYPES)
|
|
381
|
+
|
|
382
|
+
# In pydantic 2, this means that in fact there is conint/constr/etc instead of an actual Annotated.
|
|
383
|
+
# Yes, pydantic 2 changes original type hints to make sure that conint/constr/etc do not appear in types.
|
|
384
|
+
|
|
385
|
+
if index_of_field_info is not None:
|
|
386
|
+
field_call_ast = field.annotation_ast.slice.elts[index_of_field_info]
|
|
387
|
+
|
|
388
|
+
return (field.annotation_ast.slice.elts[0], cast(ast.Call, field_call_ast), contype_is_definitely_used)
|
|
389
|
+
return (field.annotation_ast.slice.elts[0], None, contype_is_definitely_used)
|
|
390
|
+
return (None, None, False)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _find_index_of_field_info_in_annotated(real_annotation: Any):
|
|
394
|
+
# Pydantic turns `Annotated[conint(lt=2 + 5), Field(default=11), annotated_types.Gt(0)]` into:
|
|
395
|
+
# `Annotated[int, None, Interval(lt=7), None, FieldInfo(default=11), annotated_types.Gt(0)]`
|
|
396
|
+
# Why? No idea. Probably due to its weird handling of constrained types. So we gotta go from the last element
|
|
397
|
+
# because constrained types can only appear and mess up indexing in the first element.
|
|
398
|
+
for i, arg in enumerate(reversed(get_args(real_annotation)), start=1):
|
|
399
|
+
if isinstance(arg, FieldInfo):
|
|
400
|
+
return -i
|
|
401
|
+
return None
|
|
199
402
|
|
|
200
403
|
|
|
201
404
|
def _delete_field_from_model(model: PydanticModelWrapper, field_name: str, version_change_name: str):
|
|
@@ -205,3 +408,16 @@ def _delete_field_from_model(model: PydanticModelWrapper, field_name: str, versi
|
|
|
205
408
|
f'in "{version_change_name}" but it doesn\'t have such a field.',
|
|
206
409
|
)
|
|
207
410
|
model.fields.pop(field_name)
|
|
411
|
+
for validator_name, validator in model.validators.copy().items():
|
|
412
|
+
if validator.field_names is not None and field_name in validator.field_names:
|
|
413
|
+
validator.field_names.remove(field_name)
|
|
414
|
+
|
|
415
|
+
validator_decorator = cast(
|
|
416
|
+
ast.Call, validator.func_ast.decorator_list[validator.index_of_validator_decorator]
|
|
417
|
+
)
|
|
418
|
+
for arg in validator_decorator.args.copy():
|
|
419
|
+
if isinstance(arg, ast.Constant) and arg.value == field_name:
|
|
420
|
+
validator_decorator.args.remove(arg)
|
|
421
|
+
validator.func_ast.decorator_list[0]
|
|
422
|
+
if not validator.field_names:
|
|
423
|
+
model.validators[validator_name].is_deleted = True
|
|
@@ -2,14 +2,8 @@ import ast
|
|
|
2
2
|
import copy
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from cadwyn._compat import (
|
|
6
|
-
PydanticFieldWrapper,
|
|
7
|
-
get_attrs_that_are_not_from_field_and_that_are_from_field,
|
|
8
|
-
is_pydantic_constrained_type,
|
|
9
|
-
)
|
|
10
5
|
from cadwyn._package_utils import IdentifierPythonPath, get_absolute_python_path_of_import
|
|
11
6
|
from cadwyn.codegen._asts import (
|
|
12
|
-
get_ast_keyword_from_argument_name_and_value,
|
|
13
7
|
get_fancy_repr,
|
|
14
8
|
pop_docstring_from_cls_body,
|
|
15
9
|
)
|
|
@@ -92,48 +86,28 @@ def _modify_schema_cls(
|
|
|
92
86
|
field_definitions = [
|
|
93
87
|
ast.AnnAssign(
|
|
94
88
|
target=ast.Name(name, ctx=ast.Store()),
|
|
95
|
-
annotation=
|
|
96
|
-
|
|
89
|
+
annotation=copy.deepcopy(field.annotation_ast),
|
|
90
|
+
# We do this because next plugins **might** use a transformer which will edit the ast within the field
|
|
91
|
+
# and break rendering
|
|
92
|
+
value=copy.deepcopy(field.value_ast),
|
|
97
93
|
simple=1,
|
|
98
94
|
)
|
|
99
95
|
for name, field in model_info.fields.items()
|
|
100
96
|
]
|
|
97
|
+
validator_definitions = [
|
|
98
|
+
validator.func_ast for validator in model_info.validators.values() if not validator.is_deleted
|
|
99
|
+
]
|
|
101
100
|
|
|
102
|
-
old_body = [
|
|
101
|
+
old_body = [
|
|
102
|
+
n
|
|
103
|
+
for n in cls_node.body
|
|
104
|
+
if not (
|
|
105
|
+
isinstance(n, ast.AnnAssign | ast.Assign | ast.Pass | ast.Constant)
|
|
106
|
+
or (isinstance(n, ast.FunctionDef) and n.name in model_info.validators)
|
|
107
|
+
)
|
|
108
|
+
]
|
|
103
109
|
docstring = pop_docstring_from_cls_body(old_body)
|
|
104
|
-
cls_node.body = docstring + field_definitions + old_body
|
|
110
|
+
cls_node.body = docstring + field_definitions + validator_definitions + old_body
|
|
105
111
|
if not cls_node.body:
|
|
106
112
|
cls_node.body = [ast.Pass()]
|
|
107
113
|
return cls_node
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _render_annotation(annotation: Any):
|
|
111
|
-
if isinstance(annotation, ast.AST):
|
|
112
|
-
return copy.deepcopy(annotation)
|
|
113
|
-
return ast.parse(get_fancy_repr(annotation), mode="eval").body
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _generate_field_ast(field: PydanticFieldWrapper):
|
|
117
|
-
if field.field_ast is not None:
|
|
118
|
-
# We do this because next plugins **might** use a transformer which will edit the ast within the field
|
|
119
|
-
# and break rendering
|
|
120
|
-
return copy.deepcopy(field.field_ast)
|
|
121
|
-
passed_attrs = field.passed_field_attributes
|
|
122
|
-
if is_pydantic_constrained_type(field.annotation) and field.annotation_ast is None:
|
|
123
|
-
(
|
|
124
|
-
attrs_that_are_only_in_contype,
|
|
125
|
-
attrs_that_are_only_in_field,
|
|
126
|
-
) = get_attrs_that_are_not_from_field_and_that_are_from_field(field.annotation)
|
|
127
|
-
if not attrs_that_are_only_in_contype:
|
|
128
|
-
passed_attrs |= attrs_that_are_only_in_field
|
|
129
|
-
|
|
130
|
-
if passed_attrs:
|
|
131
|
-
return ast.Call(
|
|
132
|
-
func=ast.Name("Field"),
|
|
133
|
-
args=[],
|
|
134
|
-
keywords=[
|
|
135
|
-
get_ast_keyword_from_argument_name_and_value(attr, attr_value)
|
|
136
|
-
for attr, attr_value in passed_attrs.items()
|
|
137
|
-
],
|
|
138
|
-
)
|
|
139
|
-
return None
|
|
@@ -5,6 +5,7 @@ from cadwyn.codegen._common import CodegenContext
|
|
|
5
5
|
|
|
6
6
|
_extra_imports: list[tuple[str, str]] = [
|
|
7
7
|
("typing", "import typing"),
|
|
8
|
+
("pydantic", "import pydantic"),
|
|
8
9
|
("Any", "from typing import Any"),
|
|
9
10
|
("Annotated", "from typing import Annotated"),
|
|
10
11
|
("Field", "from pydantic import Field"),
|
|
@@ -16,6 +17,8 @@ _extra_imports: list[tuple[str, str]] = [
|
|
|
16
17
|
("confloat", "from pydantic import confloat"),
|
|
17
18
|
("condecimal", "from pydantic import condecimal"),
|
|
18
19
|
("condate", "from pydantic import condate"),
|
|
20
|
+
("validator", "from pydantic import validator"),
|
|
21
|
+
("root_validator", "from pydantic import root_validator"),
|
|
19
22
|
]
|
|
20
23
|
|
|
21
24
|
|
|
@@ -29,6 +32,8 @@ if PYDANTIC_V2:
|
|
|
29
32
|
("StrictFloat", "from pydantic import StrictFloat"),
|
|
30
33
|
("StrictInt", "from pydantic import StrictInt"),
|
|
31
34
|
("StrictStr", "from pydantic import StrictStr"),
|
|
35
|
+
("model_validator", "from pydantic import model_validator"),
|
|
36
|
+
("field_validator", "from pydantic import field_validator"),
|
|
32
37
|
],
|
|
33
38
|
)
|
|
34
39
|
|
cadwyn/structure/data.py
CHANGED
|
@@ -73,7 +73,7 @@ class _AlterDataInstruction:
|
|
|
73
73
|
signature = inspect.signature(self.transformer)
|
|
74
74
|
if list(signature.parameters) != [self._payload_arg_name]:
|
|
75
75
|
raise ValueError(
|
|
76
|
-
f"Method '{self.transformer.__name__}' must have
|
|
76
|
+
f"Method '{self.transformer.__name__}' must have only 1 parameter: {self._payload_arg_name}",
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
functools.update_wrapper(self, self.transformer)
|
cadwyn/structure/schemas.py
CHANGED
|
@@ -1,19 +1,52 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import inspect
|
|
3
|
+
import textwrap
|
|
1
4
|
from collections.abc import Callable
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
4
7
|
|
|
5
8
|
from pydantic import BaseModel, Field
|
|
6
9
|
from pydantic.fields import FieldInfo
|
|
7
10
|
|
|
11
|
+
from cadwyn._compat import PYDANTIC_V2
|
|
12
|
+
from cadwyn._utils import Sentinel
|
|
13
|
+
from cadwyn.codegen._asts import _ValidatorWrapper, get_validator_info_or_none
|
|
8
14
|
from cadwyn.exceptions import CadwynStructureError
|
|
9
15
|
|
|
10
|
-
from .._compat import PYDANTIC_V2
|
|
11
|
-
from .._utils import Sentinel
|
|
12
|
-
|
|
13
16
|
if TYPE_CHECKING:
|
|
14
17
|
from pydantic.typing import AbstractSetIntStr, MappingIntStrAny
|
|
15
18
|
|
|
16
19
|
|
|
20
|
+
PossibleFieldAttributes = Literal[
|
|
21
|
+
"default",
|
|
22
|
+
"default_factory",
|
|
23
|
+
"alias",
|
|
24
|
+
"title",
|
|
25
|
+
"description",
|
|
26
|
+
"exclude",
|
|
27
|
+
"include",
|
|
28
|
+
"const",
|
|
29
|
+
"gt",
|
|
30
|
+
"ge",
|
|
31
|
+
"lt",
|
|
32
|
+
"le",
|
|
33
|
+
"multiple_of",
|
|
34
|
+
"allow_inf_nan",
|
|
35
|
+
"max_digits",
|
|
36
|
+
"decimal_places",
|
|
37
|
+
"min_items",
|
|
38
|
+
"max_items",
|
|
39
|
+
"unique_items",
|
|
40
|
+
"min_length",
|
|
41
|
+
"max_length",
|
|
42
|
+
"allow_mutation",
|
|
43
|
+
"regex",
|
|
44
|
+
"pattern",
|
|
45
|
+
"discriminator",
|
|
46
|
+
"repr",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
17
50
|
@dataclass(slots=True)
|
|
18
51
|
class FieldChanges:
|
|
19
52
|
default: Any
|
|
@@ -45,24 +78,31 @@ class FieldChanges:
|
|
|
45
78
|
|
|
46
79
|
|
|
47
80
|
@dataclass(slots=True)
|
|
48
|
-
class
|
|
81
|
+
class FieldHadInstruction:
|
|
49
82
|
schema: type[BaseModel]
|
|
50
|
-
|
|
83
|
+
name: str
|
|
51
84
|
type: type
|
|
52
85
|
field_changes: FieldChanges
|
|
53
86
|
new_name: str
|
|
54
87
|
|
|
55
88
|
|
|
56
89
|
@dataclass(slots=True)
|
|
57
|
-
class
|
|
90
|
+
class FieldDidntHaveInstruction:
|
|
58
91
|
schema: type[BaseModel]
|
|
59
|
-
|
|
92
|
+
name: str
|
|
93
|
+
attributes: tuple[str, ...]
|
|
60
94
|
|
|
61
95
|
|
|
62
96
|
@dataclass(slots=True)
|
|
63
|
-
class
|
|
97
|
+
class FieldDidntExistInstruction:
|
|
64
98
|
schema: type[BaseModel]
|
|
65
|
-
|
|
99
|
+
name: str
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(slots=True)
|
|
103
|
+
class FieldExistedAsInstruction:
|
|
104
|
+
schema: type[BaseModel]
|
|
105
|
+
name: str
|
|
66
106
|
type: type
|
|
67
107
|
field: FieldInfo
|
|
68
108
|
|
|
@@ -104,7 +144,7 @@ class AlterFieldInstructionFactory:
|
|
|
104
144
|
pattern: str = Sentinel,
|
|
105
145
|
discriminator: str = Sentinel,
|
|
106
146
|
repr: bool = Sentinel,
|
|
107
|
-
) ->
|
|
147
|
+
) -> FieldHadInstruction:
|
|
108
148
|
if PYDANTIC_V2:
|
|
109
149
|
if regex is not Sentinel:
|
|
110
150
|
raise CadwynStructureError("`regex` was removed in Pydantic 2. Use `pattern` instead")
|
|
@@ -122,9 +162,9 @@ class AlterFieldInstructionFactory:
|
|
|
122
162
|
else:
|
|
123
163
|
if pattern is not Sentinel:
|
|
124
164
|
raise CadwynStructureError("`pattern` is only available in Pydantic 2. use `regex` instead")
|
|
125
|
-
return
|
|
165
|
+
return FieldHadInstruction(
|
|
126
166
|
schema=self.schema,
|
|
127
|
-
|
|
167
|
+
name=self.name,
|
|
128
168
|
type=type,
|
|
129
169
|
new_name=name,
|
|
130
170
|
field_changes=FieldChanges(
|
|
@@ -157,29 +197,82 @@ class AlterFieldInstructionFactory:
|
|
|
157
197
|
),
|
|
158
198
|
)
|
|
159
199
|
|
|
200
|
+
def didnt_have(self, *attributes: PossibleFieldAttributes) -> FieldDidntHaveInstruction:
|
|
201
|
+
for attribute in attributes:
|
|
202
|
+
if attribute not in FieldChanges.__dataclass_fields__:
|
|
203
|
+
raise CadwynStructureError(
|
|
204
|
+
f"Unknown attribute {attribute!r}. Are you sure it's a valid field attribute?"
|
|
205
|
+
)
|
|
206
|
+
return FieldDidntHaveInstruction(self.schema, self.name, attributes)
|
|
207
|
+
|
|
160
208
|
@property
|
|
161
|
-
def didnt_exist(self) ->
|
|
162
|
-
return
|
|
209
|
+
def didnt_exist(self) -> FieldDidntExistInstruction:
|
|
210
|
+
return FieldDidntExistInstruction(self.schema, name=self.name)
|
|
163
211
|
|
|
164
212
|
def existed_as(
|
|
165
213
|
self,
|
|
166
214
|
*,
|
|
167
215
|
type: Any,
|
|
168
216
|
info: FieldInfo | None = None,
|
|
169
|
-
) ->
|
|
170
|
-
return
|
|
217
|
+
) -> FieldExistedAsInstruction:
|
|
218
|
+
return FieldExistedAsInstruction(
|
|
171
219
|
self.schema,
|
|
172
|
-
|
|
220
|
+
name=self.name,
|
|
173
221
|
type=type,
|
|
174
222
|
field=info or Field(),
|
|
175
223
|
)
|
|
176
224
|
|
|
177
225
|
|
|
178
|
-
|
|
226
|
+
@dataclass(slots=True)
|
|
227
|
+
class ValidatorExistedInstruction:
|
|
228
|
+
schema: type[BaseModel]
|
|
229
|
+
validator: Callable[..., Any]
|
|
230
|
+
validator_info: _ValidatorWrapper = field(init=False)
|
|
231
|
+
|
|
232
|
+
def __post_init__(self):
|
|
233
|
+
source = textwrap.dedent(inspect.getsource(self.validator))
|
|
234
|
+
validator_ast = ast.parse(source).body[0]
|
|
235
|
+
if not isinstance(validator_ast, ast.FunctionDef):
|
|
236
|
+
raise CadwynStructureError("The passed validator must be a function")
|
|
237
|
+
|
|
238
|
+
validator_info = get_validator_info_or_none(validator_ast)
|
|
239
|
+
if validator_info is None:
|
|
240
|
+
raise CadwynStructureError("The passed function must be a pydantic validator")
|
|
241
|
+
self.validator_info = validator_info
|
|
179
242
|
|
|
180
243
|
|
|
181
244
|
@dataclass(slots=True)
|
|
182
|
-
class
|
|
245
|
+
class ValidatorDidntExistInstruction:
|
|
246
|
+
schema: type[BaseModel]
|
|
247
|
+
name: str
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass(slots=True)
|
|
251
|
+
class AlterValidatorInstructionFactory:
|
|
252
|
+
schema: type[BaseModel]
|
|
253
|
+
func: Callable[..., Any]
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def existed(self) -> ValidatorExistedInstruction:
|
|
257
|
+
return ValidatorExistedInstruction(self.schema, self.func)
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def didnt_exist(self) -> ValidatorDidntExistInstruction:
|
|
261
|
+
return ValidatorDidntExistInstruction(self.schema, self.func.__name__)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
AlterSchemaSubInstruction = (
|
|
265
|
+
FieldHadInstruction
|
|
266
|
+
| FieldDidntHaveInstruction
|
|
267
|
+
| FieldDidntExistInstruction
|
|
268
|
+
| FieldExistedAsInstruction
|
|
269
|
+
| ValidatorExistedInstruction
|
|
270
|
+
| ValidatorDidntExistInstruction
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@dataclass(slots=True)
|
|
275
|
+
class SchemaHadInstruction:
|
|
183
276
|
schema: type[BaseModel]
|
|
184
277
|
name: str
|
|
185
278
|
|
|
@@ -191,8 +284,13 @@ class AlterSchemaInstructionFactory:
|
|
|
191
284
|
def field(self, name: str, /) -> AlterFieldInstructionFactory:
|
|
192
285
|
return AlterFieldInstructionFactory(self.schema, name)
|
|
193
286
|
|
|
194
|
-
def
|
|
195
|
-
|
|
287
|
+
def validator(self, func: "Callable[..., Any] | classmethod[Any, Any, Any]", /) -> AlterValidatorInstructionFactory:
|
|
288
|
+
if isinstance(func, classmethod):
|
|
289
|
+
func = func.__wrapped__
|
|
290
|
+
return AlterValidatorInstructionFactory(self.schema, func)
|
|
291
|
+
|
|
292
|
+
def had(self, *, name: str) -> SchemaHadInstruction:
|
|
293
|
+
return SchemaHadInstruction(self.schema, name)
|
|
196
294
|
|
|
197
295
|
|
|
198
296
|
def schema(model: type[BaseModel], /) -> AlterSchemaInstructionFactory:
|
cadwyn/structure/versions.py
CHANGED
|
@@ -10,6 +10,7 @@ from enum import Enum
|
|
|
10
10
|
from types import ModuleType
|
|
11
11
|
from typing import Any, ClassVar, ParamSpec, TypeAlias, TypeVar, cast
|
|
12
12
|
|
|
13
|
+
import fastapi
|
|
13
14
|
from fastapi import HTTPException, params
|
|
14
15
|
from fastapi import Request as FastapiRequest
|
|
15
16
|
from fastapi import Response as FastapiResponse
|
|
@@ -24,7 +25,7 @@ from pydantic import BaseModel
|
|
|
24
25
|
from starlette._utils import is_async_callable
|
|
25
26
|
from typing_extensions import assert_never
|
|
26
27
|
|
|
27
|
-
from cadwyn._compat import PYDANTIC_V2, ModelField,
|
|
28
|
+
from cadwyn._compat import PYDANTIC_V2, ModelField, PydanticUndefined, model_dump
|
|
28
29
|
from cadwyn._package_utils import IdentifierPythonPath, get_cls_pythonpath
|
|
29
30
|
from cadwyn.exceptions import CadwynError, CadwynStructureError
|
|
30
31
|
|
|
@@ -41,7 +42,7 @@ from .data import (
|
|
|
41
42
|
from .endpoints import AlterEndpointSubInstruction
|
|
42
43
|
from .enums import AlterEnumSubInstruction
|
|
43
44
|
from .modules import AlterModuleInstruction
|
|
44
|
-
from .schemas import
|
|
45
|
+
from .schemas import AlterSchemaSubInstruction, SchemaHadInstruction
|
|
45
46
|
|
|
46
47
|
_CADWYN_REQUEST_PARAM_NAME = "cadwyn_request_param"
|
|
47
48
|
_CADWYN_RESPONSE_PARAM_NAME = "cadwyn_response_param"
|
|
@@ -51,7 +52,7 @@ PossibleInstructions: TypeAlias = (
|
|
|
51
52
|
AlterSchemaSubInstruction
|
|
52
53
|
| AlterEndpointSubInstruction
|
|
53
54
|
| AlterEnumSubInstruction
|
|
54
|
-
|
|
|
55
|
+
| SchemaHadInstruction
|
|
55
56
|
| AlterModuleInstruction
|
|
56
57
|
| staticmethod
|
|
57
58
|
)
|
|
@@ -61,7 +62,7 @@ APIVersionVarType: TypeAlias = ContextVar[VersionDate | None] | ContextVar[Versi
|
|
|
61
62
|
class VersionChange:
|
|
62
63
|
description: ClassVar[str] = Sentinel
|
|
63
64
|
instructions_to_migrate_to_previous_version: ClassVar[Sequence[PossibleInstructions]] = Sentinel
|
|
64
|
-
alter_schema_instructions: ClassVar[list[AlterSchemaSubInstruction |
|
|
65
|
+
alter_schema_instructions: ClassVar[list[AlterSchemaSubInstruction | SchemaHadInstruction]] = Sentinel
|
|
65
66
|
alter_enum_instructions: ClassVar[list[AlterEnumSubInstruction]] = Sentinel
|
|
66
67
|
alter_module_instructions: ClassVar[list[AlterModuleInstruction]] = Sentinel
|
|
67
68
|
alter_endpoint_instructions: ClassVar[list[AlterEndpointSubInstruction]] = Sentinel
|
|
@@ -115,7 +116,7 @@ class VersionChange:
|
|
|
115
116
|
cls.alter_response_by_schema_instructions = {}
|
|
116
117
|
cls.alter_response_by_path_instructions = defaultdict(list)
|
|
117
118
|
for alter_instruction in cls.instructions_to_migrate_to_previous_version:
|
|
118
|
-
if isinstance(alter_instruction,
|
|
119
|
+
if isinstance(alter_instruction, SchemaHadInstruction | AlterSchemaSubInstruction):
|
|
119
120
|
cls.alter_schema_instructions.append(alter_instruction)
|
|
120
121
|
elif isinstance(alter_instruction, AlterEnumSubInstruction):
|
|
121
122
|
cls.alter_enum_instructions.append(alter_instruction)
|
|
@@ -324,17 +325,23 @@ class VersionBundle:
|
|
|
324
325
|
del request._headers
|
|
325
326
|
# Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
|
|
326
327
|
async with AsyncExitStack() as async_exit_stack:
|
|
327
|
-
|
|
328
|
+
# FastAPI has made a nasty breaking change in that version by adding a required argument
|
|
329
|
+
# without an optional counterpart.
|
|
330
|
+
if fastapi.__version__ >= "0.106.0":
|
|
331
|
+
kwargs: dict[str, Any] = {"async_exit_stack": async_exit_stack}
|
|
332
|
+
else: # pragma: no cover
|
|
333
|
+
kwargs: dict[str, Any] = {}
|
|
334
|
+
dependencies, errors, _, _, _ = await solve_dependencies(
|
|
328
335
|
request=request,
|
|
329
336
|
response=response,
|
|
330
337
|
dependant=latest_dependant_with_internal_schema,
|
|
331
338
|
body=request_info.body,
|
|
332
339
|
dependency_overrides_provider=latest_route.dependency_overrides_provider,
|
|
333
|
-
|
|
340
|
+
**kwargs,
|
|
334
341
|
)
|
|
335
342
|
if errors:
|
|
336
343
|
raise RequestValidationError(_normalize_errors(errors), body=request_info.body)
|
|
337
|
-
return
|
|
344
|
+
return dependencies
|
|
338
345
|
raise NotImplementedError("This code should not be reachable. If it was reached -- it's a bug.")
|
|
339
346
|
|
|
340
347
|
def _migrate_response(
|
|
@@ -560,7 +567,7 @@ async def _get_body(request: FastapiRequest, body_field: ModelField | None): #
|
|
|
560
567
|
else:
|
|
561
568
|
body_bytes = await request.body()
|
|
562
569
|
if body_bytes:
|
|
563
|
-
json_body: Any =
|
|
570
|
+
json_body: Any = PydanticUndefined
|
|
564
571
|
content_type_value = request.headers.get("content-type")
|
|
565
572
|
if not content_type_value:
|
|
566
573
|
json_body = await request.json()
|
|
@@ -571,7 +578,7 @@ async def _get_body(request: FastapiRequest, body_field: ModelField | None): #
|
|
|
571
578
|
subtype = message.get_content_subtype()
|
|
572
579
|
if subtype == "json" or subtype.endswith("+json"):
|
|
573
580
|
json_body = await request.json()
|
|
574
|
-
if json_body !=
|
|
581
|
+
if json_body != PydanticUndefined:
|
|
575
582
|
body = json_body
|
|
576
583
|
else:
|
|
577
584
|
body = body_bytes
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
cadwyn/__init__.py,sha256=gVLVH3SSBGH0IQYGL5tbro4s0vk--9sAym0UvoG3s1w,478
|
|
2
2
|
cadwyn/__main__.py,sha256=JUNmAhwn7tG1EeXI82QmFZE-fpjfAOv2kxFNDfxWbhQ,2851
|
|
3
|
-
cadwyn/_compat.py,sha256=
|
|
3
|
+
cadwyn/_compat.py,sha256=B0K-cj9bN7ytOIehOMjN9O9s0CE3lq9WAkV97pnvll8,5410
|
|
4
4
|
cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
|
|
5
5
|
cadwyn/_utils.py,sha256=gEv_1Qqp7uzvdY0u_l77d6lppAsNsOCjD8FEFBKi3Js,3758
|
|
6
6
|
cadwyn/codegen/README.md,sha256=V2Kz2IOz1cTxrC-RnQ7YbWEVCIGYr3tR4IPCvepeq0M,1047
|
|
7
7
|
cadwyn/codegen/__init__.py,sha256=JgddDjxMTjSfVrMXHwNu1ODgdn2QfPWpccrRKquBV6k,355
|
|
8
|
-
cadwyn/codegen/_asts.py,sha256=
|
|
9
|
-
cadwyn/codegen/_common.py,sha256=
|
|
10
|
-
cadwyn/codegen/_main.py,sha256=
|
|
8
|
+
cadwyn/codegen/_asts.py,sha256=rwg3FMC9c_20rawub98UTWzL8hhkBgJ0RdJsqnW9bVE,10161
|
|
9
|
+
cadwyn/codegen/_common.py,sha256=6vU9RtDPkXtuseRDtHeBbWYSTFwGtONv4OCA7BQrr3I,5651
|
|
10
|
+
cadwyn/codegen/_main.py,sha256=wiadc3OYn1MlLwirfWuhkanvr2El-GjeQJpmpxHc4jA,9122
|
|
11
11
|
cadwyn/codegen/_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
cadwyn/codegen/_plugins/class_migrations.py,sha256=
|
|
13
|
-
cadwyn/codegen/_plugins/class_rebuilding.py,sha256=
|
|
12
|
+
cadwyn/codegen/_plugins/class_migrations.py,sha256=dbmXMdfUGA2X4phsNdtRQQde4SuoOYtNB86XL05U1jY,20159
|
|
13
|
+
cadwyn/codegen/_plugins/class_rebuilding.py,sha256=mJR297bqsLUfP4HW5_1GuHlpiYSQd851yHpG_ajjilg,3703
|
|
14
14
|
cadwyn/codegen/_plugins/class_renaming.py,sha256=5ka2W1c18i4maNbEkEpELvGLEFbd8tthvQX3YA3Bu0A,1843
|
|
15
|
-
cadwyn/codegen/_plugins/import_auto_adding.py,sha256=
|
|
15
|
+
cadwyn/codegen/_plugins/import_auto_adding.py,sha256=00zGK99cT-bq2eXKDlYBR5-Z3uHLOGU7dbhB0YFFrt0,2613
|
|
16
16
|
cadwyn/codegen/_plugins/latest_version_aliasing.py,sha256=s8TgpBL9FofKyJaF4AB7UxJYkq-f9LiMVL33LJxFVp4,4010
|
|
17
17
|
cadwyn/codegen/_plugins/module_migrations.py,sha256=TeWJk4Iu4SRQ9K2iI3v3sCs1110jrltKlPdfU9mXIsQ,722
|
|
18
18
|
cadwyn/exceptions.py,sha256=XOLsT4EH1uGNirmKlkgEk03PjUMtD7tgaCDadt_eBbE,695
|
|
@@ -21,14 +21,14 @@ cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
21
21
|
cadwyn/routing.py,sha256=3H8GKT10wAYroAOCtGCNBMuA63SGAQSESze3EhBLwo0,34433
|
|
22
22
|
cadwyn/structure/__init__.py,sha256=BjFPlQYCw8ds_4zxdCi2LimarUGqSzyTNmOdT-FkGms,661
|
|
23
23
|
cadwyn/structure/common.py,sha256=6Z4nI97XPWTCinn6np73m-rLPyYNrz2fWXKJlqjsiaQ,269
|
|
24
|
-
cadwyn/structure/data.py,sha256=
|
|
24
|
+
cadwyn/structure/data.py,sha256=cCaclB67mKlgRiydPFijyfLdng4qyqnY_hP8ApS5pT4,5781
|
|
25
25
|
cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY,5627
|
|
26
26
|
cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
|
|
27
27
|
cadwyn/structure/modules.py,sha256=1FK-lLm-zOTXEvn-QtyBH38aDRht5PDQiZrOPCsBlM4,1268
|
|
28
|
-
cadwyn/structure/schemas.py,sha256=
|
|
29
|
-
cadwyn/structure/versions.py,sha256=
|
|
30
|
-
cadwyn-3.
|
|
31
|
-
cadwyn-3.
|
|
32
|
-
cadwyn-3.
|
|
33
|
-
cadwyn-3.
|
|
34
|
-
cadwyn-3.
|
|
28
|
+
cadwyn/structure/schemas.py,sha256=LIKwDuzorVC9AHg4EN-UYdI133lCk_2MkBTdiyAr-EQ,8808
|
|
29
|
+
cadwyn/structure/versions.py,sha256=UcjsvSzYNgRtTmsCgnCXdv0LtNYTZqCs5yEpU68gnq4,27980
|
|
30
|
+
cadwyn-3.4.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
|
|
31
|
+
cadwyn-3.4.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
32
|
+
cadwyn-3.4.0.dist-info/WHEEL,sha256=vxFmldFsRN_Hx10GDvsdv1wroKq8r5Lzvjp6GZ4OO8c,88
|
|
33
|
+
cadwyn-3.4.0.dist-info/METADATA,sha256=vrs8AOFwEjJZ6L_3fshUEFgRTmfWj7gh_pqGtn5axvg,4264
|
|
34
|
+
cadwyn-3.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|