cadwyn 3.4.5__py3-none-any.whl → 3.6.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,7 +1,7 @@
1
1
  import importlib.metadata
2
2
 
3
+ from .applications import Cadwyn
3
4
  from .codegen import generate_code_for_versioned_packages
4
- from .main import Cadwyn
5
5
  from .routing import InternalRepresentationOf, VersionedAPIRouter, generate_versioned_routers
6
6
  from .structure import VersionBundle
7
7
 
@@ -76,7 +76,7 @@ def transform_grouped_metadata(value: "annotated_types.GroupedMetadata"):
76
76
  modified_fields = []
77
77
  empty_obj = type(value)
78
78
 
79
- for key in empty_obj.__dataclass_fields__: # pyright: ignore[reportGeneralTypeIssues]
79
+ for key in empty_obj.__dataclass_fields__: # pyright: ignore[reportAttributeAccessIssue]
80
80
  if getattr(value, key) != getattr(empty_obj, key):
81
81
  modified_fields.append((key, getattr(value, key)))
82
82
 
@@ -136,7 +136,7 @@ def transform_auto(_: auto) -> Any:
136
136
  return PlainRepr("auto()")
137
137
 
138
138
 
139
- def transform_union(value: UnionType) -> Any:
139
+ def transform_union(value: UnionType) -> Any: # pyright: ignore[reportInvalidTypeForm]
140
140
  return "typing.Union[" + (", ".join(get_fancy_repr(a) for a in get_args(value))) + "]"
141
141
 
142
142
 
cadwyn/_compat.py CHANGED
@@ -7,7 +7,7 @@ import pydantic
7
7
  from fastapi._compat import ModelField as FastAPIModelField
8
8
  from pydantic import BaseModel, Field
9
9
 
10
- ModelField: TypeAlias = Any # pyright: ignore[reportGeneralTypeIssues]
10
+ ModelField: TypeAlias = Any # pyright: ignore[reportRedeclaration]
11
11
  PydanticUndefined: TypeAlias = Any
12
12
  VALIDATOR_CONFIG_KEY = "__validators__"
13
13
 
@@ -61,7 +61,7 @@ class PydanticFieldWrapper:
61
61
 
62
62
  annotation: Any
63
63
 
64
- init_model_field: dataclasses.InitVar[ModelField] # pyright: ignore[reportGeneralTypeIssues]
64
+ init_model_field: dataclasses.InitVar[ModelField] # pyright: ignore[reportInvalidTypeForm]
65
65
  field_info: FieldInfo = dataclasses.field(init=False)
66
66
 
67
67
  annotation_ast: ast.expr | None = None
@@ -69,7 +69,7 @@ class PydanticFieldWrapper:
69
69
  # the value_ast is "None" and "Field(default=None)" respectively
70
70
  value_ast: ast.expr | None = None
71
71
 
72
- def __post_init__(self, init_model_field: ModelField): # pyright: ignore[reportGeneralTypeIssues]
72
+ def __post_init__(self, init_model_field: ModelField): # pyright: ignore[reportInvalidTypeForm]
73
73
  if isinstance(init_model_field, FieldInfo):
74
74
  self.field_info = init_model_field
75
75
  else:
@@ -134,10 +134,10 @@ def rebuild_fastapi_body_param(old_body_param: FastAPIModelField, new_body_param
134
134
  kwargs.update(
135
135
  {
136
136
  "type_": new_body_param_type,
137
- "class_validators": old_body_param.class_validators, # pyright: ignore[reportGeneralTypeIssues]
137
+ "class_validators": old_body_param.class_validators, # pyright: ignore[reportAttributeAccessIssue]
138
138
  "default": old_body_param.default,
139
139
  "required": old_body_param.required,
140
- "model_config": old_body_param.model_config, # pyright: ignore[reportGeneralTypeIssues]
140
+ "model_config": old_body_param.model_config, # pyright: ignore[reportAttributeAccessIssue]
141
141
  "alias": old_body_param.alias,
142
142
  },
143
143
  )
cadwyn/_utils.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
  import importlib
3
3
  import inspect
4
+ import sys
4
5
  from collections.abc import Callable, Collection
5
6
  from pathlib import Path
6
7
  from types import ModuleType
@@ -35,15 +36,28 @@ class PlainRepr(str):
35
36
 
36
37
  def same_definition_as_in(t: _T) -> Callable[[Callable], _T]:
37
38
  def decorator(f: Callable) -> _T:
38
- return f # pyright: ignore[reportGeneralTypeIssues]
39
+ return f # pyright: ignore[reportReturnType]
39
40
 
40
41
  return decorator
41
42
 
42
43
 
44
+ @functools.cache
45
+ def get_another_version_of_cls(
46
+ cls_from_old_version: type[Any], new_version_dir: Path, version_dirs: frozenset[Path] | tuple[Path, ...]
47
+ ):
48
+ # version_dir = /home/myuser/package/companies/v2021_01_01
49
+ module_from_old_version = sys.modules[cls_from_old_version.__module__]
50
+ try:
51
+ module = get_another_version_of_module(module_from_old_version, new_version_dir, version_dirs)
52
+ except ModuleIsNotVersionedError:
53
+ return cls_from_old_version
54
+ return getattr(module, cls_from_old_version.__name__)
55
+
56
+
43
57
  def get_another_version_of_module(
44
58
  module_from_old_version: ModuleType,
45
59
  new_version_dir: Path,
46
- version_dirs: frozenset[Path],
60
+ version_dirs: frozenset[Path] | tuple[Path, ...],
47
61
  ):
48
62
  new_model_module_python_path = get_pythonpath_to_another_version_of_module(
49
63
  module_from_old_version,
@@ -56,7 +70,7 @@ def get_another_version_of_module(
56
70
  def get_pythonpath_to_another_version_of_module(
57
71
  module_from_old_version: ModuleType,
58
72
  new_version_dir: Path,
59
- version_dirs: frozenset[Path],
73
+ version_dirs: frozenset[Path] | tuple[Path, ...],
60
74
  ) -> str:
61
75
  # ['package', 'companies', 'latest', 'schemas']
62
76
  # ^^^^^^
cadwyn/applications.py ADDED
@@ -0,0 +1,126 @@
1
+ from collections.abc import Callable, Coroutine, Sequence
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ from fastapi import APIRouter, routing
5
+ from fastapi.datastructures import Default
6
+ from fastapi.params import Depends
7
+ from fastapi.utils import generate_unique_id
8
+ from starlette.middleware import Middleware
9
+ from starlette.requests import Request
10
+ from starlette.responses import JSONResponse, Response
11
+ from starlette.routing import BaseRoute
12
+ from starlette.types import Lifespan
13
+ from typing_extensions import Self
14
+ from verselect import HeaderRoutingFastAPI
15
+
16
+ from cadwyn.exceptions import CadwynError
17
+ from cadwyn.routing import generate_versioned_routers
18
+ from cadwyn.structure import VersionBundle
19
+
20
+ if TYPE_CHECKING:
21
+ from types import ModuleType
22
+
23
+
24
+ class Cadwyn(HeaderRoutingFastAPI):
25
+ def __init__(
26
+ self,
27
+ *,
28
+ versions: VersionBundle,
29
+ api_version_header_name: str = "x-api-version",
30
+ debug: bool = False,
31
+ title: str = "FastAPI",
32
+ summary: str | None = None,
33
+ description: str = "",
34
+ version: str = "0.1.0",
35
+ openapi_url: str | None = "/openapi.json",
36
+ openapi_tags: list[dict[str, Any]] | None = None,
37
+ servers: list[dict[str, str | Any]] | None = None,
38
+ dependencies: Sequence[Depends] | None = None,
39
+ default_response_class: type[Response] = Default(JSONResponse), # noqa: B008
40
+ redirect_slashes: bool = True,
41
+ docs_url: str | None = "/docs",
42
+ redoc_url: None = None,
43
+ swagger_ui_oauth2_redirect_url: str | None = "/docs/oauth2-redirect",
44
+ swagger_ui_init_oauth: dict[str, Any] | None = None,
45
+ middleware: Sequence[Middleware] | None = None,
46
+ exception_handlers: dict[int | type[Exception], Callable[[Request, Any], Coroutine[Any, Any, Response]]]
47
+ | None = None,
48
+ on_startup: Sequence[Callable[[], Any]] | None = None,
49
+ on_shutdown: Sequence[Callable[[], Any]] | None = None,
50
+ lifespan: Lifespan[Self] | None = None,
51
+ terms_of_service: str | None = None,
52
+ contact: dict[str, str | Any] | None = None,
53
+ license_info: dict[str, str | Any] | None = None,
54
+ openapi_prefix: str = "",
55
+ root_path: str = "",
56
+ root_path_in_servers: bool = True,
57
+ responses: dict[int | str, dict[str, Any]] | None = None,
58
+ callbacks: list[BaseRoute] | None = None,
59
+ webhooks: APIRouter | None = None,
60
+ deprecated: bool | None = None,
61
+ include_in_schema: bool = True,
62
+ swagger_ui_parameters: dict[str, Any] | None = None,
63
+ generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(generate_unique_id), # noqa: B008
64
+ separate_input_output_schemas: bool = True,
65
+ **extra: Any,
66
+ ) -> None:
67
+ # TODO: Remove argument entirely in any major version.
68
+ self.versions = versions
69
+ latest_schemas_package = extra.pop("latest_schemas_package", None) or self.versions.latest_schemas_package
70
+ if latest_schemas_package is None:
71
+ raise CadwynError(
72
+ "VersionBundle.latest_schemas_package is None but is required for Cadwyn's correct functioning."
73
+ )
74
+ self.latest_schemas_package: ModuleType = latest_schemas_package
75
+ self.versions.latest_schemas_package = latest_schemas_package
76
+
77
+ super().__init__(
78
+ api_version_header_name=api_version_header_name,
79
+ api_version_var=versions.api_version_var,
80
+ debug=debug,
81
+ title=title,
82
+ summary=summary,
83
+ description=description,
84
+ version=version,
85
+ openapi_url=openapi_url,
86
+ openapi_tags=openapi_tags,
87
+ servers=servers,
88
+ dependencies=dependencies,
89
+ default_response_class=default_response_class,
90
+ redirect_slashes=redirect_slashes,
91
+ docs_url=docs_url,
92
+ swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url,
93
+ swagger_ui_init_oauth=swagger_ui_init_oauth,
94
+ middleware=middleware,
95
+ exception_handlers=exception_handlers,
96
+ on_startup=on_startup,
97
+ on_shutdown=on_shutdown,
98
+ lifespan=lifespan,
99
+ terms_of_service=terms_of_service,
100
+ contact=contact,
101
+ license_info=license_info,
102
+ openapi_prefix=openapi_prefix,
103
+ root_path=root_path,
104
+ root_path_in_servers=root_path_in_servers,
105
+ responses=responses,
106
+ callbacks=callbacks,
107
+ webhooks=webhooks,
108
+ deprecated=deprecated,
109
+ include_in_schema=include_in_schema,
110
+ swagger_ui_parameters=swagger_ui_parameters,
111
+ generate_unique_id_function=generate_unique_id_function,
112
+ separate_input_output_schemas=separate_input_output_schemas,
113
+ **extra,
114
+ )
115
+
116
+ def generate_and_include_versioned_routers(self, *routers: APIRouter) -> None:
117
+ root_router = APIRouter()
118
+ for router in routers:
119
+ root_router.include_router(router)
120
+ router_versions = generate_versioned_routers(
121
+ root_router,
122
+ versions=self.versions,
123
+ latest_schemas_package=self.latest_schemas_package,
124
+ )
125
+ for version, router in router_versions.items():
126
+ self.add_header_versioned_routers(router, header_value=version.isoformat())
cadwyn/codegen/_common.py CHANGED
@@ -6,7 +6,7 @@ from enum import Enum
6
6
  from functools import cache
7
7
  from pathlib import Path
8
8
  from types import ModuleType
9
- from typing import Any, Generic, Protocol, TypeAlias, TypeVar, cast
9
+ from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeAlias, TypeVar, cast
10
10
 
11
11
  from pydantic import BaseModel
12
12
  from typing_extensions import Self
@@ -14,9 +14,11 @@ from typing_extensions import Self
14
14
  from cadwyn._compat import PydanticFieldWrapper, model_fields
15
15
  from cadwyn._package_utils import IdentifierPythonPath
16
16
  from cadwyn.exceptions import CodeGenerationError
17
- from cadwyn.structure.versions import Version
18
17
 
19
- from ._asts import _ValidatorWrapper, get_validator_info_or_none
18
+ if TYPE_CHECKING:
19
+ from cadwyn.structure.versions import Version
20
+
21
+ from .._asts import _ValidatorWrapper, get_validator_info_or_none
20
22
 
21
23
  _FieldName: TypeAlias = str
22
24
  _CodegenPluginASTType = TypeVar("_CodegenPluginASTType", bound=ast.AST)
@@ -122,9 +124,9 @@ class _ModuleWrapper:
122
124
 
123
125
  @dataclasses.dataclass(slots=True, kw_only=True)
124
126
  class GlobalCodegenContext:
125
- current_version: Version
126
- latest_version: Version = dataclasses.field(init=False)
127
- versions: list[Version]
127
+ current_version: "Version"
128
+ latest_version: "Version" = dataclasses.field(init=False)
129
+ versions: "list[Version]"
128
130
  schemas: dict[IdentifierPythonPath, PydanticModelWrapper] = dataclasses.field(repr=False)
129
131
  enums: dict[IdentifierPythonPath, _EnumWrapper] = dataclasses.field(repr=False)
130
132
  modules: dict[IdentifierPythonPath, _ModuleWrapper] = dataclasses.field(repr=False)
cadwyn/codegen/_main.py CHANGED
@@ -10,9 +10,9 @@ from typing import Any
10
10
 
11
11
  import ast_comments
12
12
 
13
+ from cadwyn._asts import get_all_names_defined_at_toplevel_of_module, read_python_module
13
14
  from cadwyn._package_utils import IdentifierPythonPath, get_package_path_from_module, get_version_dir_path
14
15
  from cadwyn._utils import get_index_of_latest_schema_dir_in_module_python_path
15
- from cadwyn.codegen._asts import get_all_names_defined_at_toplevel_of_module, read_python_module
16
16
  from cadwyn.codegen._common import (
17
17
  CodegenContext,
18
18
  CodegenPlugin,
@@ -4,6 +4,7 @@ from typing import Annotated, Any, cast, get_args, get_origin
4
4
 
5
5
  from typing_extensions import assert_never
6
6
 
7
+ from cadwyn._asts import add_keyword_to_call, delete_keyword_from_call, get_fancy_repr
7
8
  from cadwyn._compat import (
8
9
  PYDANTIC_V2,
9
10
  FieldInfo,
@@ -13,7 +14,6 @@ from cadwyn._compat import (
13
14
  )
14
15
  from cadwyn._package_utils import IdentifierPythonPath, get_cls_pythonpath
15
16
  from cadwyn._utils import Sentinel
16
- from cadwyn.codegen._asts import add_keyword_to_call, delete_keyword_from_call, get_fancy_repr
17
17
  from cadwyn.codegen._common import GlobalCodegenContext, PydanticModelWrapper, _EnumWrapper
18
18
  from cadwyn.exceptions import InvalidGenerationInstructionError
19
19
  from cadwyn.structure.enums import AlterEnumSubInstruction, EnumDidntHaveMembersInstruction, EnumHadMembersInstruction
@@ -2,11 +2,11 @@ import ast
2
2
  import copy
3
3
  from typing import Any
4
4
 
5
- from cadwyn._package_utils import IdentifierPythonPath, get_absolute_python_path_of_import
6
- from cadwyn.codegen._asts import (
5
+ from cadwyn._asts import (
7
6
  get_fancy_repr,
8
7
  pop_docstring_from_cls_body,
9
8
  )
9
+ from cadwyn._package_utils import IdentifierPythonPath, get_absolute_python_path_of_import
10
10
  from cadwyn.codegen._common import CodegenContext, PydanticModelWrapper, _EnumWrapper
11
11
 
12
12
 
cadwyn/exceptions.py CHANGED
@@ -1,3 +1,7 @@
1
+ import json
2
+ from datetime import date
3
+ from typing import Any
4
+
1
5
  from fastapi.routing import APIRoute
2
6
 
3
7
 
@@ -5,6 +9,19 @@ class CadwynError(Exception):
5
9
  pass
6
10
 
7
11
 
12
+ class CadwynLatestRequestValidationError(CadwynError):
13
+ def __init__(self, errors: list[Any], body: Any, version: date) -> None:
14
+ self.errors = errors
15
+ self.body = body
16
+ self.version = version
17
+ super().__init__(
18
+ f"We failed to migrate the request with version={self.version!s}. "
19
+ "This means that there is some error in your migrations or schema structure that makes it impossible "
20
+ "to migrate the request of that version to latest.\n"
21
+ f"body={self.body}\n\nerrors={json.dumps(self.errors, indent=4, ensure_ascii=False)}"
22
+ )
23
+
24
+
8
25
  class LintingError(CadwynError):
9
26
  pass
10
27
 
cadwyn/main.py CHANGED
@@ -1,116 +1,11 @@
1
- from collections.abc import Callable, Coroutine, Sequence
2
- from types import ModuleType
3
- from typing import Any
1
+ from warnings import warn
4
2
 
5
- from fastapi import APIRouter, routing
6
- from fastapi.datastructures import Default
7
- from fastapi.params import Depends
8
- from fastapi.utils import generate_unique_id
9
- from starlette.middleware import Middleware
10
- from starlette.requests import Request
11
- from starlette.responses import JSONResponse, Response
12
- from starlette.routing import BaseRoute
13
- from starlette.types import Lifespan
14
- from typing_extensions import Self
15
- from verselect import HeaderRoutingFastAPI
3
+ from .applications import Cadwyn
16
4
 
17
- from cadwyn.routing import generate_versioned_routers
18
- from cadwyn.structure import VersionBundle
5
+ warn(
6
+ "'cadwyn.main' module is deprecated. Please use 'cadwyn.applications' instead.",
7
+ DeprecationWarning,
8
+ stacklevel=2,
9
+ )
19
10
 
20
-
21
- class Cadwyn(HeaderRoutingFastAPI):
22
- def __init__(
23
- self,
24
- *,
25
- versions: VersionBundle,
26
- latest_schemas_package: ModuleType,
27
- api_version_header_name: str = "x-api-version",
28
- debug: bool = False,
29
- title: str = "FastAPI",
30
- summary: str | None = None,
31
- description: str = "",
32
- version: str = "0.1.0",
33
- openapi_url: str | None = "/openapi.json",
34
- openapi_tags: list[dict[str, Any]] | None = None,
35
- servers: list[dict[str, str | Any]] | None = None,
36
- dependencies: Sequence[Depends] | None = None,
37
- default_response_class: type[Response] = Default(JSONResponse), # noqa: B008
38
- redirect_slashes: bool = True,
39
- docs_url: str | None = "/docs",
40
- redoc_url: None = None,
41
- swagger_ui_oauth2_redirect_url: str | None = "/docs/oauth2-redirect",
42
- swagger_ui_init_oauth: dict[str, Any] | None = None,
43
- middleware: Sequence[Middleware] | None = None,
44
- exception_handlers: dict[int | type[Exception], Callable[[Request, Any], Coroutine[Any, Any, Response]]]
45
- | None = None,
46
- on_startup: Sequence[Callable[[], Any]] | None = None,
47
- on_shutdown: Sequence[Callable[[], Any]] | None = None,
48
- lifespan: Lifespan[Self] | None = None,
49
- terms_of_service: str | None = None,
50
- contact: dict[str, str | Any] | None = None,
51
- license_info: dict[str, str | Any] | None = None,
52
- openapi_prefix: str = "",
53
- root_path: str = "",
54
- root_path_in_servers: bool = True,
55
- responses: dict[int | str, dict[str, Any]] | None = None,
56
- callbacks: list[BaseRoute] | None = None,
57
- webhooks: APIRouter | None = None,
58
- deprecated: bool | None = None,
59
- include_in_schema: bool = True,
60
- swagger_ui_parameters: dict[str, Any] | None = None,
61
- generate_unique_id_function: Callable[[routing.APIRoute], str] = Default(generate_unique_id), # noqa: B008
62
- separate_input_output_schemas: bool = True,
63
- **extra: Any,
64
- ) -> None:
65
- super().__init__(
66
- api_version_header_name=api_version_header_name,
67
- api_version_var=versions.api_version_var,
68
- debug=debug,
69
- title=title,
70
- summary=summary,
71
- description=description,
72
- version=version,
73
- openapi_url=openapi_url,
74
- openapi_tags=openapi_tags,
75
- servers=servers,
76
- dependencies=dependencies,
77
- default_response_class=default_response_class,
78
- redirect_slashes=redirect_slashes,
79
- docs_url=docs_url,
80
- swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url,
81
- swagger_ui_init_oauth=swagger_ui_init_oauth,
82
- middleware=middleware,
83
- exception_handlers=exception_handlers,
84
- on_startup=on_startup,
85
- on_shutdown=on_shutdown,
86
- lifespan=lifespan,
87
- terms_of_service=terms_of_service,
88
- contact=contact,
89
- license_info=license_info,
90
- openapi_prefix=openapi_prefix,
91
- root_path=root_path,
92
- root_path_in_servers=root_path_in_servers,
93
- responses=responses,
94
- callbacks=callbacks,
95
- webhooks=webhooks,
96
- deprecated=deprecated,
97
- include_in_schema=include_in_schema,
98
- swagger_ui_parameters=swagger_ui_parameters,
99
- generate_unique_id_function=generate_unique_id_function,
100
- separate_input_output_schemas=separate_input_output_schemas,
101
- **extra,
102
- )
103
- self.versions = versions
104
- self.latest_schemas_package = latest_schemas_package
105
-
106
- def generate_and_include_versioned_routers(self, *routers: APIRouter) -> None:
107
- root_router = APIRouter()
108
- for router in routers:
109
- root_router.include_router(router)
110
- router_versions = generate_versioned_routers(
111
- root_router,
112
- versions=self.versions,
113
- latest_schemas_package=self.latest_schemas_package,
114
- )
115
- for version, router in router_versions.items():
116
- self.add_header_versioned_routers(router, header_value=version.isoformat())
11
+ __all__ = ["Cadwyn"]
cadwyn/routing.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import functools
2
2
  import inspect
3
3
  import re
4
- import sys
5
4
  import typing
6
5
  import warnings
7
6
  from collections import defaultdict
@@ -17,7 +16,7 @@ from typing import (
17
16
  Generic,
18
17
  TypeAlias,
19
18
  TypeVar,
20
- _BaseGenericAlias, # pyright: ignore[reportGeneralTypeIssues]
19
+ _BaseGenericAlias, # pyright: ignore[reportAttributeAccessIssue]
21
20
  cast,
22
21
  final,
23
22
  get_args,
@@ -44,11 +43,10 @@ from starlette.routing import (
44
43
  from typing_extensions import Self, assert_never
45
44
 
46
45
  from cadwyn._compat import model_fields, rebuild_fastapi_body_param
47
- from cadwyn._package_utils import get_package_path_from_module, get_version_dir_path
48
- from cadwyn._utils import Sentinel, UnionType, get_another_version_of_module
46
+ from cadwyn._package_utils import get_version_dir_path
47
+ from cadwyn._utils import Sentinel, UnionType, get_another_version_of_cls
49
48
  from cadwyn.exceptions import (
50
49
  CadwynError,
51
- ModuleIsNotVersionedError,
52
50
  RouteAlreadyExistsError,
53
51
  RouterGenerationError,
54
52
  RouterPathParamsModifiedError,
@@ -381,8 +379,8 @@ def _validate_no_repetitions_in_routes(routes: list[fastapi.routing.APIRoute]):
381
379
  @final
382
380
  class _AnnotationTransformer:
383
381
  __slots__ = (
382
+ "versions",
384
383
  "latest_schemas_package",
385
- "version_dirs",
386
384
  "template_version_dir",
387
385
  "latest_version_dir",
388
386
  "change_versions_of_a_non_container_annotation",
@@ -398,16 +396,14 @@ class _AnnotationTransformer:
398
396
  'The name of the latest schemas module must be "latest". '
399
397
  f'Received "{latest_schemas_package.__name__}" instead.',
400
398
  )
399
+ self.versions = versions
400
+ self.versions.latest_schemas_package = latest_schemas_package
401
401
  self.latest_schemas_package = latest_schemas_package
402
- self.version_dirs = frozenset(
403
- [get_package_path_from_module(latest_schemas_package)]
404
- + [get_version_dir_path(latest_schemas_package, version.value) for version in versions],
405
- )
406
402
  # Okay, the naming is confusing, I know. Essentially template_version_dir is a dir of
407
403
  # latest_schemas_package while latest_version_dir is a version equivalent to latest but
408
404
  # with its own directory. Pick a better naming and make a PR, I am at your mercy.
409
- self.template_version_dir = min(self.version_dirs) # "latest" < "v0000_00_00"
410
- self.latest_version_dir = max(self.version_dirs) # "v2005_11_11" > "v2000_11_11"
405
+ self.template_version_dir = min(versions.versioned_directories) # "latest" < "v0000_00_00"
406
+ self.latest_version_dir = max(versions.versioned_directories) # "v2005_11_11" > "v2000_11_11"
411
407
 
412
408
  # This cache is not here for speeding things up. It's for preventing the creation of copies of the same object
413
409
  # because such copies could produce weird behaviors at runtime, especially if you/fastapi do any comparisons.
@@ -450,15 +446,6 @@ class _AnnotationTransformer:
450
446
  self.migrate_route_to_version(callback, version_dir, ignore_response_model=ignore_response_model)
451
447
  _remake_endpoint_dependencies(route)
452
448
 
453
- def get_another_version_of_cls(self, cls_from_old_version: type[Any], new_version_dir: Path):
454
- # version_dir = /home/myuser/package/companies/v2021_01_01
455
- module_from_old_version = sys.modules[cls_from_old_version.__module__]
456
- try:
457
- module = get_another_version_of_module(module_from_old_version, new_version_dir, self.version_dirs)
458
- except ModuleIsNotVersionedError:
459
- return cls_from_old_version
460
- return getattr(module, cls_from_old_version.__name__)
461
-
462
449
  def _change_versions_of_a_non_container_annotation(self, annotation: Any, version_dir: Path) -> Any:
463
450
  if isinstance(annotation, _BaseGenericAlias | GenericAlias):
464
451
  return get_origin(annotation)[
@@ -470,7 +457,7 @@ class _AnnotationTransformer:
470
457
  use_cache=annotation.use_cache,
471
458
  )
472
459
  elif isinstance(annotation, UnionType):
473
- getitem = typing.Union.__getitem__ # pyright: ignore[reportGeneralTypeIssues]
460
+ getitem = typing.Union.__getitem__ # pyright: ignore[reportAttributeAccessIssue]
474
461
  return getitem(
475
462
  tuple(self._change_version_of_annotations(a, version_dir) for a in get_args(annotation)),
476
463
  )
@@ -526,7 +513,7 @@ class _AnnotationTransformer:
526
513
  )
527
514
  else:
528
515
  self._validate_source_file_is_located_in_template_dir(annotation, source_file)
529
- return self.get_another_version_of_cls(annotation, version_dir)
516
+ return get_another_version_of_cls(annotation, version_dir, self.versions.versioned_directories)
530
517
  else:
531
518
  return annotation
532
519
 
@@ -539,7 +526,7 @@ class _AnnotationTransformer:
539
526
  if (
540
527
  source_file.startswith(dir_with_versions)
541
528
  and not source_file.startswith(template_dir)
542
- and any(source_file.startswith(str(d)) for d in self.version_dirs)
529
+ and any(source_file.startswith(str(d)) for d in self.versions.versioned_directories)
543
530
  ):
544
531
  raise RouterGenerationError(
545
532
  f'"{annotation}" is not defined in "{self.template_version_dir}" even though it must be. '
@@ -734,7 +721,7 @@ def _copy_function(function: _T) -> _T:
734
721
  if inspect.iscoroutinefunction(function):
735
722
 
736
723
  @functools.wraps(function)
737
- async def annotation_modifying_decorator( # pyright: ignore[reportGeneralTypeIssues]
724
+ async def annotation_modifying_decorator( # pyright: ignore[reportRedeclaration]
738
725
  *args: Any,
739
726
  **kwargs: Any,
740
727
  ) -> Any:
@@ -750,7 +737,7 @@ def _copy_function(function: _T) -> _T:
750
737
  return function(*args, **kwargs)
751
738
 
752
739
  # Otherwise it will have the same signature as __wrapped__ due to how inspect module works
753
- annotation_modifying_decorator.__alt_wrapped__ = ( # pyright: ignore[reportGeneralTypeIssues]
740
+ annotation_modifying_decorator.__alt_wrapped__ = ( # pyright: ignore[reportAttributeAccessIssue]
754
741
  annotation_modifying_decorator.__wrapped__
755
742
  )
756
743
  del annotation_modifying_decorator.__wrapped__
cadwyn/structure/data.py CHANGED
@@ -136,7 +136,7 @@ def convert_request_to_next_version_for(
136
136
  transformer=transformer,
137
137
  )
138
138
 
139
- return decorator # pyright: ignore[reportGeneralTypeIssues]
139
+ return decorator # pyright: ignore[reportReturnType]
140
140
 
141
141
 
142
142
  ############
@@ -144,8 +144,10 @@ def convert_request_to_next_version_for(
144
144
  ############
145
145
 
146
146
 
147
+ @dataclass
147
148
  class _BaseAlterResponseInstruction(_AlterDataInstruction):
148
149
  _payload_arg_name = "response"
150
+ migrate_http_errors: bool
149
151
 
150
152
 
151
153
  @dataclass
@@ -160,12 +162,23 @@ class AlterResponseByPathInstruction(_BaseAlterResponseInstruction):
160
162
 
161
163
 
162
164
  @overload
163
- def convert_response_to_previous_version_for(schema: type, /) -> "type[staticmethod[_P, None]]":
165
+ def convert_response_to_previous_version_for(
166
+ schema: type,
167
+ /,
168
+ *,
169
+ migrate_http_errors: bool = False,
170
+ ) -> "type[staticmethod[_P, None]]":
164
171
  ...
165
172
 
166
173
 
167
174
  @overload
168
- def convert_response_to_previous_version_for(path: str, methods: list[str], /) -> "type[staticmethod[_P, None]]":
175
+ def convert_response_to_previous_version_for(
176
+ path: str,
177
+ methods: list[str],
178
+ /,
179
+ *,
180
+ migrate_http_errors: bool = False,
181
+ ) -> "type[staticmethod[_P, None]]":
169
182
  ...
170
183
 
171
184
 
@@ -173,6 +186,8 @@ def convert_response_to_previous_version_for(
173
186
  schema_or_path: type | str,
174
187
  methods: list[str] | None = None,
175
188
  /,
189
+ *,
190
+ migrate_http_errors: bool = False,
176
191
  ) -> "type[staticmethod[_P, None]]":
177
192
  _validate_decorator_args(schema_or_path, methods)
178
193
 
@@ -183,11 +198,16 @@ def convert_response_to_previous_version_for(
183
198
  path=schema_or_path,
184
199
  methods=set(cast(list, methods)),
185
200
  transformer=transformer,
201
+ migrate_http_errors=migrate_http_errors,
186
202
  )
187
203
  else:
188
- return AlterResponseBySchemaInstruction(schema=schema_or_path, transformer=transformer)
204
+ return AlterResponseBySchemaInstruction(
205
+ schema=schema_or_path,
206
+ transformer=transformer,
207
+ migrate_http_errors=migrate_http_errors,
208
+ )
189
209
 
190
- return decorator # pyright: ignore[reportGeneralTypeIssues]
210
+ return decorator # pyright: ignore[reportReturnType]
191
211
 
192
212
 
193
213
  def _validate_decorator_args(schema_or_path: type | str, methods: list[str] | None):
@@ -8,9 +8,9 @@ from typing import TYPE_CHECKING, Any, Literal
8
8
  from pydantic import BaseModel, Field
9
9
  from pydantic.fields import FieldInfo
10
10
 
11
+ from cadwyn._asts import _ValidatorWrapper, get_validator_info_or_none
11
12
  from cadwyn._compat import PYDANTIC_V2
12
13
  from cadwyn._utils import Sentinel
13
- from cadwyn.codegen._asts import _ValidatorWrapper, get_validator_info_or_none
14
14
  from cadwyn.exceptions import CadwynStructureError
15
15
 
16
16
  if TYPE_CHECKING:
@@ -227,7 +227,7 @@ class AlterFieldInstructionFactory:
227
227
  class ValidatorExistedInstruction:
228
228
  schema: type[BaseModel]
229
229
  validator: Callable[..., Any]
230
- validator_info: _ValidatorWrapper = field(init=False)
230
+ validator_info: "_ValidatorWrapper" = field(init=False)
231
231
 
232
232
  def __post_init__(self):
233
233
  source = textwrap.dedent(inspect.getsource(self.validator))
@@ -7,6 +7,7 @@ from collections.abc import Callable, Sequence
7
7
  from contextlib import AsyncExitStack
8
8
  from contextvars import ContextVar
9
9
  from enum import Enum
10
+ from pathlib import Path
10
11
  from types import ModuleType
11
12
  from typing import Any, ClassVar, ParamSpec, TypeAlias, TypeVar, cast
12
13
 
@@ -26,9 +27,14 @@ from starlette._utils import is_async_callable
26
27
  from typing_extensions import assert_never
27
28
 
28
29
  from cadwyn._compat import PYDANTIC_V2, ModelField, PydanticUndefined, model_dump
29
- from cadwyn._package_utils import IdentifierPythonPath, get_cls_pythonpath
30
- from cadwyn._utils import classproperty
31
- from cadwyn.exceptions import CadwynError, CadwynStructureError
30
+ from cadwyn._package_utils import (
31
+ IdentifierPythonPath,
32
+ get_cls_pythonpath,
33
+ get_package_path_from_module,
34
+ get_version_dir_path,
35
+ )
36
+ from cadwyn._utils import classproperty, get_another_version_of_cls
37
+ from cadwyn.exceptions import CadwynError, CadwynLatestRequestValidationError, CadwynStructureError
32
38
 
33
39
  from .._utils import Sentinel
34
40
  from .common import Endpoint, VersionDate, VersionedModel
@@ -39,6 +45,7 @@ from .data import (
39
45
  AlterResponseBySchemaInstruction,
40
46
  RequestInfo,
41
47
  ResponseInfo,
48
+ _BaseAlterResponseInstruction,
42
49
  )
43
50
  from .endpoints import AlterEndpointSubInstruction
44
51
  from .enums import AlterEnumSubInstruction
@@ -223,10 +230,13 @@ class VersionBundle:
223
230
  /,
224
231
  *other_versions: Version,
225
232
  api_version_var: APIVersionVarType | None = None,
233
+ latest_schemas_package: ModuleType | None = None,
226
234
  ) -> None:
227
235
  super().__init__()
228
236
 
237
+ self.latest_schemas_package: ModuleType | None = latest_schemas_package
229
238
  self.versions = (latest_version, *other_versions)
239
+ self.version_dates = tuple(version.value for version in self.versions)
230
240
  if api_version_var is None:
231
241
  api_version_var = ContextVar("cadwyn_api_version")
232
242
  self.api_version_var = api_version_var
@@ -291,6 +301,47 @@ class VersionBundle:
291
301
  for instruction in version_change.alter_module_instructions
292
302
  }
293
303
 
304
+ @functools.cached_property
305
+ def versioned_directories(self) -> tuple[Path, ...]:
306
+ if self.latest_schemas_package is None:
307
+ raise CadwynError(
308
+ f"You cannot call 'VersionBundle.{self.migrate_response_body.__name__}' because it has no access to "
309
+ "'latest_schemas_package'. It likely means that it was not attached "
310
+ "to any Cadwyn application which attaches 'latest_schemas_package' during initialization."
311
+ )
312
+ return tuple(
313
+ [get_package_path_from_module(self.latest_schemas_package)]
314
+ + [get_version_dir_path(self.latest_schemas_package, version.value) for version in self]
315
+ )
316
+
317
+ def migrate_response_body(self, latest_response_model: type[BaseModel], *, latest_body: Any, version: VersionDate):
318
+ """Convert the data to a specific version by applying all version changes from latest until that version
319
+ in reverse order and wrapping the result in the correct version of latest_response_model.
320
+ """
321
+ response = ResponseInfo(FastapiResponse(status_code=200), body=latest_body)
322
+ migrated_response = self._migrate_response(
323
+ response,
324
+ current_version=version,
325
+ latest_response_model=latest_response_model,
326
+ path="\0\0\0",
327
+ method="GET",
328
+ )
329
+
330
+ version = self._get_closest_lesser_version(version)
331
+ # + 1 comes from latest also being in the versioned_directories list
332
+ version_dir = self.versioned_directories[self.version_dates.index(version) + 1]
333
+
334
+ versioned_response_model: type[BaseModel] = get_another_version_of_cls(
335
+ latest_response_model, version_dir, self.versioned_directories
336
+ )
337
+ return versioned_response_model.parse_obj(migrated_response.body)
338
+
339
+ def _get_closest_lesser_version(self, version: VersionDate):
340
+ for defined_version in self.version_dates:
341
+ if defined_version <= version:
342
+ return defined_version
343
+ raise CadwynError("You tried to migrate to version that is earlier than the first version which is prohibited.")
344
+
294
345
  @functools.cached_property
295
346
  def _version_changes_to_version_mapping(
296
347
  self,
@@ -340,7 +391,9 @@ class VersionBundle:
340
391
  **kwargs,
341
392
  )
342
393
  if errors:
343
- raise RequestValidationError(_normalize_errors(errors), body=request_info.body)
394
+ raise CadwynLatestRequestValidationError(
395
+ _normalize_errors(errors), body=request_info.body, version=current_version
396
+ )
344
397
  return dependencies
345
398
  raise NotImplementedError("This code should not be reachable. If it was reached -- it's a bug.")
346
399
 
@@ -348,7 +401,7 @@ class VersionBundle:
348
401
  self,
349
402
  response_info: ResponseInfo,
350
403
  current_version: VersionDate,
351
- latest_route: APIRoute,
404
+ latest_response_model: type[BaseModel],
352
405
  path: str,
353
406
  method: str,
354
407
  ) -> ResponseInfo:
@@ -367,15 +420,24 @@ class VersionBundle:
367
420
  if v.value <= current_version:
368
421
  break
369
422
  for version_change in v.version_changes:
423
+ migrations_to_apply: list[_BaseAlterResponseInstruction] = []
424
+
370
425
  if (
371
- latest_route.response_model
372
- and latest_route.response_model in version_change.alter_response_by_schema_instructions
426
+ latest_response_model
427
+ and latest_response_model in version_change.alter_response_by_schema_instructions
373
428
  ):
374
- version_change.alter_response_by_schema_instructions[latest_route.response_model](response_info)
429
+ migrations_to_apply.append(
430
+ version_change.alter_response_by_schema_instructions[latest_response_model]
431
+ )
432
+
375
433
  if path in version_change.alter_response_by_path_instructions:
376
434
  for instruction in version_change.alter_response_by_path_instructions[path]:
377
435
  if method in instruction.methods:
378
- instruction(response_info)
436
+ migrations_to_apply.append(instruction)
437
+
438
+ for migration in migrations_to_apply:
439
+ if response_info.status_code < 300 or migration.migrate_http_errors:
440
+ migration(response_info)
379
441
  return response_info
380
442
 
381
443
  # TODO (https://github.com/zmievsa/cadwyn/issues/113): Refactor this function and all functions it calls.
@@ -475,7 +537,7 @@ class VersionBundle:
475
537
 
476
538
  response_info = ResponseInfo(response_or_response_body, body)
477
539
  else:
478
- if fastapi_response_dependency.status_code is not None:
540
+ if fastapi_response_dependency.status_code is not None: # pyright: ignore[reportUnnecessaryComparison]
479
541
  status_code = fastapi_response_dependency.status_code
480
542
  elif route.status_code is not None:
481
543
  status_code = route.status_code
@@ -497,7 +559,7 @@ class VersionBundle:
497
559
  response_info = self._migrate_response(
498
560
  response_info,
499
561
  api_version,
500
- latest_route,
562
+ latest_route.response_model,
501
563
  route.path,
502
564
  method,
503
565
  )
@@ -564,7 +626,7 @@ class VersionBundle:
564
626
  body = raw_body
565
627
  else:
566
628
  body = model_dump(raw_body, by_alias=True, exclude_unset=True)
567
- if not PYDANTIC_V2 and raw_body.__custom_root_type__: # pyright: ignore[reportGeneralTypeIssues]
629
+ if not PYDANTIC_V2 and raw_body.__custom_root_type__: # pyright: ignore[reportAttributeAccessIssue]
568
630
  body = body["__root__"]
569
631
  else:
570
632
  # This is for requests without body or with complex body such as form or file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cadwyn
3
- Version: 3.4.5
3
+ Version: 3.6.0
4
4
  Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
5
5
  Home-page: https://github.com/zmievsa/cadwyn
6
6
  License: MIT
@@ -22,9 +22,6 @@ Classifier: Programming Language :: Python
22
22
  Classifier: Programming Language :: Python :: 3
23
23
  Classifier: Programming Language :: Python :: 3.10
24
24
  Classifier: Programming Language :: Python :: 3.11
25
- Classifier: Programming Language :: Python :: 3
26
- Classifier: Programming Language :: Python :: 3.10
27
- Classifier: Programming Language :: Python :: 3.11
28
25
  Classifier: Programming Language :: Python :: 3.12
29
26
  Classifier: Topic :: Internet
30
27
  Classifier: Topic :: Internet :: WWW/HTTP
@@ -38,7 +35,7 @@ Provides-Extra: cli
38
35
  Requires-Dist: better-ast-comments (>=1.2.1,<1.3.0)
39
36
  Requires-Dist: fastapi (>=0.96.1)
40
37
  Requires-Dist: pydantic (>=1.0.0)
41
- Requires-Dist: typer (>=0.7.0); extra == "cli"
38
+ Requires-Dist: typer (>=0.7.0) ; extra == "cli"
42
39
  Requires-Dist: typing-extensions
43
40
  Requires-Dist: verselect (>=0.0.6)
44
41
  Project-URL: Documentation, https://docs.cadwyn.dev
@@ -0,0 +1,35 @@
1
+ cadwyn/__init__.py,sha256=L5OVmOYlh5z3OYvwj6HidGBy8gvstCPMX5l4f4E-wr0,486
2
+ cadwyn/__main__.py,sha256=JUNmAhwn7tG1EeXI82QmFZE-fpjfAOv2kxFNDfxWbhQ,2851
3
+ cadwyn/_asts.py,sha256=jFdnbkDDu_10YhJemDUVMut_XoqwIvGMtCQpDeitCiM,10206
4
+ cadwyn/_compat.py,sha256=6QwtzbXn53mIhEFfEizmFjd-f894oLsM6ITxqq2rCpc,5408
5
+ cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
6
+ cadwyn/_utils.py,sha256=nBBE9PGo9MuHlCgbX8JPz9XcQxF7zIbVtKaBEsgfhGc,4861
7
+ cadwyn/applications.py,sha256=sFx3d7fOx7wGCcLIcF5IoY_QbPX0SooYYzvb4iWtpWg,5397
8
+ cadwyn/codegen/README.md,sha256=V2Kz2IOz1cTxrC-RnQ7YbWEVCIGYr3tR4IPCvepeq0M,1047
9
+ cadwyn/codegen/__init__.py,sha256=JgddDjxMTjSfVrMXHwNu1ODgdn2QfPWpccrRKquBV6k,355
10
+ cadwyn/codegen/_common.py,sha256=j-K0zMINmOKPhwsKjtfmrUrNzgEU4gM2EEFC7wvpCdU,5696
11
+ cadwyn/codegen/_main.py,sha256=3aKB2g08WJqu86rc0VYfh1UfeHfipd1-tGncjiDKOlA,9114
12
+ cadwyn/codegen/_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ cadwyn/codegen/_plugins/class_migrations.py,sha256=NS1FfNfrBL2fbg27-0xZOvFYDE6I2wxfdR5VdR38ytk,20151
14
+ cadwyn/codegen/_plugins/class_rebuilding.py,sha256=15SI1AAQgLgIkz176Gf86whaw0NWAYX8_2ogYHUeT1g,3695
15
+ cadwyn/codegen/_plugins/class_renaming.py,sha256=5ka2W1c18i4maNbEkEpELvGLEFbd8tthvQX3YA3Bu0A,1843
16
+ cadwyn/codegen/_plugins/import_auto_adding.py,sha256=00zGK99cT-bq2eXKDlYBR5-Z3uHLOGU7dbhB0YFFrt0,2613
17
+ cadwyn/codegen/_plugins/latest_version_aliasing.py,sha256=9MPW-FMOcjBZ7L05T0sxwSDCfFZHAn6xZV_E1KImbUA,3946
18
+ cadwyn/codegen/_plugins/module_migrations.py,sha256=TeWJk4Iu4SRQ9K2iI3v3sCs1110jrltKlPdfU9mXIsQ,722
19
+ cadwyn/exceptions.py,sha256=JUHFjxWl2XlnweVnSpq3mWFUNU0ocyXBBleMMQyCyBk,1442
20
+ cadwyn/main.py,sha256=kt2Vn7TIA4ZnD_xrgz57TOjUk-4zVP8SV8nuTZBEaaU,218
21
+ cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ cadwyn/routing.py,sha256=njVzrVOruO-WFI74f3JIfATivSiz1C5EJuWjjZyaseI,34937
23
+ cadwyn/structure/__init__.py,sha256=BjFPlQYCw8ds_4zxdCi2LimarUGqSzyTNmOdT-FkGms,661
24
+ cadwyn/structure/common.py,sha256=6Z4nI97XPWTCinn6np73m-rLPyYNrz2fWXKJlqjsiaQ,269
25
+ cadwyn/structure/data.py,sha256=u5-pJ48BRnPSt2JbH6AefHGddTEE5z7YBnyv0iGw3dQ,6133
26
+ cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY,5627
27
+ cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
28
+ cadwyn/structure/modules.py,sha256=1FK-lLm-zOTXEvn-QtyBH38aDRht5PDQiZrOPCsBlM4,1268
29
+ cadwyn/structure/schemas.py,sha256=5hExJEdvEFPK4Cv8G_Gh6E6ltiNMyId_UjGIJz3jz0o,8802
30
+ cadwyn/structure/versions.py,sha256=6M_saZPlzEQ0LNRQywhtrw-YPdjT0P18EEPTRg1dR8Y,32785
31
+ cadwyn-3.6.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
32
+ cadwyn-3.6.0.dist-info/METADATA,sha256=Z3Y3MtpC_AnXyqeU6lfkfzuye8_di4uiGuUPBrzNjBg,4115
33
+ cadwyn-3.6.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
34
+ cadwyn-3.6.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
35
+ cadwyn-3.6.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.3.2
2
+ Generator: poetry-core 1.8.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,34 +0,0 @@
1
- cadwyn/__init__.py,sha256=gVLVH3SSBGH0IQYGL5tbro4s0vk--9sAym0UvoG3s1w,478
2
- cadwyn/__main__.py,sha256=JUNmAhwn7tG1EeXI82QmFZE-fpjfAOv2kxFNDfxWbhQ,2851
3
- cadwyn/_compat.py,sha256=B0K-cj9bN7ytOIehOMjN9O9s0CE3lq9WAkV97pnvll8,5410
4
- cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
5
- cadwyn/_utils.py,sha256=JW3NCttM-Uj3PPW4CkYnCqROqWKOJWyHo1lcDMQillU,4274
6
- cadwyn/codegen/README.md,sha256=V2Kz2IOz1cTxrC-RnQ7YbWEVCIGYr3tR4IPCvepeq0M,1047
7
- cadwyn/codegen/__init__.py,sha256=JgddDjxMTjSfVrMXHwNu1ODgdn2QfPWpccrRKquBV6k,355
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
- cadwyn/codegen/_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- cadwyn/codegen/_plugins/class_migrations.py,sha256=dbmXMdfUGA2X4phsNdtRQQde4SuoOYtNB86XL05U1jY,20159
13
- cadwyn/codegen/_plugins/class_rebuilding.py,sha256=mJR297bqsLUfP4HW5_1GuHlpiYSQd851yHpG_ajjilg,3703
14
- cadwyn/codegen/_plugins/class_renaming.py,sha256=5ka2W1c18i4maNbEkEpELvGLEFbd8tthvQX3YA3Bu0A,1843
15
- cadwyn/codegen/_plugins/import_auto_adding.py,sha256=00zGK99cT-bq2eXKDlYBR5-Z3uHLOGU7dbhB0YFFrt0,2613
16
- cadwyn/codegen/_plugins/latest_version_aliasing.py,sha256=9MPW-FMOcjBZ7L05T0sxwSDCfFZHAn6xZV_E1KImbUA,3946
17
- cadwyn/codegen/_plugins/module_migrations.py,sha256=TeWJk4Iu4SRQ9K2iI3v3sCs1110jrltKlPdfU9mXIsQ,722
18
- cadwyn/exceptions.py,sha256=0nvauw2r5VOaTg9tQ9TZgLdQd3xiqqi6YD7e3-vOcVs,766
19
- cadwyn/main.py,sha256=_hC2Ke1uwtnjg2WueDXlg_QnzFgJbTcAlpHgqUBTmg4,4899
20
- cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- cadwyn/routing.py,sha256=85xqLfRuLtYwyoFl97ITHPNBSnoN7wP9a_mjyEDAJDc,35563
22
- cadwyn/structure/__init__.py,sha256=BjFPlQYCw8ds_4zxdCi2LimarUGqSzyTNmOdT-FkGms,661
23
- cadwyn/structure/common.py,sha256=6Z4nI97XPWTCinn6np73m-rLPyYNrz2fWXKJlqjsiaQ,269
24
- cadwyn/structure/data.py,sha256=cCaclB67mKlgRiydPFijyfLdng4qyqnY_hP8ApS5pT4,5781
25
- cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY,5627
26
- cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
27
- cadwyn/structure/modules.py,sha256=1FK-lLm-zOTXEvn-QtyBH38aDRht5PDQiZrOPCsBlM4,1268
28
- cadwyn/structure/schemas.py,sha256=LIKwDuzorVC9AHg4EN-UYdI133lCk_2MkBTdiyAr-EQ,8808
29
- cadwyn/structure/versions.py,sha256=QQqdYr4Cr7tjxwUKFlrxYeFXNNVugW0xI7tFEYLBOsI,29744
30
- cadwyn-3.4.5.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
31
- cadwyn-3.4.5.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
32
- cadwyn-3.4.5.dist-info/WHEEL,sha256=vxFmldFsRN_Hx10GDvsdv1wroKq8r5Lzvjp6GZ4OO8c,88
33
- cadwyn-3.4.5.dist-info/METADATA,sha256=rN1QWmGL8Qv8IRwIijcMeZ4v-VnEDe1sueIcndfACDs,4264
34
- cadwyn-3.4.5.dist-info/RECORD,,