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 CHANGED
@@ -1,22 +1,39 @@
1
1
  import importlib.metadata
2
2
 
3
3
  from .applications import Cadwyn
4
- from .codegen import generate_code_for_versioned_packages
5
- from .route_generation import (
6
- InternalRepresentationOf, # pyright: ignore[reportDeprecated]
7
- VersionedAPIRouter,
8
- generate_versioned_routers,
4
+ from .route_generation import VersionedAPIRouter, generate_versioned_routers
5
+ from .schema_generation import migrate_response_body
6
+ from .structure import (
7
+ HeadVersion,
8
+ RequestInfo,
9
+ ResponseInfo,
10
+ Version,
11
+ VersionBundle,
12
+ VersionChange,
13
+ VersionChangeWithSideEffects,
14
+ convert_request_to_next_version_for,
15
+ convert_response_to_previous_version_for,
16
+ endpoint,
17
+ enum,
18
+ schema,
9
19
  )
10
- from .structure import HeadVersion, Version, VersionBundle
11
20
 
12
21
  __version__ = importlib.metadata.version("cadwyn")
13
22
  __all__ = [
14
23
  "Cadwyn",
15
24
  "VersionedAPIRouter",
16
- "generate_code_for_versioned_packages",
17
25
  "VersionBundle",
18
26
  "HeadVersion",
19
27
  "Version",
28
+ "migrate_response_body",
20
29
  "generate_versioned_routers",
21
- "InternalRepresentationOf",
30
+ "VersionChange",
31
+ "VersionChangeWithSideEffects",
32
+ "endpoint",
33
+ "schema",
34
+ "enum",
35
+ "convert_response_to_previous_version_for",
36
+ "convert_request_to_next_version_for",
37
+ "RequestInfo",
38
+ "ResponseInfo",
22
39
  ]
cadwyn/__main__.py CHANGED
@@ -1,13 +1,14 @@
1
- import importlib
2
- import sys
3
- import warnings
4
- from pathlib import Path
5
- from typing import Any
1
+ from datetime import date
2
+ from typing import Annotated
6
3
 
7
4
  import typer
5
+ from rich.console import Console
6
+ from rich.syntax import Syntax
8
7
 
9
- from cadwyn.exceptions import CadwynError
10
- from cadwyn.structure.versions import VersionBundle
8
+ from cadwyn._render import render_model_by_path, render_module_by_path
9
+
10
+ _CONSOLE = Console()
11
+ _RAW_ARG = Annotated[bool, typer.Option(help="Output code without color")]
11
12
 
12
13
  app = typer.Typer(
13
14
  name="cadwyn",
@@ -15,6 +16,14 @@ app = typer.Typer(
15
16
  help="Modern Stripe-like API versioning in FastAPI",
16
17
  )
17
18
 
19
+ render_subapp = typer.Typer(
20
+ name="render",
21
+ add_completion=False,
22
+ help="Render pydantic models and enums from a certainn version and output them to stdout",
23
+ )
24
+
25
+ app.add_typer(render_subapp)
26
+
18
27
 
19
28
  def version_callback(value: bool):
20
29
  if value:
@@ -24,92 +33,39 @@ def version_callback(value: bool):
24
33
  raise typer.Exit
25
34
 
26
35
 
27
- @app.command(name="generate-code-for-versioned-packages", hidden=True)
28
- def deprecated_generate_versioned_packages(
29
- path_to_template_package: str = typer.Argument(
30
- ...,
31
- help=(
32
- "The python path to the template package, from which we will generate the versioned packages. "
33
- "Format: 'path.to.template_package'"
34
- ),
35
- show_default=False,
36
- ),
37
- full_path_to_version_bundle: str = typer.Argument(
38
- ...,
39
- help="The python path to the version bundle. Format: 'path.to.version_bundle:my_version_bundle_variable'",
40
- show_default=False,
41
- ),
42
- ignore_coverage_for_latest_aliases: bool = typer.Option(
43
- default=True,
44
- help="Add a pragma: no cover comment to the star imports in the generated version of the latest module.",
45
- ),
36
+ def output_code(code: str, raw: bool):
37
+ if raw:
38
+ typer.echo(code)
39
+ else:
40
+ _CONSOLE.print(Syntax(code, "python", line_numbers=True))
41
+
42
+
43
+ @render_subapp.command(
44
+ name="model",
45
+ help="Render a concrete pydantic model or enum from a certain version and output it to stdout",
46
+ short_help="Render a single model or enum",
47
+ )
48
+ def render(
49
+ model: Annotated[str, typer.Argument(metavar="<module>:<attribute>", help="Python path to the model to render")],
50
+ app: Annotated[str, typer.Option(metavar="<module>:<attribute>", help="Python path to the main Cadwyn app")],
51
+ version: Annotated[str, typer.Option(parser=lambda s: str(date.fromisoformat(s)), metavar="ISO-VERSION")],
52
+ raw: _RAW_ARG = False,
46
53
  ) -> None:
47
- """For each version in the version bundle, generate a versioned package based on the template package"""
48
- warnings.warn(
49
- "`cadwyn generate-code-for-versioned-packages` is deprecated. Please, use `cadwyn codegen` instead",
50
- DeprecationWarning,
51
- stacklevel=1,
52
- )
53
-
54
- from .codegen._main import generate_code_for_versioned_packages
55
-
56
- sys.path.append(str(Path.cwd()))
57
- template_package = importlib.import_module(path_to_template_package)
58
- path_to_version_bundle, version_bundle_variable_name = full_path_to_version_bundle.split(":")
59
- version_bundle_module = importlib.import_module(path_to_version_bundle)
60
- possibly_version_bundle = getattr(version_bundle_module, version_bundle_variable_name)
61
- version_bundle = _get_version_bundle(possibly_version_bundle)
62
-
63
- return generate_code_for_versioned_packages( # pyright: ignore[reportDeprecated]
64
- template_package,
65
- version_bundle,
66
- ignore_coverage_for_latest_aliases=ignore_coverage_for_latest_aliases,
67
- )
68
-
69
-
70
- @app.command(
71
- name="codegen",
72
- help=(
73
- "For each version in the version bundle, generate a versioned package based on the "
74
- "`head_schema_package` package"
75
- ),
76
- short_help="Generate code for all versions of schemas",
54
+ output_code(render_model_by_path(model, app, version), raw)
55
+
56
+
57
+ @render_subapp.command(
58
+ name="module",
59
+ help="Render all versioned models and enums within a module from a certain version and output them to stdout",
60
+ short_help="Render all models and enums from an entire module",
77
61
  )
78
- def generate_versioned_packages(
79
- full_path_to_version_bundle: str = typer.Argument(
80
- ...,
81
- help="The python path to the version bundle. Format: 'path.to.version_bundle:my_version_bundle_var'",
82
- show_default=False,
83
- ),
62
+ def render_module(
63
+ module: Annotated[str, typer.Argument(metavar="<module>", help="Python path to the module to render")],
64
+ app: Annotated[str, typer.Option(metavar="<module>:<attribute>", help="Python path to the main Cadwyn app")],
65
+ version: Annotated[str, typer.Option(parser=lambda s: str(date.fromisoformat(s)), metavar="ISO-VERSION")],
66
+ raw: _RAW_ARG = False,
84
67
  ) -> None:
85
- from .codegen._main import generate_code_for_versioned_packages
86
-
87
- sys.path.append(str(Path.cwd()))
88
- path_to_version_bundle, version_bundle_variable_name = full_path_to_version_bundle.split(":")
89
- version_bundle_module = importlib.import_module(path_to_version_bundle)
90
- possibly_version_bundle = getattr(version_bundle_module, version_bundle_variable_name)
91
- version_bundle = _get_version_bundle(possibly_version_bundle)
92
-
93
- if version_bundle.head_schemas_package is None: # pragma: no cover
94
- raise CadwynError("VersionBundle requires a 'head_schemas_package' argument to generate schemas.")
95
-
96
- return generate_code_for_versioned_packages(version_bundle.head_schemas_package, version_bundle)
97
-
98
-
99
- def _get_version_bundle(possibly_version_bundle: Any) -> VersionBundle:
100
- if not isinstance(possibly_version_bundle, VersionBundle):
101
- err = TypeError(
102
- "The provided version bundle is not a version bundle and "
103
- "is not a zero-argument callable that returns the version bundle. "
104
- f"Instead received: {possibly_version_bundle}",
105
- )
106
- if callable(possibly_version_bundle):
107
- try:
108
- return _get_version_bundle(possibly_version_bundle())
109
- except TypeError as e:
110
- raise err from e
111
- raise err
112
- return possibly_version_bundle
68
+ output_code(render_module_by_path(module, app, version), raw)
113
69
 
114
70
 
115
71
  @app.callback()
cadwyn/_asts.py CHANGED
@@ -1,13 +1,9 @@
1
1
  import ast
2
2
  import inspect
3
- import re
4
3
  from collections.abc import Callable
5
- from dataclasses import dataclass
6
4
  from enum import Enum, auto
7
- from pathlib import Path
8
- from types import GenericAlias, LambdaType, ModuleType, NoneType
5
+ from types import GenericAlias, LambdaType, NoneType
9
6
  from typing import ( # noqa: UP035
10
- TYPE_CHECKING,
11
7
  Any,
12
8
  List,
13
9
  cast,
@@ -15,21 +11,12 @@ from typing import ( # noqa: UP035
15
11
  get_origin,
16
12
  )
17
13
 
18
- from cadwyn._compat import (
19
- PYDANTIC_V2,
20
- is_pydantic_1_constrained_type,
21
- )
22
- from cadwyn._package_utils import (
23
- get_absolute_python_path_of_import,
24
- )
25
- from cadwyn._utils import PlainRepr, UnionType
26
- from cadwyn.exceptions import CodeGenerationError, InvalidGenerationInstructionError, ModuleIsNotAvailableAsTextError
14
+ import annotated_types
27
15
 
28
- if TYPE_CHECKING:
29
- import annotated_types
16
+ from cadwyn._utils import PlainRepr, UnionType
17
+ from cadwyn.exceptions import InvalidGenerationInstructionError
30
18
 
31
19
  _LambdaFunctionName = (lambda: None).__name__ # pragma: no branch
32
- _RE_CAMEL_TO_SNAKE = re.compile(r"(?<!^)(?=[A-Z])")
33
20
 
34
21
 
35
22
  # A parent type of typing._GenericAlias
@@ -42,11 +29,8 @@ GenericAliasUnion = GenericAlias | _BaseGenericAlias
42
29
 
43
30
 
44
31
  def get_fancy_repr(value: Any):
45
- if PYDANTIC_V2:
46
- import annotated_types
47
-
48
- if isinstance(value, annotated_types.GroupedMetadata) and hasattr(type(value), "__dataclass_fields__"):
49
- return transform_grouped_metadata(value)
32
+ if isinstance(value, annotated_types.GroupedMetadata) and hasattr(type(value), "__dataclass_fields__"):
33
+ return transform_grouped_metadata(value)
50
34
  if isinstance(value, list | tuple | set | frozenset):
51
35
  return transform_collection(value)
52
36
  if isinstance(value, dict):
@@ -59,7 +43,7 @@ def get_fancy_repr(value: Any):
59
43
  return transform_type(value)
60
44
  if isinstance(value, Enum):
61
45
  return transform_enum(value)
62
- if isinstance(value, auto):
46
+ if isinstance(value, auto): # pragma: no cover # it works but we no longer use auto
63
47
  return transform_auto(value)
64
48
  if isinstance(value, UnionType):
65
49
  return transform_union(value)
@@ -101,29 +85,11 @@ def transform_generic_alias(value: GenericAliasUnion) -> Any:
101
85
  return f"{get_fancy_repr(get_origin(value))}[{', '.join(get_fancy_repr(a) for a in get_args(value))}]"
102
86
 
103
87
 
104
- def transform_none(_: Any) -> Any:
88
+ def transform_none(_: NoneType) -> Any:
105
89
  return "None"
106
90
 
107
91
 
108
92
  def transform_type(value: type) -> Any:
109
- # This is a hack for pydantic's Constrained types
110
- if is_pydantic_1_constrained_type(value):
111
- parent = value.mro()[1]
112
- snake_case = _RE_CAMEL_TO_SNAKE.sub("_", value.__name__)
113
- cls_name = "con" + "".join(snake_case.split("_")[1:-1])
114
- return (
115
- cls_name.lower()
116
- + "("
117
- + ", ".join(
118
- [
119
- f"{key}={get_fancy_repr(val)}"
120
- for key, val in value.__dict__.items()
121
- if not key.startswith("_") and val is not None and val != parent.__dict__[key]
122
- ],
123
- )
124
- + ")"
125
- )
126
-
127
93
  return value.__name__
128
94
 
129
95
 
@@ -131,7 +97,7 @@ def transform_enum(value: Enum) -> Any:
131
97
  return PlainRepr(f"{value.__class__.__name__}.{value.name}")
132
98
 
133
99
 
134
- def transform_auto(_: auto) -> Any:
100
+ def transform_auto(_: auto) -> Any: # pragma: no cover # it works but we no longer use auto
135
101
  return PlainRepr("auto()")
136
102
 
137
103
 
@@ -172,68 +138,6 @@ def _get_lambda_source_from_default_factory(source: str) -> str:
172
138
  )
173
139
 
174
140
 
175
- def read_python_module(module: ModuleType) -> str:
176
- # Can be cached in the future to gain some speedups
177
- try:
178
- return inspect.getsource(module)
179
- except OSError as e:
180
- if module.__file__ is None: # pragma: no cover
181
- raise CodeGenerationError(f"Failed to get file path to the module {module}") from e
182
- path = Path(module.__file__)
183
- if path.is_file() and path.read_text() == "":
184
- return ""
185
- raise ModuleIsNotAvailableAsTextError( # pragma: no cover
186
- f"Failed to get source code for module {module}. "
187
- "This is likely because this module is not available as code "
188
- "(it could be a compiled C extension or even a .pyc file). "
189
- "Cadwyn does not support models from such code. "
190
- "Please, open an issue on Cadwyn's issue tracker if you believe that your use case is valid "
191
- "and if you believe that it is possible for Cadwyn to support it.",
192
- ) from e
193
-
194
-
195
- def get_all_names_defined_at_toplevel_of_module(body: ast.Module, module_python_path: str) -> dict[str, str]:
196
- """Some day we will want to use this to auto-add imports for new symbols in versions. Some day..."""
197
- defined_names = {}
198
- for node in body.body:
199
- if isinstance(node, ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef):
200
- defined_names[node.name] = module_python_path
201
- elif isinstance(node, ast.Assign):
202
- for target in node.targets:
203
- if isinstance(target, ast.Name):
204
- defined_names[target.id] = module_python_path
205
- elif isinstance(node, ast.ImportFrom):
206
- for name in node.names:
207
- defined_names[name.name] = get_absolute_python_path_of_import(node, module_python_path)
208
- elif isinstance(node, ast.Import):
209
- for name in node.names:
210
- defined_names[name.name] = name.name
211
- return defined_names
212
-
213
-
214
- def add_keyword_to_call(attr_name: str, attr_value: Any, call: ast.Call):
215
- new_keyword = get_ast_keyword_from_argument_name_and_value(attr_name, attr_value)
216
- for i, keyword in enumerate(call.keywords):
217
- if keyword.arg == attr_name:
218
- call.keywords[i] = new_keyword
219
- break
220
- else:
221
- call.keywords.append(new_keyword)
222
-
223
-
224
- def delete_keyword_from_call(attr_name: str, call: ast.Call):
225
- for i, keyword in enumerate(call.keywords): # pragma: no branch
226
- if keyword.arg == attr_name:
227
- call.keywords.pop(i)
228
- break
229
-
230
-
231
- def get_ast_keyword_from_argument_name_and_value(name: str, value: Any):
232
- if not isinstance(value, ast.AST):
233
- value = ast.parse(get_fancy_repr(value), mode="eval").body
234
- return ast.keyword(arg=name, value=value) # pyright: ignore[reportArgumentType, reportCallIssue]
235
-
236
-
237
141
  def pop_docstring_from_cls_body(cls_body: list[ast.stmt]) -> list[ast.stmt]:
238
142
  if (
239
143
  len(cls_body) > 0
@@ -244,31 +148,3 @@ def pop_docstring_from_cls_body(cls_body: list[ast.stmt]) -> list[ast.stmt]:
244
148
  return [cls_body.pop(0)]
245
149
  else:
246
150
  return []
247
-
248
-
249
- @dataclass(slots=True)
250
- class _ValidatorWrapper:
251
- func_ast: ast.FunctionDef
252
- index_of_validator_decorator: int
253
- field_names: set[str | ast.expr] | None
254
- is_deleted: bool = False
255
-
256
-
257
- def get_validator_info_or_none(method: ast.FunctionDef) -> _ValidatorWrapper | None:
258
- for index, decorator in enumerate(method.decorator_list):
259
- # The cases we handle here:
260
- # * `Name(id="root_validator")`
261
- # * `Call(func=Name(id="validator"), args=[Constant(value="foo")])`
262
- # * `Attribute(value=Name(id="pydantic"), attr="root_validator")`
263
- # * `Call(func=Attribute(value=Name(id="pydantic"), attr="root_validator"), args=[])`
264
-
265
- if isinstance(decorator, ast.Call) and ast.unparse(decorator.func).endswith("validator"):
266
- if len(decorator.args) == 0:
267
- return _ValidatorWrapper(method, index, None)
268
- else:
269
- return _ValidatorWrapper(
270
- method, index, {arg.value if isinstance(arg, ast.Constant) else arg for arg in decorator.args}
271
- )
272
- elif isinstance(decorator, ast.Name | ast.Attribute) and ast.unparse(decorator).endswith("validator"):
273
- return _ValidatorWrapper(method, index, None)
274
- return None
cadwyn/_importer.py ADDED
@@ -0,0 +1,31 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ from cadwyn.exceptions import ImportFromStringError
5
+
6
+
7
+ def import_attribute_from_string(import_str: str) -> Any:
8
+ module_str, _, attrs_str = import_str.partition(":")
9
+ if not module_str or not attrs_str:
10
+ message = 'Import string "{import_str}" must be in format "<module>:<attribute>".'
11
+ raise ImportFromStringError(message.format(import_str=import_str))
12
+
13
+ module = import_module_from_string(module_str)
14
+
15
+ instance = module
16
+ try:
17
+ for attr_str in attrs_str.split("."):
18
+ instance = getattr(instance, attr_str)
19
+ except AttributeError as e:
20
+ raise ImportFromStringError(f'Attribute "{attrs_str}" not found in module "{module_str}".') from e
21
+
22
+ return instance
23
+
24
+
25
+ def import_module_from_string(module_str: str):
26
+ try:
27
+ return importlib.import_module(module_str)
28
+ except ModuleNotFoundError as e:
29
+ if e.name != module_str: # pragma: no cover
30
+ raise e from None
31
+ raise ImportFromStringError(f'Could not import module "{module_str}".') from e
cadwyn/_render.py ADDED
@@ -0,0 +1,152 @@
1
+ import ast
2
+ import inspect
3
+ import textwrap
4
+ from enum import Enum
5
+ from typing import TYPE_CHECKING
6
+
7
+ import typer
8
+ from issubclass import issubclass as lenient_issubclass
9
+ from pydantic import BaseModel
10
+
11
+ from cadwyn._asts import get_fancy_repr, pop_docstring_from_cls_body
12
+ from cadwyn.exceptions import CadwynRenderError
13
+ from cadwyn.schema_generation import (
14
+ PydanticFieldWrapper,
15
+ _EnumWrapper,
16
+ _generate_versioned_models,
17
+ _PydanticRuntimeModelWrapper,
18
+ )
19
+ from cadwyn.structure.versions import VersionBundle, get_cls_pythonpath
20
+
21
+ from ._importer import import_attribute_from_string, import_module_from_string
22
+
23
+ if TYPE_CHECKING:
24
+ from cadwyn.applications import Cadwyn
25
+
26
+
27
+ def render_module_by_path(module_path: str, app_path: str, version: str):
28
+ module = import_module_from_string(module_path)
29
+ app: Cadwyn = import_attribute_from_string(app_path)
30
+ attributes_to_alter = [
31
+ name
32
+ for name, value in module.__dict__.items()
33
+ if lenient_issubclass(value, Enum | BaseModel) and value.__module__ == module.__name__
34
+ ]
35
+
36
+ try:
37
+ module_ast = ast.parse(inspect.getsource(module))
38
+ except (OSError, SyntaxError, ValueError) as e: # pragma: no cover
39
+ raise CadwynRenderError(f"Failed to find the source for module {module.__name__}") from e
40
+
41
+ return ast.unparse(
42
+ ast.Module(
43
+ body=[
44
+ _render_model_from_ast(node, getattr(module, node.name), app.versions, version)
45
+ if isinstance(node, ast.ClassDef) and node.name in attributes_to_alter
46
+ else node
47
+ for node in module_ast.body
48
+ ],
49
+ type_ignores=module_ast.type_ignores,
50
+ )
51
+ )
52
+
53
+
54
+ def render_model_by_path(model_path: str, app_path: str, version: str) -> str:
55
+ # cadwyn render model schemas:MySchema --app=run:app --version=2000-01-01
56
+ model: type[BaseModel | Enum] = import_attribute_from_string(model_path)
57
+ app: Cadwyn = import_attribute_from_string(app_path)
58
+ return render_model(model, app.versions, version)
59
+
60
+
61
+ def render_model(model: type[BaseModel | Enum], versions: VersionBundle, version: str) -> str:
62
+ try:
63
+ original_cls_node = ast.parse(textwrap.dedent(inspect.getsource(model))).body[0]
64
+ except (OSError, SyntaxError, ValueError): # pragma: no cover
65
+ typer.echo(f"Failed to find the source for model {get_cls_pythonpath(model)}")
66
+ return f"class {model.__name__}: 'failed to find the original class source'"
67
+ if not isinstance(original_cls_node, ast.ClassDef):
68
+ raise TypeError(f"{get_cls_pythonpath(model)} is not a class")
69
+
70
+ return ast.unparse(_render_model_from_ast(original_cls_node, model, versions, version))
71
+
72
+
73
+ def _render_model_from_ast(
74
+ model_ast: ast.ClassDef, model: type[BaseModel | Enum], versions: VersionBundle, version: str
75
+ ):
76
+ versioned_models = _generate_versioned_models(versions)
77
+ generator = versioned_models[version]
78
+ wrapper = generator._get_wrapper_for_model(model)
79
+
80
+ if isinstance(wrapper, _EnumWrapper):
81
+ return _render_enum_model(wrapper, model_ast)
82
+ else:
83
+ return _render_pydantic_model(wrapper, model_ast)
84
+
85
+
86
+ def _render_enum_model(wrapper: _EnumWrapper, original_cls_node: ast.ClassDef):
87
+ # This is for possible schema renaming
88
+ original_cls_node.name = wrapper.cls.__name__
89
+
90
+ new_body = [
91
+ ast.Assign(
92
+ targets=[ast.Name(member, ctx=ast.Store())],
93
+ value=ast.Name(get_fancy_repr(member_value)),
94
+ lineno=0,
95
+ )
96
+ for member, member_value in wrapper.members.items()
97
+ ]
98
+
99
+ old_body = [
100
+ n for n in original_cls_node.body if not isinstance(n, ast.AnnAssign | ast.Assign | ast.Pass | ast.Constant)
101
+ ]
102
+ docstring = pop_docstring_from_cls_body(old_body)
103
+
104
+ original_cls_node.body = docstring + new_body + old_body
105
+ if not original_cls_node.body:
106
+ original_cls_node.body = [ast.Pass()]
107
+ return original_cls_node
108
+
109
+
110
+ def _render_pydantic_model(wrapper: _PydanticRuntimeModelWrapper, original_cls_node: ast.ClassDef):
111
+ # This is for possible schema renaming
112
+ original_cls_node.name = wrapper.name
113
+
114
+ field_definitions = [
115
+ ast.AnnAssign(
116
+ target=ast.Name(name),
117
+ annotation=ast.Name(get_fancy_repr(field.annotation)),
118
+ value=_generate_field_ast(field),
119
+ simple=1,
120
+ )
121
+ for name, field in wrapper.fields.items()
122
+ ]
123
+ validator_definitions = [
124
+ ast.parse(textwrap.dedent(inspect.getsource(validator.func))).body[0]
125
+ for validator in wrapper.validators.values()
126
+ if not validator.is_deleted
127
+ ]
128
+
129
+ old_body = [
130
+ n
131
+ for n in original_cls_node.body
132
+ if not (
133
+ isinstance(n, ast.AnnAssign | ast.Assign | ast.Pass | ast.Constant)
134
+ or (isinstance(n, ast.FunctionDef) and n.name in wrapper.validators)
135
+ )
136
+ ]
137
+ docstring = pop_docstring_from_cls_body(old_body)
138
+ original_cls_node.body = docstring + field_definitions + validator_definitions + old_body
139
+ if not original_cls_node.body:
140
+ original_cls_node.body = [ast.Pass()]
141
+ return original_cls_node
142
+
143
+
144
+ def _generate_field_ast(field: PydanticFieldWrapper) -> ast.Call:
145
+ return ast.Call(
146
+ func=ast.Name("Field"),
147
+ args=[],
148
+ keywords=[
149
+ ast.keyword(arg=attr, value=ast.parse(get_fancy_repr(attr_value), mode="eval").body)
150
+ for attr, attr_value in field.passed_field_attributes.items()
151
+ ],
152
+ )