cadwyn 3.12.0__py3-none-any.whl → 3.15.10__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 +8 -2
- cadwyn/__main__.py +2 -3
- cadwyn/_asts.py +5 -5
- cadwyn/_compat.py +9 -2
- cadwyn/applications.py +132 -148
- cadwyn/codegen/_common.py +2 -1
- cadwyn/codegen/_main.py +9 -11
- cadwyn/codegen/_plugins/class_rebuilding.py +2 -2
- cadwyn/exceptions.py +12 -0
- cadwyn/middleware.py +4 -4
- cadwyn/route_generation.py +215 -106
- cadwyn/routing.py +38 -7
- cadwyn/structure/data.py +17 -17
- cadwyn/structure/endpoints.py +7 -3
- cadwyn/structure/modules.py +2 -1
- cadwyn/structure/versions.py +64 -43
- {cadwyn-3.12.0.dist-info → cadwyn-3.15.10.dist-info}/METADATA +5 -4
- cadwyn-3.15.10.dist-info/RECORD +38 -0
- {cadwyn-3.12.0.dist-info → cadwyn-3.15.10.dist-info}/WHEEL +1 -1
- cadwyn-3.12.0.dist-info/RECORD +0 -38
- {cadwyn-3.12.0.dist-info → cadwyn-3.15.10.dist-info}/LICENSE +0 -0
- {cadwyn-3.12.0.dist-info → cadwyn-3.15.10.dist-info}/entry_points.txt +0 -0
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/modules.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import dataclasses
|
|
3
|
+
import textwrap
|
|
3
4
|
from dataclasses import InitVar, dataclass
|
|
4
5
|
from types import ModuleType
|
|
5
6
|
|
|
@@ -13,7 +14,7 @@ class AlterModuleInstruction:
|
|
|
13
14
|
import_: ast.Import | ast.ImportFrom = dataclasses.field(init=False)
|
|
14
15
|
|
|
15
16
|
def __post_init__(self, raw_import: str):
|
|
16
|
-
parsed_body = ast.parse(raw_import).body
|
|
17
|
+
parsed_body = ast.parse(textwrap.dedent(raw_import)).body
|
|
17
18
|
if len(parsed_body) > 1:
|
|
18
19
|
raise CadwynStructureError(
|
|
19
20
|
f"You have specified more than just a single import. This is prohibited. "
|
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,
|
|
@@ -287,6 +285,8 @@ class VersionBundle:
|
|
|
287
285
|
raise CadwynStructureError(
|
|
288
286
|
"Versions are not sorted correctly. Please sort them in descending order.",
|
|
289
287
|
)
|
|
288
|
+
if not self.versions:
|
|
289
|
+
raise CadwynStructureError("You must define at least one non-head version in a VersionBundle.")
|
|
290
290
|
if self.versions[-1].version_changes:
|
|
291
291
|
raise CadwynStructureError(
|
|
292
292
|
f'The first version "{self.versions[-1].value}" cannot have any version changes. '
|
|
@@ -327,7 +327,7 @@ class VersionBundle:
|
|
|
327
327
|
raise CadwynStructureError(
|
|
328
328
|
f'The head schemas package must be a package. "{head_schemas_package.__name__}" is not a package.',
|
|
329
329
|
)
|
|
330
|
-
elif head_schemas_package.__name__.endswith(".head"):
|
|
330
|
+
elif head_schemas_package.__name__.endswith(".head") or head_schemas_package.__name__ == "head":
|
|
331
331
|
return "head"
|
|
332
332
|
elif head_schemas_package.__name__.endswith(".latest"):
|
|
333
333
|
warnings.warn(
|
|
@@ -387,7 +387,7 @@ class VersionBundle:
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
@functools.cached_property
|
|
390
|
-
def
|
|
390
|
+
def versioned_directories_with_head(self) -> tuple[Path, ...]:
|
|
391
391
|
if self.head_schemas_package is None:
|
|
392
392
|
raise CadwynError(
|
|
393
393
|
f"You cannot call 'VersionBundle.{self.migrate_response_body.__name__}' because it has no access to "
|
|
@@ -399,6 +399,10 @@ class VersionBundle:
|
|
|
399
399
|
+ [get_version_dir_path(self.head_schemas_package, version.value) for version in self]
|
|
400
400
|
)
|
|
401
401
|
|
|
402
|
+
@functools.cached_property
|
|
403
|
+
def versioned_directories_without_head(self) -> tuple[Path, ...]:
|
|
404
|
+
return self.versioned_directories_with_head[1:]
|
|
405
|
+
|
|
402
406
|
def migrate_response_body(self, latest_response_model: type[BaseModel], *, latest_body: Any, version: VersionDate):
|
|
403
407
|
"""Convert the data to a specific version by applying all version changes from latest until that version
|
|
404
408
|
in reverse order and wrapping the result in the correct version of latest_response_model.
|
|
@@ -413,13 +417,12 @@ class VersionBundle:
|
|
|
413
417
|
)
|
|
414
418
|
|
|
415
419
|
version = self._get_closest_lesser_version(version)
|
|
416
|
-
|
|
417
|
-
version_dir = self.versioned_directories[self.version_dates.index(version) + 1]
|
|
420
|
+
version_dir = self.versioned_directories_without_head[self.version_dates.index(version)]
|
|
418
421
|
|
|
419
422
|
versioned_response_model: type[BaseModel] = get_another_version_of_cls(
|
|
420
|
-
latest_response_model, version_dir, self.
|
|
423
|
+
latest_response_model, version_dir, self.versioned_directories_with_head
|
|
421
424
|
)
|
|
422
|
-
return versioned_response_model.parse_obj(migrated_response.body)
|
|
425
|
+
return versioned_response_model.parse_obj(migrated_response.body) # pyright: ignore[reportDeprecated]
|
|
423
426
|
|
|
424
427
|
def _get_closest_lesser_version(self, version: VersionDate):
|
|
425
428
|
for defined_version in self.version_dates:
|
|
@@ -446,6 +449,8 @@ class VersionBundle:
|
|
|
446
449
|
current_version: VersionDate,
|
|
447
450
|
head_route: APIRoute,
|
|
448
451
|
exit_stack: AsyncExitStack,
|
|
452
|
+
*,
|
|
453
|
+
embed_body_fields: bool,
|
|
449
454
|
) -> dict[str, Any]:
|
|
450
455
|
method = request.method
|
|
451
456
|
for v in reversed(self.versions):
|
|
@@ -457,25 +462,25 @@ class VersionBundle:
|
|
|
457
462
|
instruction(request_info)
|
|
458
463
|
if path in version_change.alter_request_by_path_instructions:
|
|
459
464
|
for instruction in version_change.alter_request_by_path_instructions[path]:
|
|
460
|
-
if method in instruction.methods:
|
|
465
|
+
if method in instruction.methods: # pragma: no branch # safe branch to skip
|
|
461
466
|
instruction(request_info)
|
|
462
467
|
request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
|
|
463
468
|
del request._headers
|
|
464
469
|
# Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
|
|
465
|
-
|
|
466
|
-
dependencies, errors, _, _, _ = await solve_dependencies(
|
|
470
|
+
result = await solve_dependencies(
|
|
467
471
|
request=request,
|
|
468
472
|
response=response,
|
|
469
473
|
dependant=head_dependant,
|
|
470
474
|
body=request_info.body,
|
|
471
475
|
dependency_overrides_provider=head_route.dependency_overrides_provider,
|
|
472
476
|
async_exit_stack=exit_stack,
|
|
477
|
+
embed_body_fields=embed_body_fields,
|
|
473
478
|
)
|
|
474
|
-
if errors:
|
|
479
|
+
if result.errors:
|
|
475
480
|
raise CadwynHeadRequestValidationError(
|
|
476
|
-
_normalize_errors(errors), body=request_info.body, version=current_version
|
|
481
|
+
_normalize_errors(result.errors), body=request_info.body, version=current_version
|
|
477
482
|
)
|
|
478
|
-
return
|
|
483
|
+
return result.values
|
|
479
484
|
|
|
480
485
|
def _migrate_response(
|
|
481
486
|
self,
|
|
@@ -509,7 +514,7 @@ class VersionBundle:
|
|
|
509
514
|
|
|
510
515
|
if path in version_change.alter_response_by_path_instructions:
|
|
511
516
|
for instruction in version_change.alter_response_by_path_instructions[path]:
|
|
512
|
-
if method in instruction.methods:
|
|
517
|
+
if method in instruction.methods: # pragma: no branch # Safe branch to skip
|
|
513
518
|
migrations_to_apply.append(instruction)
|
|
514
519
|
|
|
515
520
|
for migration in migrations_to_apply:
|
|
@@ -620,7 +625,10 @@ class VersionBundle:
|
|
|
620
625
|
if isinstance(response_or_response_body, StreamingResponse | FileResponse):
|
|
621
626
|
body = None
|
|
622
627
|
elif response_or_response_body.body:
|
|
623
|
-
|
|
628
|
+
if isinstance(response_or_response_body, JSONResponse) or raised_exception is not None:
|
|
629
|
+
body = json.loads(response_or_response_body.body)
|
|
630
|
+
else:
|
|
631
|
+
body = response_or_response_body.body.decode(response_or_response_body.charset)
|
|
624
632
|
else:
|
|
625
633
|
body = None
|
|
626
634
|
# TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
|
|
@@ -666,16 +674,28 @@ class VersionBundle:
|
|
|
666
674
|
# that do not have it. We don't support it too.
|
|
667
675
|
if response_info.body is not None and hasattr(response_info._response, "body"):
|
|
668
676
|
# TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
|
|
669
|
-
|
|
677
|
+
if (
|
|
678
|
+
isinstance(response_info.body, str)
|
|
679
|
+
and response_info._response.headers.get("content-type") != "application/json"
|
|
680
|
+
):
|
|
681
|
+
response_info._response.body = response_info.body.encode(response_info._response.charset)
|
|
682
|
+
else:
|
|
683
|
+
response_info._response.body = json.dumps(
|
|
684
|
+
response_info.body,
|
|
685
|
+
ensure_ascii=False,
|
|
686
|
+
allow_nan=False,
|
|
687
|
+
indent=None,
|
|
688
|
+
separators=(",", ":"),
|
|
689
|
+
).encode("utf-8")
|
|
690
|
+
# It makes sense to re-calculate content length because the previously calculated one
|
|
691
|
+
# might slightly differ. If it differs -- uvicorn will break.
|
|
692
|
+
response_info.headers["content-length"] = str(len(response_info._response.body))
|
|
670
693
|
|
|
671
694
|
if raised_exception is not None and response_info.status_code >= 400:
|
|
672
695
|
if isinstance(response_info.body, dict) and "detail" in response_info.body:
|
|
673
696
|
detail = response_info.body["detail"]
|
|
674
697
|
else:
|
|
675
698
|
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
699
|
|
|
680
700
|
raise HTTPException(
|
|
681
701
|
status_code=response_info.status_code,
|
|
@@ -713,7 +733,7 @@ class VersionBundle:
|
|
|
713
733
|
and body_field_alias in kwargs
|
|
714
734
|
):
|
|
715
735
|
raw_body: BaseModel | None = kwargs.get(body_field_alias)
|
|
716
|
-
if raw_body is None:
|
|
736
|
+
if raw_body is None: # pragma: no cover # This is likely an impossible case but we would like to be safe
|
|
717
737
|
body = None
|
|
718
738
|
# It means we have a dict or a list instead of a full model.
|
|
719
739
|
# This covers the following use case in the endpoint definition: "payload: dict = Body(None)"
|
|
@@ -738,6 +758,7 @@ class VersionBundle:
|
|
|
738
758
|
api_version,
|
|
739
759
|
head_route,
|
|
740
760
|
exit_stack=exit_stack,
|
|
761
|
+
embed_body_fields=route._embed_body_fields,
|
|
741
762
|
)
|
|
742
763
|
# Because we re-added it into our kwargs when we did solve_dependencies
|
|
743
764
|
if _CADWYN_REQUEST_PARAM_NAME in new_kwargs:
|
|
@@ -790,7 +811,7 @@ async def _get_body(
|
|
|
790
811
|
) from e
|
|
791
812
|
except HTTPException:
|
|
792
813
|
raise
|
|
793
|
-
except Exception as e:
|
|
814
|
+
except Exception as e:
|
|
794
815
|
raise HTTPException(status_code=400, detail="There was an error parsing the body") from e
|
|
795
816
|
return body
|
|
796
817
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.15.10
|
|
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
|
|
@@ -33,7 +33,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
33
33
|
Classifier: Typing :: Typed
|
|
34
34
|
Provides-Extra: cli
|
|
35
35
|
Requires-Dist: better-ast-comments (>=1.2.1,<1.3.0)
|
|
36
|
-
Requires-Dist: fastapi (>=0.
|
|
36
|
+
Requires-Dist: fastapi (>=0.115.2)
|
|
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)
|
|
@@ -53,8 +54,8 @@ Production-ready community-driven modern [Stripe-like](https://stripe.com/blog/a
|
|
|
53
54
|
<a href="https://github.com/zmievsa/cadwyn/actions/workflows/ci.yaml?branch=main&event=push" target="_blank">
|
|
54
55
|
<img src="https://github.com/zmievsa/cadwyn/actions/workflows/ci.yaml/badge.svg?branch=main&event=push" alt="Test">
|
|
55
56
|
</a>
|
|
56
|
-
<a href="https://codecov.io/gh/
|
|
57
|
-
<img src="https://img.shields.io/codecov/c/github/
|
|
57
|
+
<a href="https://codecov.io/gh/zmievsa/cadwyn" target="_blank">
|
|
58
|
+
<img src="https://img.shields.io/codecov/c/github/zmievsa/cadwyn?color=%2334D058&logo=codecov" alt="Coverage">
|
|
58
59
|
</a>
|
|
59
60
|
<a href="https://pypi.org/project/cadwyn/" target="_blank">
|
|
60
61
|
<img alt="PyPI" src="https://img.shields.io/pypi/v/cadwyn?color=%2334D058&logo=pypi&label=PyPI package" alt="Package version">
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
cadwyn/__init__.py,sha256=Wh_CtNgodacy8plxyDXCDb52CDftbql4jGXQ2pldX4s,605
|
|
2
|
+
cadwyn/__main__.py,sha256=cc-5iYItjxRnB09uxuxlEbjrLm1AEhXI2KrI5iakEOw,4376
|
|
3
|
+
cadwyn/_asts.py,sha256=0OcYNZPFm65CFb-oZMwbx191Bai0asqDA4y_IKzh6_w,10147
|
|
4
|
+
cadwyn/_compat.py,sha256=yAPmfGl2vVEYXlNHHPMoa2JkEJCVPjbP_Uz0WOIVOp4,5494
|
|
5
|
+
cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
|
|
6
|
+
cadwyn/_utils.py,sha256=BFsfZBpdoL5RMAaT1V1cXJVpTZCmwksQ-Le2MTHivGI,4841
|
|
7
|
+
cadwyn/applications.py,sha256=MkZ_vMs6YfnfuWPUzBjNBmrxtDa97etsV0zcnlv-iS0,15749
|
|
8
|
+
cadwyn/codegen/README.md,sha256=hc7AE87LsEsvbh-wX1H10JEWh-8bLHoe-1CkY3h00FI,879
|
|
9
|
+
cadwyn/codegen/__init__.py,sha256=JgddDjxMTjSfVrMXHwNu1ODgdn2QfPWpccrRKquBV6k,355
|
|
10
|
+
cadwyn/codegen/_common.py,sha256=fnz9Q-C7oZ3JKypPC_Xi6_CssF803XAUHL661IYY1hM,5910
|
|
11
|
+
cadwyn/codegen/_main.py,sha256=KT362MCSeb_CYBJrCHej-E15dtmCYgykwE9Jz43B17g,10546
|
|
12
|
+
cadwyn/codegen/_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
cadwyn/codegen/_plugins/class_migrations.py,sha256=kHZ-RMRTARZ4l70fxHMtul_204Ute2_yQmEej7wMwwo,20119
|
|
14
|
+
cadwyn/codegen/_plugins/class_rebuilding.py,sha256=_gjQsx_kU36tf8HT3mKhSv7V9RXfawFdbKTtYLP2IMk,3698
|
|
15
|
+
cadwyn/codegen/_plugins/class_renaming.py,sha256=oc9Ms6YnpJKaq1iOehcBfA_OFUFL-CAAZJiaQPlkKHs,1773
|
|
16
|
+
cadwyn/codegen/_plugins/import_auto_adding.py,sha256=krAVzsmsW-CbKP-W9oCkQsL7aPfhHzRq4STgai6Tm5s,2543
|
|
17
|
+
cadwyn/codegen/_plugins/module_migrations.py,sha256=TeWJk4Iu4SRQ9K2iI3v3sCs1110jrltKlPdfU9mXIsQ,722
|
|
18
|
+
cadwyn/exceptions.py,sha256=aJKx1qgzZqShL4MX3COjS780qzNJcdZFeGzYYa5gbzw,1726
|
|
19
|
+
cadwyn/main.py,sha256=kt2Vn7TIA4ZnD_xrgz57TOjUk-4zVP8SV8nuTZBEaaU,218
|
|
20
|
+
cadwyn/middleware.py,sha256=kUZK2dmoricMbv6knPCIHpXEInX2670XIwAj0v_XQxk,3408
|
|
21
|
+
cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
cadwyn/route_generation.py,sha256=80LmEodZ5SgscnhVL4A3ck0t8RJK5d5Acnefyo4CfIc,41607
|
|
23
|
+
cadwyn/routing.py,sha256=o6IMjxTxARPa5BxFfsXqOP3bVw9Ya6OBAEbUwH9lMVM,7445
|
|
24
|
+
cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
cadwyn/static/docs.html,sha256=WNm5ANJVy51TcIUFOaqKf1Z8eF86CC85TTHPxACtkzw,3455
|
|
26
|
+
cadwyn/structure/__init__.py,sha256=HjaNd6H4m4Cia42-dCO7A7sLWuVII7oldjaCabhbs_o,697
|
|
27
|
+
cadwyn/structure/common.py,sha256=6Z4nI97XPWTCinn6np73m-rLPyYNrz2fWXKJlqjsiaQ,269
|
|
28
|
+
cadwyn/structure/data.py,sha256=1ALPhBBCE_t4GrxM0Fa3hQ-jkORJgeWNySnZ42bsi0g,7382
|
|
29
|
+
cadwyn/structure/endpoints.py,sha256=JhTgVrqLjm5LkE9thjvU1UuWcSCmDgW2bMdqznsZb2Y,5777
|
|
30
|
+
cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
|
|
31
|
+
cadwyn/structure/modules.py,sha256=v3hA_KiqKwwo-ur0Z84WvqD0rTTe4fBTkMUK8SxUj7s,1301
|
|
32
|
+
cadwyn/structure/schemas.py,sha256=0ylArAkUw626VkUOJSulOwJs7CS6lrGBRECEG5HFD4Q,8897
|
|
33
|
+
cadwyn/structure/versions.py,sha256=Vlp-H1K36NRztHRkNhagmNKgQbIP6bc0gAgm7egHCTs,37679
|
|
34
|
+
cadwyn-3.15.10.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
35
|
+
cadwyn-3.15.10.dist-info/METADATA,sha256=HVnWPABfADEa4aUPJvW_1PEmDmfbUu9waeKNXwnLW-I,4398
|
|
36
|
+
cadwyn-3.15.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
37
|
+
cadwyn-3.15.10.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
|
|
38
|
+
cadwyn-3.15.10.dist-info/RECORD,,
|
cadwyn-3.12.0.dist-info/RECORD
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
cadwyn/__init__.py,sha256=XgF-CtZo-fPk5730sKlY2fAmPsTQRIsFbrfNFeUZyFY,495
|
|
2
|
-
cadwyn/__main__.py,sha256=TMxAEvgN1MFc1TkkKmsS-jFMBAW8lVos4wUZ9stp6WU,4343
|
|
3
|
-
cadwyn/_asts.py,sha256=S-x9fVKTENZZxwWfabm0PbztcHyX2MJkI6Cwv5XgVrI,10138
|
|
4
|
-
cadwyn/_compat.py,sha256=6QwtzbXn53mIhEFfEizmFjd-f894oLsM6ITxqq2rCpc,5408
|
|
5
|
-
cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
|
|
6
|
-
cadwyn/_utils.py,sha256=BFsfZBpdoL5RMAaT1V1cXJVpTZCmwksQ-Le2MTHivGI,4841
|
|
7
|
-
cadwyn/applications.py,sha256=MAVsgYojgQO4PrUETVMAsp49k6baW4h4LtS6z12gTZs,15767
|
|
8
|
-
cadwyn/codegen/README.md,sha256=hc7AE87LsEsvbh-wX1H10JEWh-8bLHoe-1CkY3h00FI,879
|
|
9
|
-
cadwyn/codegen/__init__.py,sha256=JgddDjxMTjSfVrMXHwNu1ODgdn2QfPWpccrRKquBV6k,355
|
|
10
|
-
cadwyn/codegen/_common.py,sha256=FTI4fqpUFGBMACVlPiDMHTWhqwW_-zQNa_4Qh7m-hCA,5877
|
|
11
|
-
cadwyn/codegen/_main.py,sha256=8VdMhbQyNDyeZaxKV-2sUW_Sl5X-Lu92rHdINAHaTn0,10491
|
|
12
|
-
cadwyn/codegen/_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
cadwyn/codegen/_plugins/class_migrations.py,sha256=kHZ-RMRTARZ4l70fxHMtul_204Ute2_yQmEej7wMwwo,20119
|
|
14
|
-
cadwyn/codegen/_plugins/class_rebuilding.py,sha256=zNlB_VxoEAtdC5Ydiqa7pu6Ka-pKnpPQk_dvovaK0QI,3623
|
|
15
|
-
cadwyn/codegen/_plugins/class_renaming.py,sha256=oc9Ms6YnpJKaq1iOehcBfA_OFUFL-CAAZJiaQPlkKHs,1773
|
|
16
|
-
cadwyn/codegen/_plugins/import_auto_adding.py,sha256=krAVzsmsW-CbKP-W9oCkQsL7aPfhHzRq4STgai6Tm5s,2543
|
|
17
|
-
cadwyn/codegen/_plugins/module_migrations.py,sha256=TeWJk4Iu4SRQ9K2iI3v3sCs1110jrltKlPdfU9mXIsQ,722
|
|
18
|
-
cadwyn/exceptions.py,sha256=gsb1vszQ2VsnfTBM5B8AYmXwCW0yvA8pJ1GhNkGzgyE,1440
|
|
19
|
-
cadwyn/main.py,sha256=kt2Vn7TIA4ZnD_xrgz57TOjUk-4zVP8SV8nuTZBEaaU,218
|
|
20
|
-
cadwyn/middleware.py,sha256=8cuBri_yRkl0goe6G0MLwtL04WGbW9Infah3wy9hUVM,3372
|
|
21
|
-
cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
cadwyn/route_generation.py,sha256=7nHe_IP7Uz1rXmnItJsQ2yIuqe3lRgtOr8_MP_6Vg8c,35704
|
|
23
|
-
cadwyn/routing.py,sha256=s_-PxzDq0GT0pW-JtRLQrcR51tDQfd420oyWBP9S-sg,6158
|
|
24
|
-
cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
-
cadwyn/static/docs.html,sha256=WNm5ANJVy51TcIUFOaqKf1Z8eF86CC85TTHPxACtkzw,3455
|
|
26
|
-
cadwyn/structure/__init__.py,sha256=HjaNd6H4m4Cia42-dCO7A7sLWuVII7oldjaCabhbs_o,697
|
|
27
|
-
cadwyn/structure/common.py,sha256=6Z4nI97XPWTCinn6np73m-rLPyYNrz2fWXKJlqjsiaQ,269
|
|
28
|
-
cadwyn/structure/data.py,sha256=F7jqlRohpLJrgJxkxs2gTkO77q6BJ6pCbt6wJIo3TRk,7128
|
|
29
|
-
cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY,5627
|
|
30
|
-
cadwyn/structure/enums.py,sha256=iMokxA2QYJ61SzyB-Pmuq3y7KL7-e6TsnjLVUaVZQnw,954
|
|
31
|
-
cadwyn/structure/modules.py,sha256=1FK-lLm-zOTXEvn-QtyBH38aDRht5PDQiZrOPCsBlM4,1268
|
|
32
|
-
cadwyn/structure/schemas.py,sha256=0ylArAkUw626VkUOJSulOwJs7CS6lrGBRECEG5HFD4Q,8897
|
|
33
|
-
cadwyn/structure/versions.py,sha256=SjrVqwWFIgUQEazaIW8xXjwMhgJSdSNakbAyfHSArwA,36188
|
|
34
|
-
cadwyn-3.12.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
35
|
-
cadwyn-3.12.0.dist-info/METADATA,sha256=YWd0MSdFsQe6ZedoasOrtI_8J-RKlRL8TkSylw8XuYk,4360
|
|
36
|
-
cadwyn-3.12.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
37
|
-
cadwyn-3.12.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
|
|
38
|
-
cadwyn-3.12.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|