cadwyn 3.12.0__py3-none-any.whl → 3.13.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/__main__.py +1 -2
- cadwyn/_asts.py +3 -2
- cadwyn/_compat.py +9 -2
- cadwyn/codegen/_main.py +2 -4
- cadwyn/exceptions.py +12 -0
- cadwyn/route_generation.py +117 -52
- cadwyn/routing.py +4 -5
- cadwyn/structure/data.py +17 -17
- cadwyn/structure/endpoints.py +7 -3
- cadwyn/structure/versions.py +48 -35
- {cadwyn-3.12.0.dist-info → cadwyn-3.13.0.dist-info}/METADATA +2 -1
- {cadwyn-3.12.0.dist-info → cadwyn-3.13.0.dist-info}/RECORD +15 -15
- {cadwyn-3.12.0.dist-info → cadwyn-3.13.0.dist-info}/LICENSE +0 -0
- {cadwyn-3.12.0.dist-info → cadwyn-3.13.0.dist-info}/WHEEL +0 -0
- {cadwyn-3.12.0.dist-info → cadwyn-3.13.0.dist-info}/entry_points.txt +0 -0
cadwyn/__main__.py
CHANGED
|
@@ -115,8 +115,7 @@ def _get_version_bundle(possibly_version_bundle: Any) -> VersionBundle:
|
|
|
115
115
|
@app.callback()
|
|
116
116
|
def main(
|
|
117
117
|
version: bool = typer.Option(None, "-V", "--version", callback=version_callback, is_eager=True),
|
|
118
|
-
):
|
|
119
|
-
...
|
|
118
|
+
): ...
|
|
120
119
|
|
|
121
120
|
|
|
122
121
|
if __name__ == "__main__":
|
cadwyn/_asts.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import ( # noqa: UP035
|
|
|
10
10
|
TYPE_CHECKING,
|
|
11
11
|
Any,
|
|
12
12
|
List,
|
|
13
|
+
cast,
|
|
13
14
|
get_args,
|
|
14
15
|
get_origin,
|
|
15
16
|
)
|
|
@@ -32,7 +33,7 @@ _RE_CAMEL_TO_SNAKE = re.compile(r"(?<!^)(?=[A-Z])")
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
# A parent type of typing._GenericAlias
|
|
35
|
-
_BaseGenericAlias = type(List[int]).mro()[1] # noqa: UP006
|
|
36
|
+
_BaseGenericAlias = cast(type, type(List[int])).mro()[1] # noqa: UP006
|
|
36
37
|
|
|
37
38
|
# type(list[int]) and type(List[int]) are different which is why we have to do this.
|
|
38
39
|
# Please note that this problem is much wider than just lists which is why we use typing._BaseGenericAlias
|
|
@@ -134,7 +135,7 @@ def transform_auto(_: auto) -> Any:
|
|
|
134
135
|
return PlainRepr("auto()")
|
|
135
136
|
|
|
136
137
|
|
|
137
|
-
def transform_union(value: UnionType) -> Any:
|
|
138
|
+
def transform_union(value: UnionType) -> Any:
|
|
138
139
|
return "typing.Union[" + (", ".join(get_fancy_repr(a) for a in get_args(value))) + "]"
|
|
139
140
|
|
|
140
141
|
|
cadwyn/_compat.py
CHANGED
|
@@ -61,7 +61,7 @@ class PydanticFieldWrapper:
|
|
|
61
61
|
|
|
62
62
|
annotation: Any
|
|
63
63
|
|
|
64
|
-
init_model_field: dataclasses.InitVar[ModelField]
|
|
64
|
+
init_model_field: dataclasses.InitVar[ModelField]
|
|
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):
|
|
72
|
+
def __post_init__(self, init_model_field: ModelField):
|
|
73
73
|
if isinstance(init_model_field, FieldInfo):
|
|
74
74
|
self.field_info = init_model_field
|
|
75
75
|
else:
|
|
@@ -111,6 +111,13 @@ class PydanticFieldWrapper:
|
|
|
111
111
|
return attributes | extras
|
|
112
112
|
|
|
113
113
|
|
|
114
|
+
def get_annotation_from_model_field(model: ModelField) -> Any:
|
|
115
|
+
if PYDANTIC_V2:
|
|
116
|
+
return model.field_info.annotation
|
|
117
|
+
else:
|
|
118
|
+
return model.annotation
|
|
119
|
+
|
|
120
|
+
|
|
114
121
|
def model_fields(model: type[BaseModel]) -> dict[str, FieldInfo]:
|
|
115
122
|
if PYDANTIC_V2:
|
|
116
123
|
return model.model_fields
|
cadwyn/codegen/_main.py
CHANGED
|
@@ -53,8 +53,7 @@ def generate_code_for_versioned_packages(
|
|
|
53
53
|
codegen_plugins: Sequence[CodegenPlugin] = DEFAULT_CODEGEN_PLUGINS,
|
|
54
54
|
migration_plugins: Sequence[MigrationPlugin] = DEFAULT_CODEGEN_MIGRATION_PLUGINS,
|
|
55
55
|
extra_context: dict[str, Any] | None = None,
|
|
56
|
-
):
|
|
57
|
-
...
|
|
56
|
+
): ...
|
|
58
57
|
|
|
59
58
|
|
|
60
59
|
@overload
|
|
@@ -70,8 +69,7 @@ def generate_code_for_versioned_packages(
|
|
|
70
69
|
codegen_plugins: Sequence[CodegenPlugin] = DEFAULT_CODEGEN_PLUGINS,
|
|
71
70
|
migration_plugins: Sequence[MigrationPlugin] = DEFAULT_CODEGEN_MIGRATION_PLUGINS,
|
|
72
71
|
extra_context: dict[str, Any] | None = None,
|
|
73
|
-
):
|
|
74
|
-
...
|
|
72
|
+
): ...
|
|
75
73
|
|
|
76
74
|
|
|
77
75
|
def generate_code_for_versioned_packages(
|
cadwyn/exceptions.py
CHANGED
|
@@ -46,6 +46,18 @@ class RouterPathParamsModifiedError(RouterGenerationError):
|
|
|
46
46
|
pass
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class RouteResponseBySchemaConverterDoesNotApplyToAnythingError(RouterGenerationError):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RouteRequestBySchemaConverterDoesNotApplyToAnythingError(RouterGenerationError):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RouteByPathConverterDoesNotApplyToAnythingError(RouterGenerationError):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
49
61
|
class RouteAlreadyExistsError(RouterGenerationError):
|
|
50
62
|
def __init__(self, *routes: APIRoute):
|
|
51
63
|
self.routes = routes
|
cadwyn/route_generation.py
CHANGED
|
@@ -15,7 +15,6 @@ from typing import (
|
|
|
15
15
|
Annotated,
|
|
16
16
|
Any,
|
|
17
17
|
Generic,
|
|
18
|
-
TypeAlias,
|
|
19
18
|
TypeVar,
|
|
20
19
|
_BaseGenericAlias, # pyright: ignore[reportAttributeAccessIssue]
|
|
21
20
|
cast,
|
|
@@ -29,6 +28,7 @@ import fastapi.params
|
|
|
29
28
|
import fastapi.routing
|
|
30
29
|
import fastapi.security.base
|
|
31
30
|
import fastapi.utils
|
|
31
|
+
from fastapi import APIRouter
|
|
32
32
|
from fastapi._compat import ModelField as FastAPIModelField
|
|
33
33
|
from fastapi._compat import create_body_model
|
|
34
34
|
from fastapi.dependencies.models import Dependant
|
|
@@ -39,6 +39,7 @@ from fastapi.dependencies.utils import (
|
|
|
39
39
|
)
|
|
40
40
|
from fastapi.params import Depends
|
|
41
41
|
from fastapi.routing import APIRoute
|
|
42
|
+
from issubclass import issubclass as lenient_issubclass
|
|
42
43
|
from pydantic import BaseModel
|
|
43
44
|
from starlette.routing import (
|
|
44
45
|
BaseRoute,
|
|
@@ -46,12 +47,15 @@ from starlette.routing import (
|
|
|
46
47
|
)
|
|
47
48
|
from typing_extensions import Self, assert_never, deprecated
|
|
48
49
|
|
|
49
|
-
from cadwyn._compat import model_fields, rebuild_fastapi_body_param
|
|
50
|
+
from cadwyn._compat import get_annotation_from_model_field, model_fields, rebuild_fastapi_body_param
|
|
50
51
|
from cadwyn._package_utils import get_version_dir_path
|
|
51
52
|
from cadwyn._utils import Sentinel, UnionType, get_another_version_of_cls
|
|
52
53
|
from cadwyn.exceptions import (
|
|
53
54
|
CadwynError,
|
|
54
55
|
RouteAlreadyExistsError,
|
|
56
|
+
RouteByPathConverterDoesNotApplyToAnythingError,
|
|
57
|
+
RouteRequestBySchemaConverterDoesNotApplyToAnythingError,
|
|
58
|
+
RouteResponseBySchemaConverterDoesNotApplyToAnythingError,
|
|
55
59
|
RouterGenerationError,
|
|
56
60
|
RouterPathParamsModifiedError,
|
|
57
61
|
)
|
|
@@ -68,8 +72,6 @@ _T = TypeVar("_T", bound=Callable[..., Any])
|
|
|
68
72
|
_R = TypeVar("_R", bound=fastapi.routing.APIRouter)
|
|
69
73
|
# This is a hack we do because we can't guarantee how the user will use the router.
|
|
70
74
|
_DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
|
|
71
|
-
_EndpointPath: TypeAlias = str
|
|
72
|
-
_EndpointMethod: TypeAlias = str
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
@dataclass(slots=True, frozen=True, eq=True)
|
|
@@ -78,13 +80,6 @@ class _EndpointInfo:
|
|
|
78
80
|
endpoint_methods: frozenset[str]
|
|
79
81
|
|
|
80
82
|
|
|
81
|
-
@dataclass(slots=True)
|
|
82
|
-
class _RouterInfo(Generic[_R]):
|
|
83
|
-
router: _R
|
|
84
|
-
routes_with_migrated_requests: dict[_EndpointPath, set[_EndpointMethod]]
|
|
85
|
-
route_bodies_with_migrated_requests: set[type[BaseModel]]
|
|
86
|
-
|
|
87
|
-
|
|
88
83
|
@deprecated("It will soon be deleted. Use HeadVersion version changes instead.")
|
|
89
84
|
class InternalRepresentationOf:
|
|
90
85
|
def __class_getitem__(cls, original_schema: type, /) -> type[Self]:
|
|
@@ -95,8 +90,7 @@ class InternalRepresentationOf:
|
|
|
95
90
|
def generate_versioned_routers(
|
|
96
91
|
router: _R,
|
|
97
92
|
versions: VersionBundle,
|
|
98
|
-
) -> dict[VersionDate, _R]:
|
|
99
|
-
...
|
|
93
|
+
) -> dict[VersionDate, _R]: ...
|
|
100
94
|
|
|
101
95
|
|
|
102
96
|
@overload
|
|
@@ -105,8 +99,7 @@ def generate_versioned_routers(
|
|
|
105
99
|
router: _R,
|
|
106
100
|
versions: VersionBundle,
|
|
107
101
|
latest_schemas_package: ModuleType | None,
|
|
108
|
-
) -> dict[VersionDate, _R]:
|
|
109
|
-
...
|
|
102
|
+
) -> dict[VersionDate, _R]: ...
|
|
110
103
|
|
|
111
104
|
|
|
112
105
|
def generate_versioned_routers(
|
|
@@ -163,22 +156,15 @@ class _EndpointTransformer(Generic[_R]):
|
|
|
163
156
|
self.parent_router
|
|
164
157
|
)
|
|
165
158
|
router = deepcopy(self.parent_router)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
route_bodies_with_migrated_requests: set[type[BaseModel]] = set()
|
|
159
|
+
routers: dict[VersionDate, _R] = {}
|
|
160
|
+
|
|
169
161
|
for version in self.versions:
|
|
170
162
|
self.annotation_transformer.migrate_router_to_version(router, version)
|
|
171
163
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
route_bodies_with_migrated_requests,
|
|
176
|
-
)
|
|
164
|
+
self._validate_all_data_converters_are_applied(router, version)
|
|
165
|
+
|
|
166
|
+
routers[version.value] = router
|
|
177
167
|
# Applying changes for the next version
|
|
178
|
-
routes_with_migrated_requests = _get_migrated_routes_by_path(version)
|
|
179
|
-
route_bodies_with_migrated_requests = {
|
|
180
|
-
schema for change in version.version_changes for schema in change.alter_request_by_schema_instructions
|
|
181
|
-
}
|
|
182
168
|
router = deepcopy(router)
|
|
183
169
|
self._apply_endpoint_changes_to_router(router, version)
|
|
184
170
|
|
|
@@ -196,21 +182,21 @@ class _EndpointTransformer(Generic[_R]):
|
|
|
196
182
|
continue
|
|
197
183
|
_add_request_and_response_params(head_route)
|
|
198
184
|
copy_of_dependant = deepcopy(head_route.dependant)
|
|
199
|
-
|
|
200
|
-
if
|
|
185
|
+
|
|
186
|
+
if _route_has_a_simple_body_schema(head_route):
|
|
201
187
|
self._replace_internal_representation_with_the_versioned_schema(
|
|
202
188
|
copy_of_dependant,
|
|
203
189
|
schema_to_internal_request_body_representation,
|
|
204
190
|
)
|
|
205
191
|
|
|
206
|
-
for
|
|
207
|
-
older_route =
|
|
192
|
+
for older_router in list(routers.values()):
|
|
193
|
+
older_route = older_router.routes[route_index]
|
|
208
194
|
|
|
209
195
|
# We know they are APIRoutes because of the check at the very beginning of the top loop.
|
|
210
196
|
# I.e. Because head_route is an APIRoute, both routes are APIRoutes too
|
|
211
197
|
older_route = cast(APIRoute, older_route)
|
|
212
198
|
# Wait.. Why do we need this code again?
|
|
213
|
-
if older_route.body_field is not None and
|
|
199
|
+
if older_route.body_field is not None and _route_has_a_simple_body_schema(older_route):
|
|
214
200
|
template_older_body_model = self.annotation_transformer._change_version_of_annotations(
|
|
215
201
|
older_route.body_field.type_,
|
|
216
202
|
self.annotation_transformer.head_version_dir,
|
|
@@ -226,13 +212,99 @@ class _EndpointTransformer(Generic[_R]):
|
|
|
226
212
|
copy_of_dependant,
|
|
227
213
|
self.versions,
|
|
228
214
|
)
|
|
229
|
-
for _,
|
|
230
|
-
|
|
215
|
+
for _, router in routers.items():
|
|
216
|
+
router.routes = [
|
|
231
217
|
route
|
|
232
|
-
for route in
|
|
218
|
+
for route in router.routes
|
|
233
219
|
if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
|
|
234
220
|
]
|
|
235
|
-
return
|
|
221
|
+
return routers
|
|
222
|
+
|
|
223
|
+
def _validate_all_data_converters_are_applied(self, router: APIRouter, version: Version):
|
|
224
|
+
path_to_route_methods_mapping, head_response_models, head_request_bodies = self._extract_all_routes_identifiers(
|
|
225
|
+
router
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
for version_change in version.version_changes:
|
|
229
|
+
for by_path_converters in [
|
|
230
|
+
*version_change.alter_response_by_path_instructions.values(),
|
|
231
|
+
*version_change.alter_request_by_path_instructions.values(),
|
|
232
|
+
]:
|
|
233
|
+
for by_path_converter in by_path_converters:
|
|
234
|
+
missing_methods = by_path_converter.methods.difference(
|
|
235
|
+
path_to_route_methods_mapping[by_path_converter.path]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if missing_methods:
|
|
239
|
+
raise RouteByPathConverterDoesNotApplyToAnythingError(
|
|
240
|
+
f"{by_path_converter.repr_name} "
|
|
241
|
+
f'"{version_change.__name__}.{by_path_converter.transformer.__name__}" '
|
|
242
|
+
f"failed to find routes with the following methods: {list(missing_methods)}. "
|
|
243
|
+
f"This means that you are trying to apply this converter to non-existing endpoint(s). "
|
|
244
|
+
"Please, check whether the path and methods are correct. (hint: path must include "
|
|
245
|
+
"all path variables and have a name that was used in the version that this "
|
|
246
|
+
"VersionChange resides in)"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
for by_schema_converters in version_change.alter_request_by_schema_instructions.values():
|
|
250
|
+
for by_schema_converter in by_schema_converters:
|
|
251
|
+
missing_models = set(by_schema_converter.schemas) - head_request_bodies
|
|
252
|
+
if missing_models:
|
|
253
|
+
raise RouteRequestBySchemaConverterDoesNotApplyToAnythingError(
|
|
254
|
+
f"Request by body schema converter "
|
|
255
|
+
f'"{version_change.__name__}.{by_schema_converter.transformer.__name__}" '
|
|
256
|
+
f"failed to find routes with the following body schemas: "
|
|
257
|
+
f"{[m.__name__ for m in missing_models]}. "
|
|
258
|
+
f"This means that you are trying to apply this converter to non-existing endpoint(s). "
|
|
259
|
+
)
|
|
260
|
+
for by_schema_converters in version_change.alter_response_by_schema_instructions.values():
|
|
261
|
+
for by_schema_converter in by_schema_converters:
|
|
262
|
+
missing_models = set(by_schema_converter.schemas) - head_response_models
|
|
263
|
+
if missing_models:
|
|
264
|
+
raise RouteResponseBySchemaConverterDoesNotApplyToAnythingError(
|
|
265
|
+
f"Response by response model converter "
|
|
266
|
+
f'"{version_change.__name__}.{by_schema_converter.transformer.__name__}" '
|
|
267
|
+
f"failed to find routes with the following response models: "
|
|
268
|
+
f"{[m.__name__ for m in missing_models]}. "
|
|
269
|
+
f"This means that you are trying to apply this converter to non-existing endpoint(s). "
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def _extract_all_routes_identifiers(
|
|
273
|
+
self, router: APIRouter
|
|
274
|
+
) -> tuple[defaultdict[str, set[str]], set[Any], set[Any]]:
|
|
275
|
+
response_models: set[Any] = set()
|
|
276
|
+
request_bodies: set[Any] = set()
|
|
277
|
+
path_to_route_methods_mapping: dict[str, set[str]] = defaultdict(set)
|
|
278
|
+
|
|
279
|
+
for route in router.routes:
|
|
280
|
+
if isinstance(route, APIRoute):
|
|
281
|
+
if route.response_model is not None and lenient_issubclass(route.response_model, BaseModel):
|
|
282
|
+
# FIXME: This is going to fail on Pydantic 1
|
|
283
|
+
response_models.add(route.response_model)
|
|
284
|
+
# Not sure if it can ever be None when it's a simple schema. Eh, I would rather be safe than sorry
|
|
285
|
+
if _route_has_a_simple_body_schema(route) and route.body_field is not None:
|
|
286
|
+
annotation = get_annotation_from_model_field(route.body_field)
|
|
287
|
+
if lenient_issubclass(annotation, BaseModel):
|
|
288
|
+
# FIXME: This is going to fail on Pydantic 1
|
|
289
|
+
request_bodies.add(annotation)
|
|
290
|
+
path_to_route_methods_mapping[route.path] |= route.methods
|
|
291
|
+
|
|
292
|
+
head_response_models = {
|
|
293
|
+
self.annotation_transformer._change_version_of_annotations(
|
|
294
|
+
model,
|
|
295
|
+
self.versions.versioned_directories_with_head[0],
|
|
296
|
+
)
|
|
297
|
+
for model in response_models
|
|
298
|
+
}
|
|
299
|
+
head_request_bodies = {
|
|
300
|
+
self.annotation_transformer._change_version_of_annotations(
|
|
301
|
+
body,
|
|
302
|
+
self.versions.versioned_directories_with_head[0],
|
|
303
|
+
)
|
|
304
|
+
for body in request_bodies
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return path_to_route_methods_mapping, head_response_models, head_request_bodies
|
|
236
308
|
|
|
237
309
|
def _replace_internal_representation_with_the_versioned_schema(
|
|
238
310
|
self,
|
|
@@ -424,8 +496,8 @@ class _AnnotationTransformer:
|
|
|
424
496
|
self.versions = versions
|
|
425
497
|
self.versions.head_schemas_package = head_schemas_package
|
|
426
498
|
self.head_schemas_package = head_schemas_package
|
|
427
|
-
self.head_version_dir = min(versions.
|
|
428
|
-
self.latest_version_dir = max(versions.
|
|
499
|
+
self.head_version_dir = min(versions.versioned_directories_with_head) # "head" < "v0000_00_00"
|
|
500
|
+
self.latest_version_dir = max(versions.versioned_directories_with_head) # "v2005_11_11" > "v2000_11_11"
|
|
429
501
|
|
|
430
502
|
# This cache is not here for speeding things up. It's for preventing the creation of copies of the same object
|
|
431
503
|
# because such copies could produce weird behaviors at runtime, especially if you/fastapi do any comparisons.
|
|
@@ -539,7 +611,7 @@ class _AnnotationTransformer:
|
|
|
539
611
|
)
|
|
540
612
|
else:
|
|
541
613
|
self._validate_source_file_is_located_in_template_dir(annotation, source_file)
|
|
542
|
-
return get_another_version_of_cls(annotation, version_dir, self.versions.
|
|
614
|
+
return get_another_version_of_cls(annotation, version_dir, self.versions.versioned_directories_with_head)
|
|
543
615
|
else:
|
|
544
616
|
return annotation
|
|
545
617
|
|
|
@@ -552,7 +624,7 @@ class _AnnotationTransformer:
|
|
|
552
624
|
if (
|
|
553
625
|
source_file.startswith(dir_with_versions)
|
|
554
626
|
and not source_file.startswith(template_dir)
|
|
555
|
-
and any(source_file.startswith(str(d)) for d in self.versions.
|
|
627
|
+
and any(source_file.startswith(str(d)) for d in self.versions.versioned_directories_with_head)
|
|
556
628
|
):
|
|
557
629
|
raise RouterGenerationError(
|
|
558
630
|
f'"{annotation}" is not defined in "{self.head_version_dir}" even though it must be. '
|
|
@@ -727,18 +799,6 @@ def _get_route_from_func(
|
|
|
727
799
|
return None
|
|
728
800
|
|
|
729
801
|
|
|
730
|
-
def _get_migrated_routes_by_path(version: Version) -> dict[_EndpointPath, set[_EndpointMethod]]:
|
|
731
|
-
request_by_path_migration_instructions = [
|
|
732
|
-
version_change.alter_request_by_path_instructions for version_change in version.version_changes
|
|
733
|
-
]
|
|
734
|
-
migrated_routes = defaultdict(set)
|
|
735
|
-
for instruction_dict in request_by_path_migration_instructions:
|
|
736
|
-
for path, instruction_list in instruction_dict.items():
|
|
737
|
-
for instruction in instruction_list:
|
|
738
|
-
migrated_routes[path] |= instruction.methods
|
|
739
|
-
return migrated_routes
|
|
740
|
-
|
|
741
|
-
|
|
742
802
|
def _copy_function(function: _T) -> _T:
|
|
743
803
|
while hasattr(function, "__alt_wrapped__"):
|
|
744
804
|
function = function.__alt_wrapped__
|
|
@@ -770,3 +830,8 @@ def _copy_function(function: _T) -> _T:
|
|
|
770
830
|
del annotation_modifying_wrapper.__wrapped__
|
|
771
831
|
|
|
772
832
|
return cast(_T, annotation_modifying_wrapper)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def _route_has_a_simple_body_schema(route: APIRoute) -> bool:
|
|
836
|
+
# Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
|
|
837
|
+
return len(route.dependant.body_params) == 1
|
cadwyn/routing.py
CHANGED
|
@@ -67,15 +67,14 @@ class _RootHeaderAPIRouter(APIRouter):
|
|
|
67
67
|
if self.min_routes_version > request_header_value:
|
|
68
68
|
# then the request version is older that the oldest route we have
|
|
69
69
|
_logger.info(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
f"version {self.min_routes_version.isoformat()} ",
|
|
70
|
+
"Request version is older than the oldest version. No route can match this version",
|
|
71
|
+
extra={"oldest_version": self.min_routes_version.isoformat(), "request_version": request_version},
|
|
73
72
|
)
|
|
74
73
|
return []
|
|
75
74
|
version_chosen = self.find_closest_date_but_not_new(request_header_value)
|
|
76
75
|
_logger.info(
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
"Partial match. The endpoint with a lower version was selected for the API call",
|
|
77
|
+
extra={"version_chosen": version_chosen, "request_version": request_version},
|
|
79
78
|
)
|
|
80
79
|
return self.versioned_routers[version_chosen].routes
|
|
81
80
|
|
cadwyn/structure/data.py
CHANGED
|
@@ -8,6 +8,7 @@ from fastapi import Request, Response
|
|
|
8
8
|
from starlette.datastructures import MutableHeaders
|
|
9
9
|
|
|
10
10
|
from cadwyn._utils import same_definition_as_in
|
|
11
|
+
from cadwyn.structure.endpoints import _validate_that_strings_are_valid_http_methods
|
|
11
12
|
|
|
12
13
|
_P = ParamSpec("_P")
|
|
13
14
|
|
|
@@ -96,26 +97,25 @@ class _BaseAlterRequestInstruction(_AlterDataInstruction):
|
|
|
96
97
|
|
|
97
98
|
|
|
98
99
|
@dataclass
|
|
99
|
-
class
|
|
100
|
+
class _AlterRequestBySchemaInstruction(_BaseAlterRequestInstruction):
|
|
100
101
|
schemas: tuple[Any, ...]
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
@dataclass
|
|
104
|
-
class
|
|
105
|
+
class _AlterRequestByPathInstruction(_BaseAlterRequestInstruction):
|
|
105
106
|
path: str
|
|
106
107
|
methods: set[str]
|
|
108
|
+
repr_name = "Request by path converter"
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
@overload
|
|
110
112
|
def convert_request_to_next_version_for(
|
|
111
113
|
first_schema: type, /, *additional_schemas: type
|
|
112
|
-
) -> "type[staticmethod[_P, None]]":
|
|
113
|
-
...
|
|
114
|
+
) -> "type[staticmethod[_P, None]]": ...
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
@overload
|
|
117
|
-
def convert_request_to_next_version_for(path: str, methods: list[str], /) -> "type[staticmethod[_P, None]]":
|
|
118
|
-
...
|
|
118
|
+
def convert_request_to_next_version_for(path: str, methods: list[str], /) -> "type[staticmethod[_P, None]]": ...
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def convert_request_to_next_version_for(
|
|
@@ -128,7 +128,7 @@ def convert_request_to_next_version_for(
|
|
|
128
128
|
|
|
129
129
|
def decorator(transformer: Callable[[RequestInfo], None]) -> Any:
|
|
130
130
|
if isinstance(schema_or_path, str):
|
|
131
|
-
return
|
|
131
|
+
return _AlterRequestByPathInstruction(
|
|
132
132
|
path=schema_or_path,
|
|
133
133
|
methods=set(cast(list, methods_or_second_schema)),
|
|
134
134
|
transformer=transformer,
|
|
@@ -138,7 +138,7 @@ def convert_request_to_next_version_for(
|
|
|
138
138
|
schemas = (schema_or_path,)
|
|
139
139
|
else:
|
|
140
140
|
schemas = (schema_or_path, methods_or_second_schema, *additional_schemas)
|
|
141
|
-
return
|
|
141
|
+
return _AlterRequestBySchemaInstruction(
|
|
142
142
|
schemas=schemas,
|
|
143
143
|
transformer=transformer,
|
|
144
144
|
)
|
|
@@ -158,14 +158,15 @@ class _BaseAlterResponseInstruction(_AlterDataInstruction):
|
|
|
158
158
|
|
|
159
159
|
|
|
160
160
|
@dataclass
|
|
161
|
-
class
|
|
161
|
+
class _AlterResponseBySchemaInstruction(_BaseAlterResponseInstruction):
|
|
162
162
|
schemas: tuple[Any, ...]
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
@dataclass
|
|
166
|
-
class
|
|
166
|
+
class _AlterResponseByPathInstruction(_BaseAlterResponseInstruction):
|
|
167
167
|
path: str
|
|
168
168
|
methods: set[str]
|
|
169
|
+
repr_name = "Response by path converter"
|
|
169
170
|
|
|
170
171
|
|
|
171
172
|
@overload
|
|
@@ -174,8 +175,7 @@ def convert_response_to_previous_version_for(
|
|
|
174
175
|
/,
|
|
175
176
|
*schemas: type,
|
|
176
177
|
migrate_http_errors: bool = False,
|
|
177
|
-
) -> "type[staticmethod[_P, None]]":
|
|
178
|
-
...
|
|
178
|
+
) -> "type[staticmethod[_P, None]]": ...
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
@overload
|
|
@@ -185,8 +185,7 @@ def convert_response_to_previous_version_for(
|
|
|
185
185
|
/,
|
|
186
186
|
*,
|
|
187
187
|
migrate_http_errors: bool = False,
|
|
188
|
-
) -> "type[staticmethod[_P, None]]":
|
|
189
|
-
...
|
|
188
|
+
) -> "type[staticmethod[_P, None]]": ...
|
|
190
189
|
|
|
191
190
|
|
|
192
191
|
def convert_response_to_previous_version_for(
|
|
@@ -201,7 +200,7 @@ def convert_response_to_previous_version_for(
|
|
|
201
200
|
def decorator(transformer: Callable[[ResponseInfo], None]) -> Any:
|
|
202
201
|
if isinstance(schema_or_path, str):
|
|
203
202
|
# The validation above checks that methods is not None
|
|
204
|
-
return
|
|
203
|
+
return _AlterResponseByPathInstruction(
|
|
205
204
|
path=schema_or_path,
|
|
206
205
|
methods=set(cast(list, methods_or_second_schema)),
|
|
207
206
|
transformer=transformer,
|
|
@@ -212,7 +211,7 @@ def convert_response_to_previous_version_for(
|
|
|
212
211
|
schemas = (schema_or_path,)
|
|
213
212
|
else:
|
|
214
213
|
schemas = (schema_or_path, methods_or_second_schema, *additional_schemas)
|
|
215
|
-
return
|
|
214
|
+
return _AlterResponseBySchemaInstruction(
|
|
216
215
|
schemas=schemas,
|
|
217
216
|
transformer=transformer,
|
|
218
217
|
migrate_http_errors=migrate_http_errors,
|
|
@@ -223,10 +222,11 @@ def convert_response_to_previous_version_for(
|
|
|
223
222
|
|
|
224
223
|
def _validate_decorator_args(
|
|
225
224
|
schema_or_path: type | str, methods_or_second_schema: list[str] | type | None, additional_schemas: tuple[type, ...]
|
|
226
|
-
):
|
|
225
|
+
) -> None:
|
|
227
226
|
if isinstance(schema_or_path, str):
|
|
228
227
|
if not isinstance(methods_or_second_schema, list):
|
|
229
228
|
raise TypeError("If path was provided as a first argument, methods must be provided as a second argument")
|
|
229
|
+
_validate_that_strings_are_valid_http_methods(methods_or_second_schema)
|
|
230
230
|
if additional_schemas:
|
|
231
231
|
raise TypeError("If path was provided as a first argument, then additional schemas cannot be added")
|
|
232
232
|
|
cadwyn/structure/endpoints.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from collections.abc import Callable, Sequence
|
|
1
|
+
from collections.abc import Callable, Collection, Sequence
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from typing import Any
|
|
@@ -148,6 +148,12 @@ class EndpointInstructionFactory:
|
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
def endpoint(path: str, methods: list[str], /, *, func_name: str | None = None) -> EndpointInstructionFactory:
|
|
151
|
+
_validate_that_strings_are_valid_http_methods(methods)
|
|
152
|
+
|
|
153
|
+
return EndpointInstructionFactory(path, set(methods), func_name)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _validate_that_strings_are_valid_http_methods(methods: Collection[str]):
|
|
151
157
|
invalid_methods = set(methods) - HTTP_METHODS
|
|
152
158
|
if invalid_methods:
|
|
153
159
|
invalid_methods = ", ".join(sorted(invalid_methods))
|
|
@@ -156,7 +162,5 @@ def endpoint(path: str, methods: list[str], /, *, func_name: str | None = None)
|
|
|
156
162
|
"Please use valid HTTP methods such as GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD.",
|
|
157
163
|
)
|
|
158
164
|
|
|
159
|
-
return EndpointInstructionFactory(path, set(methods), func_name)
|
|
160
|
-
|
|
161
165
|
|
|
162
166
|
AlterEndpointSubInstruction = EndpointDidntExistInstruction | EndpointExistedInstruction | EndpointHadInstruction
|
cadwyn/structure/versions.py
CHANGED
|
@@ -20,7 +20,7 @@ from fastapi.concurrency import run_in_threadpool
|
|
|
20
20
|
from fastapi.dependencies.models import Dependant
|
|
21
21
|
from fastapi.dependencies.utils import solve_dependencies
|
|
22
22
|
from fastapi.exceptions import RequestValidationError
|
|
23
|
-
from fastapi.responses import FileResponse, StreamingResponse
|
|
23
|
+
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
|
24
24
|
from fastapi.routing import APIRoute, _prepare_response_content
|
|
25
25
|
from pydantic import BaseModel
|
|
26
26
|
from starlette._utils import is_async_callable
|
|
@@ -39,12 +39,12 @@ from cadwyn.exceptions import CadwynError, CadwynHeadRequestValidationError, Cad
|
|
|
39
39
|
from .._utils import Sentinel
|
|
40
40
|
from .common import Endpoint, VersionDate, VersionedModel
|
|
41
41
|
from .data import (
|
|
42
|
-
AlterRequestByPathInstruction,
|
|
43
|
-
AlterRequestBySchemaInstruction,
|
|
44
|
-
AlterResponseByPathInstruction,
|
|
45
|
-
AlterResponseBySchemaInstruction,
|
|
46
42
|
RequestInfo,
|
|
47
43
|
ResponseInfo,
|
|
44
|
+
_AlterRequestByPathInstruction,
|
|
45
|
+
_AlterRequestBySchemaInstruction,
|
|
46
|
+
_AlterResponseByPathInstruction,
|
|
47
|
+
_AlterResponseBySchemaInstruction,
|
|
48
48
|
_BaseAlterResponseInstruction,
|
|
49
49
|
)
|
|
50
50
|
from .endpoints import AlterEndpointSubInstruction
|
|
@@ -74,12 +74,12 @@ class VersionChange:
|
|
|
74
74
|
alter_enum_instructions: ClassVar[list[AlterEnumSubInstruction]] = Sentinel
|
|
75
75
|
alter_module_instructions: ClassVar[list[AlterModuleInstruction]] = Sentinel
|
|
76
76
|
alter_endpoint_instructions: ClassVar[list[AlterEndpointSubInstruction]] = Sentinel
|
|
77
|
-
alter_request_by_schema_instructions: ClassVar[
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
alter_request_by_path_instructions: ClassVar[dict[str, list[
|
|
81
|
-
alter_response_by_schema_instructions: ClassVar[dict[type, list[
|
|
82
|
-
alter_response_by_path_instructions: ClassVar[dict[str, list[
|
|
77
|
+
alter_request_by_schema_instructions: ClassVar[dict[type[BaseModel], list[_AlterRequestBySchemaInstruction]]] = (
|
|
78
|
+
Sentinel
|
|
79
|
+
)
|
|
80
|
+
alter_request_by_path_instructions: ClassVar[dict[str, list[_AlterRequestByPathInstruction]]] = Sentinel
|
|
81
|
+
alter_response_by_schema_instructions: ClassVar[dict[type, list[_AlterResponseBySchemaInstruction]]] = Sentinel
|
|
82
|
+
alter_response_by_path_instructions: ClassVar[dict[str, list[_AlterResponseByPathInstruction]]] = Sentinel
|
|
83
83
|
_bound_version_bundle: "VersionBundle | None"
|
|
84
84
|
|
|
85
85
|
def __init_subclass__(cls, _abstract: bool = False) -> None:
|
|
@@ -96,15 +96,15 @@ class VersionChange:
|
|
|
96
96
|
@classmethod
|
|
97
97
|
def _extract_body_instructions_into_correct_containers(cls):
|
|
98
98
|
for instruction in cls.__dict__.values():
|
|
99
|
-
if isinstance(instruction,
|
|
99
|
+
if isinstance(instruction, _AlterRequestBySchemaInstruction):
|
|
100
100
|
for schema in instruction.schemas:
|
|
101
101
|
cls.alter_request_by_schema_instructions[schema].append(instruction)
|
|
102
|
-
elif isinstance(instruction,
|
|
102
|
+
elif isinstance(instruction, _AlterRequestByPathInstruction):
|
|
103
103
|
cls.alter_request_by_path_instructions[instruction.path].append(instruction)
|
|
104
|
-
elif isinstance(instruction,
|
|
104
|
+
elif isinstance(instruction, _AlterResponseBySchemaInstruction):
|
|
105
105
|
for schema in instruction.schemas:
|
|
106
106
|
cls.alter_response_by_schema_instructions[schema].append(instruction)
|
|
107
|
-
elif isinstance(instruction,
|
|
107
|
+
elif isinstance(instruction, _AlterResponseByPathInstruction):
|
|
108
108
|
cls.alter_response_by_path_instructions[instruction.path].append(instruction)
|
|
109
109
|
|
|
110
110
|
@classmethod
|
|
@@ -154,10 +154,10 @@ class VersionChange:
|
|
|
154
154
|
for attr_name, attr_value in cls.__dict__.items():
|
|
155
155
|
if not isinstance(
|
|
156
156
|
attr_value,
|
|
157
|
-
|
|
158
|
-
|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
157
|
+
_AlterRequestBySchemaInstruction
|
|
158
|
+
| _AlterRequestByPathInstruction
|
|
159
|
+
| _AlterResponseBySchemaInstruction
|
|
160
|
+
| _AlterResponseByPathInstruction,
|
|
161
161
|
) and attr_name not in {
|
|
162
162
|
"description",
|
|
163
163
|
"side_effects",
|
|
@@ -245,8 +245,7 @@ class VersionBundle:
|
|
|
245
245
|
*other_versions: Version,
|
|
246
246
|
api_version_var: APIVersionVarType | None = None,
|
|
247
247
|
head_schemas_package: ModuleType | None = None,
|
|
248
|
-
) -> None:
|
|
249
|
-
...
|
|
248
|
+
) -> None: ...
|
|
250
249
|
|
|
251
250
|
@overload
|
|
252
251
|
@deprecated("Pass head_version_package instead of latest_schemas_package.")
|
|
@@ -257,8 +256,7 @@ class VersionBundle:
|
|
|
257
256
|
*other_versions: Version,
|
|
258
257
|
api_version_var: APIVersionVarType | None = None,
|
|
259
258
|
latest_schemas_package: ModuleType | None = None,
|
|
260
|
-
) -> None:
|
|
261
|
-
...
|
|
259
|
+
) -> None: ...
|
|
262
260
|
|
|
263
261
|
def __init__(
|
|
264
262
|
self,
|
|
@@ -387,7 +385,7 @@ class VersionBundle:
|
|
|
387
385
|
}
|
|
388
386
|
|
|
389
387
|
@functools.cached_property
|
|
390
|
-
def
|
|
388
|
+
def versioned_directories_with_head(self) -> tuple[Path, ...]:
|
|
391
389
|
if self.head_schemas_package is None:
|
|
392
390
|
raise CadwynError(
|
|
393
391
|
f"You cannot call 'VersionBundle.{self.migrate_response_body.__name__}' because it has no access to "
|
|
@@ -399,6 +397,10 @@ class VersionBundle:
|
|
|
399
397
|
+ [get_version_dir_path(self.head_schemas_package, version.value) for version in self]
|
|
400
398
|
)
|
|
401
399
|
|
|
400
|
+
@functools.cached_property
|
|
401
|
+
def versioned_directories_without_head(self) -> tuple[Path, ...]:
|
|
402
|
+
return self.versioned_directories_with_head[1:]
|
|
403
|
+
|
|
402
404
|
def migrate_response_body(self, latest_response_model: type[BaseModel], *, latest_body: Any, version: VersionDate):
|
|
403
405
|
"""Convert the data to a specific version by applying all version changes from latest until that version
|
|
404
406
|
in reverse order and wrapping the result in the correct version of latest_response_model.
|
|
@@ -413,11 +415,10 @@ class VersionBundle:
|
|
|
413
415
|
)
|
|
414
416
|
|
|
415
417
|
version = self._get_closest_lesser_version(version)
|
|
416
|
-
|
|
417
|
-
version_dir = self.versioned_directories[self.version_dates.index(version) + 1]
|
|
418
|
+
version_dir = self.versioned_directories_without_head[self.version_dates.index(version)]
|
|
418
419
|
|
|
419
420
|
versioned_response_model: type[BaseModel] = get_another_version_of_cls(
|
|
420
|
-
latest_response_model, version_dir, self.
|
|
421
|
+
latest_response_model, version_dir, self.versioned_directories_with_head
|
|
421
422
|
)
|
|
422
423
|
return versioned_response_model.parse_obj(migrated_response.body)
|
|
423
424
|
|
|
@@ -457,7 +458,7 @@ class VersionBundle:
|
|
|
457
458
|
instruction(request_info)
|
|
458
459
|
if path in version_change.alter_request_by_path_instructions:
|
|
459
460
|
for instruction in version_change.alter_request_by_path_instructions[path]:
|
|
460
|
-
if method in instruction.methods:
|
|
461
|
+
if method in instruction.methods: # pragma: no branch # safe branch to skip
|
|
461
462
|
instruction(request_info)
|
|
462
463
|
request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
|
|
463
464
|
del request._headers
|
|
@@ -509,7 +510,7 @@ class VersionBundle:
|
|
|
509
510
|
|
|
510
511
|
if path in version_change.alter_response_by_path_instructions:
|
|
511
512
|
for instruction in version_change.alter_response_by_path_instructions[path]:
|
|
512
|
-
if method in instruction.methods:
|
|
513
|
+
if method in instruction.methods: # pragma: no branch # Safe branch to skip
|
|
513
514
|
migrations_to_apply.append(instruction)
|
|
514
515
|
|
|
515
516
|
for migration in migrations_to_apply:
|
|
@@ -620,7 +621,10 @@ class VersionBundle:
|
|
|
620
621
|
if isinstance(response_or_response_body, StreamingResponse | FileResponse):
|
|
621
622
|
body = None
|
|
622
623
|
elif response_or_response_body.body:
|
|
623
|
-
|
|
624
|
+
if isinstance(response_or_response_body, JSONResponse) or raised_exception is not None:
|
|
625
|
+
body = json.loads(response_or_response_body.body)
|
|
626
|
+
else:
|
|
627
|
+
body = response_or_response_body.body.decode(response_or_response_body.charset)
|
|
624
628
|
else:
|
|
625
629
|
body = None
|
|
626
630
|
# TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
|
|
@@ -666,16 +670,25 @@ class VersionBundle:
|
|
|
666
670
|
# that do not have it. We don't support it too.
|
|
667
671
|
if response_info.body is not None and hasattr(response_info._response, "body"):
|
|
668
672
|
# TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
|
|
669
|
-
|
|
673
|
+
if isinstance(response_info.body, str):
|
|
674
|
+
response_info._response.body = response_info.body.encode(response_info._response.charset)
|
|
675
|
+
else:
|
|
676
|
+
response_info._response.body = json.dumps(
|
|
677
|
+
response_info.body,
|
|
678
|
+
ensure_ascii=False,
|
|
679
|
+
allow_nan=False,
|
|
680
|
+
indent=None,
|
|
681
|
+
separators=(",", ":"),
|
|
682
|
+
).encode("utf-8")
|
|
683
|
+
# It makes sense to re-calculate content length because the previously calculated one
|
|
684
|
+
# might slightly differ. If it differs -- uvicorn will break.
|
|
685
|
+
response_info.headers["content-length"] = str(len(response_info._response.body))
|
|
670
686
|
|
|
671
687
|
if raised_exception is not None and response_info.status_code >= 400:
|
|
672
688
|
if isinstance(response_info.body, dict) and "detail" in response_info.body:
|
|
673
689
|
detail = response_info.body["detail"]
|
|
674
690
|
else:
|
|
675
691
|
detail = response_info.body
|
|
676
|
-
# It makes more sense to re-calculate content length because the previously calculated one
|
|
677
|
-
# might slightly differ.
|
|
678
|
-
del response_info.headers["content-length"]
|
|
679
692
|
|
|
680
693
|
raise HTTPException(
|
|
681
694
|
status_code=response_info.status_code,
|
|
@@ -713,7 +726,7 @@ class VersionBundle:
|
|
|
713
726
|
and body_field_alias in kwargs
|
|
714
727
|
):
|
|
715
728
|
raw_body: BaseModel | None = kwargs.get(body_field_alias)
|
|
716
|
-
if raw_body is None:
|
|
729
|
+
if raw_body is None: # pragma: no cover # This is likely an impossible case but we would like to be safe
|
|
717
730
|
body = None
|
|
718
731
|
# It means we have a dict or a list instead of a full model.
|
|
719
732
|
# This covers the following use case in the endpoint definition: "payload: dict = Body(None)"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.13.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
|
|
@@ -34,6 +34,7 @@ Classifier: Typing :: Typed
|
|
|
34
34
|
Provides-Extra: cli
|
|
35
35
|
Requires-Dist: better-ast-comments (>=1.2.1,<1.3.0)
|
|
36
36
|
Requires-Dist: fastapi (>=0.110.0)
|
|
37
|
+
Requires-Dist: issubclass (>=0.1.2,<0.2.0)
|
|
37
38
|
Requires-Dist: jinja2 (>=3.1.2)
|
|
38
39
|
Requires-Dist: pydantic (>=1.0.0)
|
|
39
40
|
Requires-Dist: starlette (>=0.36.3)
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
cadwyn/__init__.py,sha256=XgF-CtZo-fPk5730sKlY2fAmPsTQRIsFbrfNFeUZyFY,495
|
|
2
|
-
cadwyn/__main__.py,sha256=
|
|
3
|
-
cadwyn/_asts.py,sha256=
|
|
4
|
-
cadwyn/_compat.py,sha256=
|
|
2
|
+
cadwyn/__main__.py,sha256=q7oNhnJ_hNRib3o6cAo4QC0cME0prVi-6P0G14tRdkw,4339
|
|
3
|
+
cadwyn/_asts.py,sha256=OF1qQKPqTbgYhH1tYF-MB8CCU0r6YITZpMFegzmk0Ic,10118
|
|
4
|
+
cadwyn/_compat.py,sha256=yAPmfGl2vVEYXlNHHPMoa2JkEJCVPjbP_Uz0WOIVOp4,5494
|
|
5
5
|
cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
|
|
6
6
|
cadwyn/_utils.py,sha256=BFsfZBpdoL5RMAaT1V1cXJVpTZCmwksQ-Le2MTHivGI,4841
|
|
7
7
|
cadwyn/applications.py,sha256=MAVsgYojgQO4PrUETVMAsp49k6baW4h4LtS6z12gTZs,15767
|
|
8
8
|
cadwyn/codegen/README.md,sha256=hc7AE87LsEsvbh-wX1H10JEWh-8bLHoe-1CkY3h00FI,879
|
|
9
9
|
cadwyn/codegen/__init__.py,sha256=JgddDjxMTjSfVrMXHwNu1ODgdn2QfPWpccrRKquBV6k,355
|
|
10
10
|
cadwyn/codegen/_common.py,sha256=FTI4fqpUFGBMACVlPiDMHTWhqwW_-zQNa_4Qh7m-hCA,5877
|
|
11
|
-
cadwyn/codegen/_main.py,sha256=
|
|
11
|
+
cadwyn/codegen/_main.py,sha256=1mpXq_1AuZaAOeGjrCVMhcK7zhjmmlO82Q3ehoOOAfM,10483
|
|
12
12
|
cadwyn/codegen/_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
cadwyn/codegen/_plugins/class_migrations.py,sha256=kHZ-RMRTARZ4l70fxHMtul_204Ute2_yQmEej7wMwwo,20119
|
|
14
14
|
cadwyn/codegen/_plugins/class_rebuilding.py,sha256=zNlB_VxoEAtdC5Ydiqa7pu6Ka-pKnpPQk_dvovaK0QI,3623
|
|
15
15
|
cadwyn/codegen/_plugins/class_renaming.py,sha256=oc9Ms6YnpJKaq1iOehcBfA_OFUFL-CAAZJiaQPlkKHs,1773
|
|
16
16
|
cadwyn/codegen/_plugins/import_auto_adding.py,sha256=krAVzsmsW-CbKP-W9oCkQsL7aPfhHzRq4STgai6Tm5s,2543
|
|
17
17
|
cadwyn/codegen/_plugins/module_migrations.py,sha256=TeWJk4Iu4SRQ9K2iI3v3sCs1110jrltKlPdfU9mXIsQ,722
|
|
18
|
-
cadwyn/exceptions.py,sha256=
|
|
18
|
+
cadwyn/exceptions.py,sha256=aJKx1qgzZqShL4MX3COjS780qzNJcdZFeGzYYa5gbzw,1726
|
|
19
19
|
cadwyn/main.py,sha256=kt2Vn7TIA4ZnD_xrgz57TOjUk-4zVP8SV8nuTZBEaaU,218
|
|
20
20
|
cadwyn/middleware.py,sha256=8cuBri_yRkl0goe6G0MLwtL04WGbW9Infah3wy9hUVM,3372
|
|
21
21
|
cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
cadwyn/route_generation.py,sha256=
|
|
23
|
-
cadwyn/routing.py,sha256=
|
|
22
|
+
cadwyn/route_generation.py,sha256=kpcOknl-R2TBsFtsliA0MZ_bdqH4qZ9-J7eCbejCXfE,39747
|
|
23
|
+
cadwyn/routing.py,sha256=ObH4-ETYPQjm3bMVCNzGttEKv1LL9q2sbi9eJD4W3lY,6250
|
|
24
24
|
cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
cadwyn/static/docs.html,sha256=WNm5ANJVy51TcIUFOaqKf1Z8eF86CC85TTHPxACtkzw,3455
|
|
26
26
|
cadwyn/structure/__init__.py,sha256=HjaNd6H4m4Cia42-dCO7A7sLWuVII7oldjaCabhbs_o,697
|
|
27
27
|
cadwyn/structure/common.py,sha256=6Z4nI97XPWTCinn6np73m-rLPyYNrz2fWXKJlqjsiaQ,269
|
|
28
|
-
cadwyn/structure/data.py,sha256=
|
|
29
|
-
cadwyn/structure/endpoints.py,sha256=
|
|
28
|
+
cadwyn/structure/data.py,sha256=1ALPhBBCE_t4GrxM0Fa3hQ-jkORJgeWNySnZ42bsi0g,7382
|
|
29
|
+
cadwyn/structure/endpoints.py,sha256=JhTgVrqLjm5LkE9thjvU1UuWcSCmDgW2bMdqznsZb2Y,5777
|
|
30
30
|
cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
|
|
31
31
|
cadwyn/structure/modules.py,sha256=1FK-lLm-zOTXEvn-QtyBH38aDRht5PDQiZrOPCsBlM4,1268
|
|
32
32
|
cadwyn/structure/schemas.py,sha256=0ylArAkUw626VkUOJSulOwJs7CS6lrGBRECEG5HFD4Q,8897
|
|
33
|
-
cadwyn/structure/versions.py,sha256=
|
|
34
|
-
cadwyn-3.
|
|
35
|
-
cadwyn-3.
|
|
36
|
-
cadwyn-3.
|
|
37
|
-
cadwyn-3.
|
|
38
|
-
cadwyn-3.
|
|
33
|
+
cadwyn/structure/versions.py,sha256=Z9TQs4QsuLOAWxjKeq0KERYVCHneFIXyeoUo4NdmMo8,37199
|
|
34
|
+
cadwyn-3.13.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
35
|
+
cadwyn-3.13.0.dist-info/METADATA,sha256=1BGYb7exKyPC1Da58xWpJziBDSzxZlzxq5hs9XMTJW8,4403
|
|
36
|
+
cadwyn-3.13.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
37
|
+
cadwyn-3.13.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
|
|
38
|
+
cadwyn-3.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|