cadwyn 5.0.0__py3-none-any.whl → 5.1.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/_asts.py +10 -7
- cadwyn/_render.py +7 -7
- cadwyn/_utils.py +25 -2
- cadwyn/applications.py +34 -32
- cadwyn/changelogs.py +182 -181
- cadwyn/middleware.py +7 -7
- cadwyn/route_generation.py +10 -9
- cadwyn/routing.py +2 -2
- cadwyn/schema_generation.py +114 -46
- cadwyn/structure/common.py +5 -3
- cadwyn/structure/data.py +10 -7
- cadwyn/structure/endpoints.py +24 -21
- cadwyn/structure/enums.py +13 -7
- cadwyn/structure/schemas.py +63 -53
- cadwyn/structure/versions.py +50 -40
- {cadwyn-5.0.0.dist-info → cadwyn-5.1.0.dist-info}/METADATA +4 -3
- cadwyn-5.1.0.dist-info/RECORD +28 -0
- cadwyn-5.0.0.dist-info/RECORD +0 -28
- {cadwyn-5.0.0.dist-info → cadwyn-5.1.0.dist-info}/WHEEL +0 -0
- {cadwyn-5.0.0.dist-info → cadwyn-5.1.0.dist-info}/entry_points.txt +0 -0
- {cadwyn-5.0.0.dist-info → cadwyn-5.1.0.dist-info}/licenses/LICENSE +0 -0
cadwyn/structure/schemas.py
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Literal, Union, cast
|
|
4
4
|
|
|
5
5
|
from issubclass import issubclass as lenient_issubclass
|
|
6
6
|
from pydantic import AliasChoices, AliasPath, BaseModel, Field
|
|
7
7
|
from pydantic._internal._decorators import PydanticDescriptorProxy, unwrap_wrapped_function
|
|
8
8
|
from pydantic.fields import FieldInfo
|
|
9
9
|
|
|
10
|
-
from cadwyn._utils import
|
|
10
|
+
from cadwyn._utils import (
|
|
11
|
+
DATACLASS_SLOTS,
|
|
12
|
+
Sentinel,
|
|
13
|
+
fully_unwrap_decorator,
|
|
14
|
+
get_name_of_function_wrapped_in_pydantic_validator,
|
|
15
|
+
)
|
|
11
16
|
from cadwyn.exceptions import CadwynStructureError
|
|
12
17
|
|
|
13
18
|
from .common import _HiddenAttributeMixin
|
|
@@ -57,34 +62,34 @@ PossibleFieldAttributes = Literal[
|
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
# TODO: Add json_schema_extra as a breaking change in a major version
|
|
60
|
-
@dataclass(
|
|
65
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
61
66
|
class FieldChanges:
|
|
62
67
|
default: Any
|
|
63
|
-
alias: str
|
|
68
|
+
alias: Union[str, None]
|
|
64
69
|
default_factory: Any
|
|
65
|
-
alias_priority: int
|
|
66
|
-
validation_alias: str
|
|
67
|
-
serialization_alias: str
|
|
68
|
-
title: str
|
|
69
|
-
field_title_generator: Callable[[str, FieldInfo], str]
|
|
70
|
+
alias_priority: Union[int, None]
|
|
71
|
+
validation_alias: Union[str, AliasPath, AliasChoices, None]
|
|
72
|
+
serialization_alias: Union[str, None]
|
|
73
|
+
title: Union[str, None]
|
|
74
|
+
field_title_generator: Union[Callable[[str, FieldInfo], str], None]
|
|
70
75
|
description: str
|
|
71
|
-
examples: list[Any]
|
|
76
|
+
examples: Union[list[Any], None]
|
|
72
77
|
exclude: "AbstractSetIntStr | MappingIntStrAny | Any"
|
|
73
78
|
const: bool
|
|
74
79
|
deprecated: bool
|
|
75
|
-
frozen: bool
|
|
76
|
-
validate_default: bool
|
|
80
|
+
frozen: Union[bool, None]
|
|
81
|
+
validate_default: Union[bool, None]
|
|
77
82
|
repr: bool
|
|
78
|
-
init: bool
|
|
79
|
-
init_var: bool
|
|
80
|
-
kw_only: bool
|
|
83
|
+
init: Union[bool, None]
|
|
84
|
+
init_var: Union[bool, None]
|
|
85
|
+
kw_only: Union[bool, None]
|
|
81
86
|
fail_fast: bool
|
|
82
87
|
gt: float
|
|
83
88
|
ge: float
|
|
84
89
|
lt: float
|
|
85
90
|
le: float
|
|
86
91
|
strict: bool
|
|
87
|
-
coerce_numbers_to_str: bool
|
|
92
|
+
coerce_numbers_to_str: Union[bool, None]
|
|
88
93
|
multiple_of: float
|
|
89
94
|
allow_inf_nan: bool
|
|
90
95
|
max_digits: int
|
|
@@ -97,7 +102,7 @@ class FieldChanges:
|
|
|
97
102
|
discriminator: str
|
|
98
103
|
|
|
99
104
|
|
|
100
|
-
@dataclass(
|
|
105
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
101
106
|
class FieldHadInstruction(_HiddenAttributeMixin):
|
|
102
107
|
schema: type[BaseModel]
|
|
103
108
|
name: str
|
|
@@ -106,20 +111,20 @@ class FieldHadInstruction(_HiddenAttributeMixin):
|
|
|
106
111
|
new_name: str
|
|
107
112
|
|
|
108
113
|
|
|
109
|
-
@dataclass(
|
|
114
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
110
115
|
class FieldDidntHaveInstruction(_HiddenAttributeMixin):
|
|
111
116
|
schema: type[BaseModel]
|
|
112
117
|
name: str
|
|
113
118
|
attributes: tuple[str, ...]
|
|
114
119
|
|
|
115
120
|
|
|
116
|
-
@dataclass(
|
|
121
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
117
122
|
class FieldDidntExistInstruction(_HiddenAttributeMixin):
|
|
118
123
|
schema: type[BaseModel]
|
|
119
124
|
name: str
|
|
120
125
|
|
|
121
126
|
|
|
122
|
-
@dataclass(
|
|
127
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
123
128
|
class FieldExistedAsInstruction(_HiddenAttributeMixin):
|
|
124
129
|
schema: type[BaseModel]
|
|
125
130
|
name: str
|
|
@@ -127,7 +132,7 @@ class FieldExistedAsInstruction(_HiddenAttributeMixin):
|
|
|
127
132
|
|
|
128
133
|
|
|
129
134
|
# TODO (https://github.com/zmievsa/cadwyn/issues/112): Add an ability to add extras
|
|
130
|
-
@dataclass(
|
|
135
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
131
136
|
class AlterFieldInstructionFactory:
|
|
132
137
|
schema: type[BaseModel]
|
|
133
138
|
name: str
|
|
@@ -138,20 +143,20 @@ class AlterFieldInstructionFactory:
|
|
|
138
143
|
name: str = Sentinel,
|
|
139
144
|
type: Any = Sentinel,
|
|
140
145
|
default: Any = Sentinel,
|
|
141
|
-
alias: str
|
|
146
|
+
alias: Union[str, None] = Sentinel,
|
|
142
147
|
default_factory: Callable = Sentinel,
|
|
143
|
-
alias_priority: int = Sentinel,
|
|
144
|
-
validation_alias: str = Sentinel,
|
|
145
|
-
serialization_alias: str = Sentinel,
|
|
146
|
-
title: str = Sentinel,
|
|
147
|
-
field_title_generator: Callable[[str, FieldInfo], str] = Sentinel,
|
|
148
|
+
alias_priority: Union[int, None] = Sentinel,
|
|
149
|
+
validation_alias: Union[str, AliasPath, AliasChoices, None] = Sentinel,
|
|
150
|
+
serialization_alias: Union[str, None] = Sentinel,
|
|
151
|
+
title: Union[str, None] = Sentinel,
|
|
152
|
+
field_title_generator: Union[Callable[[str, FieldInfo], str], None] = Sentinel,
|
|
148
153
|
description: str = Sentinel,
|
|
149
|
-
examples: list[Any] = Sentinel,
|
|
154
|
+
examples: Union[list[Any], None] = Sentinel,
|
|
150
155
|
exclude: "AbstractSetIntStr | MappingIntStrAny | Any" = Sentinel,
|
|
151
156
|
const: bool = Sentinel,
|
|
152
157
|
deprecated: bool = Sentinel,
|
|
153
|
-
frozen: bool = Sentinel,
|
|
154
|
-
validate_default: bool = Sentinel,
|
|
158
|
+
frozen: Union[bool, None] = Sentinel,
|
|
159
|
+
validate_default: Union[bool, None] = Sentinel,
|
|
155
160
|
repr: bool = Sentinel,
|
|
156
161
|
init: bool = Sentinel,
|
|
157
162
|
init_var: bool = Sentinel,
|
|
@@ -175,6 +180,7 @@ class AlterFieldInstructionFactory:
|
|
|
175
180
|
discriminator: str = Sentinel,
|
|
176
181
|
) -> FieldHadInstruction:
|
|
177
182
|
return FieldHadInstruction(
|
|
183
|
+
is_hidden_from_changelog=False,
|
|
178
184
|
schema=self.schema,
|
|
179
185
|
name=self.name,
|
|
180
186
|
type=type,
|
|
@@ -225,22 +231,24 @@ class AlterFieldInstructionFactory:
|
|
|
225
231
|
raise CadwynStructureError(
|
|
226
232
|
f"Unknown attribute {attribute!r}. Are you sure it's a valid field attribute?"
|
|
227
233
|
)
|
|
228
|
-
return FieldDidntHaveInstruction(
|
|
234
|
+
return FieldDidntHaveInstruction(
|
|
235
|
+
is_hidden_from_changelog=False, schema=self.schema, name=self.name, attributes=attributes
|
|
236
|
+
)
|
|
229
237
|
|
|
230
238
|
@property
|
|
231
239
|
def didnt_exist(self) -> FieldDidntExistInstruction:
|
|
232
|
-
return FieldDidntExistInstruction(self.schema, name=self.name)
|
|
240
|
+
return FieldDidntExistInstruction(is_hidden_from_changelog=False, schema=self.schema, name=self.name)
|
|
233
241
|
|
|
234
242
|
def existed_as(
|
|
235
243
|
self,
|
|
236
244
|
*,
|
|
237
245
|
type: Any,
|
|
238
|
-
info: FieldInfo
|
|
246
|
+
info: Union[FieldInfo, Any, None] = None,
|
|
239
247
|
) -> FieldExistedAsInstruction:
|
|
240
248
|
if info is None:
|
|
241
249
|
info = cast(FieldInfo, Field())
|
|
242
250
|
info.annotation = type
|
|
243
|
-
return FieldExistedAsInstruction(self.schema, name=self.name, field=info)
|
|
251
|
+
return FieldExistedAsInstruction(is_hidden_from_changelog=False, schema=self.schema, name=self.name, field=info)
|
|
244
252
|
|
|
245
253
|
|
|
246
254
|
def _get_model_decorators(model: type[BaseModel]):
|
|
@@ -255,22 +263,22 @@ def _get_model_decorators(model: type[BaseModel]):
|
|
|
255
263
|
]
|
|
256
264
|
|
|
257
265
|
|
|
258
|
-
@dataclass(
|
|
266
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
259
267
|
class ValidatorExistedInstruction:
|
|
260
268
|
schema: type[BaseModel]
|
|
261
|
-
validator: Callable[..., Any]
|
|
269
|
+
validator: Union[Callable[..., Any], PydanticDescriptorProxy]
|
|
262
270
|
|
|
263
271
|
|
|
264
|
-
@dataclass(
|
|
272
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
265
273
|
class ValidatorDidntExistInstruction:
|
|
266
274
|
schema: type[BaseModel]
|
|
267
275
|
name: str
|
|
268
276
|
|
|
269
277
|
|
|
270
|
-
@dataclass(
|
|
278
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
271
279
|
class AlterValidatorInstructionFactory:
|
|
272
280
|
schema: type[BaseModel]
|
|
273
|
-
func: Callable[..., Any]
|
|
281
|
+
func: Union[Callable[..., Any], PydanticDescriptorProxy]
|
|
274
282
|
|
|
275
283
|
@property
|
|
276
284
|
def existed(self) -> ValidatorExistedInstruction:
|
|
@@ -278,26 +286,28 @@ class AlterValidatorInstructionFactory:
|
|
|
278
286
|
|
|
279
287
|
@property
|
|
280
288
|
def didnt_exist(self) -> ValidatorDidntExistInstruction:
|
|
281
|
-
return ValidatorDidntExistInstruction(
|
|
289
|
+
return ValidatorDidntExistInstruction(
|
|
290
|
+
self.schema, get_name_of_function_wrapped_in_pydantic_validator(self.func)
|
|
291
|
+
)
|
|
282
292
|
|
|
283
293
|
|
|
284
|
-
AlterSchemaSubInstruction =
|
|
285
|
-
FieldHadInstruction
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
294
|
+
AlterSchemaSubInstruction = Union[
|
|
295
|
+
FieldHadInstruction,
|
|
296
|
+
FieldDidntHaveInstruction,
|
|
297
|
+
FieldDidntExistInstruction,
|
|
298
|
+
FieldExistedAsInstruction,
|
|
299
|
+
ValidatorExistedInstruction,
|
|
300
|
+
ValidatorDidntExistInstruction,
|
|
301
|
+
]
|
|
292
302
|
|
|
293
303
|
|
|
294
|
-
@dataclass(
|
|
304
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
295
305
|
class SchemaHadInstruction(_HiddenAttributeMixin):
|
|
296
306
|
schema: type[BaseModel]
|
|
297
307
|
name: str
|
|
298
308
|
|
|
299
309
|
|
|
300
|
-
@dataclass(
|
|
310
|
+
@dataclass(**DATACLASS_SLOTS)
|
|
301
311
|
class AlterSchemaInstructionFactory:
|
|
302
312
|
schema: type[BaseModel]
|
|
303
313
|
|
|
@@ -305,9 +315,9 @@ class AlterSchemaInstructionFactory:
|
|
|
305
315
|
return AlterFieldInstructionFactory(self.schema, name)
|
|
306
316
|
|
|
307
317
|
def validator(
|
|
308
|
-
self, func: "Callable[..., Any]
|
|
318
|
+
self, func: "Union[Callable[..., Any], classmethod[Any, Any, Any], PydanticDescriptorProxy]", /
|
|
309
319
|
) -> AlterValidatorInstructionFactory:
|
|
310
|
-
func = cast(Callable
|
|
320
|
+
func = cast(Union[Callable[..., Any], PydanticDescriptorProxy], unwrap_wrapped_function(func))
|
|
311
321
|
|
|
312
322
|
if not isinstance(func, PydanticDescriptorProxy):
|
|
313
323
|
if hasattr(func, "__self__"):
|
|
@@ -321,7 +331,7 @@ class AlterSchemaInstructionFactory:
|
|
|
321
331
|
return AlterValidatorInstructionFactory(self.schema, func)
|
|
322
332
|
|
|
323
333
|
def had(self, *, name: str) -> SchemaHadInstruction:
|
|
324
|
-
return SchemaHadInstruction(self.schema, name)
|
|
334
|
+
return SchemaHadInstruction(is_hidden_from_changelog=False, schema=self.schema, name=name)
|
|
325
335
|
|
|
326
336
|
|
|
327
337
|
def schema(model: type[BaseModel], /) -> AlterSchemaInstructionFactory:
|
cadwyn/structure/versions.py
CHANGED
|
@@ -8,7 +8,7 @@ from contextlib import AsyncExitStack
|
|
|
8
8
|
from contextvars import ContextVar
|
|
9
9
|
from datetime import date
|
|
10
10
|
from enum import Enum
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import TYPE_CHECKING, ClassVar, Union
|
|
12
12
|
|
|
13
13
|
from fastapi import BackgroundTasks, HTTPException, params
|
|
14
14
|
from fastapi import Request as FastapiRequest
|
|
@@ -23,7 +23,7 @@ from fastapi.routing import APIRoute, _prepare_response_content
|
|
|
23
23
|
from pydantic import BaseModel
|
|
24
24
|
from pydantic_core import PydanticUndefined
|
|
25
25
|
from starlette._utils import is_async_callable
|
|
26
|
-
from typing_extensions import assert_never, deprecated
|
|
26
|
+
from typing_extensions import Any, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
|
|
27
27
|
|
|
28
28
|
from cadwyn._utils import classproperty
|
|
29
29
|
from cadwyn.exceptions import (
|
|
@@ -51,22 +51,28 @@ _CADWYN_REQUEST_PARAM_NAME = "cadwyn_request_param"
|
|
|
51
51
|
_CADWYN_RESPONSE_PARAM_NAME = "cadwyn_response_param"
|
|
52
52
|
_P = ParamSpec("_P")
|
|
53
53
|
_R = TypeVar("_R")
|
|
54
|
-
PossibleInstructions: TypeAlias =
|
|
55
|
-
AlterSchemaSubInstruction
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
| SchemaHadInstruction
|
|
59
|
-
| staticmethod
|
|
60
|
-
)
|
|
61
|
-
APIVersionVarType: TypeAlias = ContextVar[VersionType | None] | ContextVar[VersionType]
|
|
54
|
+
PossibleInstructions: TypeAlias = Union[
|
|
55
|
+
AlterSchemaSubInstruction, AlterEndpointSubInstruction, AlterEnumSubInstruction, SchemaHadInstruction, staticmethod
|
|
56
|
+
]
|
|
57
|
+
APIVersionVarType: TypeAlias = Union[ContextVar[Union[VersionType, None]], ContextVar[VersionType]]
|
|
62
58
|
IdentifierPythonPath = str
|
|
63
59
|
|
|
64
60
|
|
|
61
|
+
if TYPE_CHECKING:
|
|
62
|
+
_AlterSchemaSubInstructionArgs = AlterSchemaSubInstruction
|
|
63
|
+
_AlterEnumSubInstructionArgs = AlterEnumSubInstruction
|
|
64
|
+
_AlterEndpointSubInstructionArgs = AlterEndpointSubInstruction
|
|
65
|
+
else:
|
|
66
|
+
_AlterSchemaSubInstructionArgs = get_args(AlterSchemaSubInstruction)
|
|
67
|
+
_AlterEnumSubInstructionArgs = get_args(AlterEnumSubInstruction)
|
|
68
|
+
_AlterEndpointSubInstructionArgs = get_args(AlterEndpointSubInstruction)
|
|
69
|
+
|
|
70
|
+
|
|
65
71
|
class VersionChange:
|
|
66
72
|
description: ClassVar[str] = Sentinel
|
|
67
73
|
is_hidden_from_changelog: bool = False
|
|
68
74
|
instructions_to_migrate_to_previous_version: ClassVar[Sequence[PossibleInstructions]] = Sentinel
|
|
69
|
-
alter_schema_instructions: ClassVar[list[AlterSchemaSubInstruction
|
|
75
|
+
alter_schema_instructions: ClassVar[list[Union[AlterSchemaSubInstruction, SchemaHadInstruction]]] = Sentinel
|
|
70
76
|
alter_enum_instructions: ClassVar[list[AlterEnumSubInstruction]] = Sentinel
|
|
71
77
|
alter_endpoint_instructions: ClassVar[list[AlterEndpointSubInstruction]] = Sentinel
|
|
72
78
|
alter_request_by_schema_instructions: ClassVar[dict[type[BaseModel], list[_AlterRequestBySchemaInstruction]]] = (
|
|
@@ -75,7 +81,7 @@ class VersionChange:
|
|
|
75
81
|
alter_request_by_path_instructions: ClassVar[dict[str, list[_AlterRequestByPathInstruction]]] = Sentinel
|
|
76
82
|
alter_response_by_schema_instructions: ClassVar[dict[type, list[_AlterResponseBySchemaInstruction]]] = Sentinel
|
|
77
83
|
alter_response_by_path_instructions: ClassVar[dict[str, list[_AlterResponseByPathInstruction]]] = Sentinel
|
|
78
|
-
_bound_version_bundle: "VersionBundle
|
|
84
|
+
_bound_version_bundle: "Union[VersionBundle, None]"
|
|
79
85
|
|
|
80
86
|
def __init_subclass__(cls, _abstract: bool = False) -> None:
|
|
81
87
|
super().__init_subclass__()
|
|
@@ -112,11 +118,13 @@ class VersionChange:
|
|
|
112
118
|
cls.alter_response_by_schema_instructions = defaultdict(list)
|
|
113
119
|
cls.alter_response_by_path_instructions = defaultdict(list)
|
|
114
120
|
for alter_instruction in cls.instructions_to_migrate_to_previous_version:
|
|
115
|
-
if isinstance(alter_instruction, SchemaHadInstruction
|
|
121
|
+
if isinstance(alter_instruction, SchemaHadInstruction) or isinstance( # noqa: SIM101
|
|
122
|
+
alter_instruction, _AlterSchemaSubInstructionArgs
|
|
123
|
+
):
|
|
116
124
|
cls.alter_schema_instructions.append(alter_instruction)
|
|
117
|
-
elif isinstance(alter_instruction,
|
|
125
|
+
elif isinstance(alter_instruction, _AlterEnumSubInstructionArgs):
|
|
118
126
|
cls.alter_enum_instructions.append(alter_instruction)
|
|
119
|
-
elif isinstance(alter_instruction,
|
|
127
|
+
elif isinstance(alter_instruction, _AlterEndpointSubInstructionArgs):
|
|
120
128
|
cls.alter_endpoint_instructions.append(alter_instruction)
|
|
121
129
|
elif isinstance(alter_instruction, staticmethod): # pragma: no cover
|
|
122
130
|
raise NotImplementedError(f'"{alter_instruction}" is an unacceptable version change instruction')
|
|
@@ -139,17 +147,19 @@ class VersionChange:
|
|
|
139
147
|
f"Attribute 'instructions_to_migrate_to_previous_version' must be a sequence in '{cls.__name__}'.",
|
|
140
148
|
)
|
|
141
149
|
for instruction in cls.instructions_to_migrate_to_previous_version:
|
|
142
|
-
if not isinstance(instruction, PossibleInstructions):
|
|
150
|
+
if not isinstance(instruction, get_args(PossibleInstructions)):
|
|
143
151
|
raise CadwynStructureError(
|
|
144
152
|
f"Instruction '{instruction}' is not allowed. Please, use the correct instruction types",
|
|
145
153
|
)
|
|
146
154
|
for attr_name, attr_value in cls.__dict__.items():
|
|
147
155
|
if not isinstance(
|
|
148
156
|
attr_value,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
157
|
+
(
|
|
158
|
+
_AlterRequestBySchemaInstruction,
|
|
159
|
+
_AlterRequestByPathInstruction,
|
|
160
|
+
_AlterResponseBySchemaInstruction,
|
|
161
|
+
_AlterResponseByPathInstruction,
|
|
162
|
+
),
|
|
153
163
|
) and attr_name not in {
|
|
154
164
|
"description",
|
|
155
165
|
"side_effects",
|
|
@@ -201,7 +211,7 @@ class VersionChangeWithSideEffects(VersionChange, _abstract=True):
|
|
|
201
211
|
|
|
202
212
|
|
|
203
213
|
class Version:
|
|
204
|
-
def __init__(self, value: str
|
|
214
|
+
def __init__(self, value: Union[str, date], *changes: type[VersionChange]) -> None:
|
|
205
215
|
super().__init__()
|
|
206
216
|
|
|
207
217
|
if isinstance(value, date):
|
|
@@ -249,10 +259,10 @@ def get_cls_pythonpath(cls: type) -> IdentifierPythonPath:
|
|
|
249
259
|
class VersionBundle:
|
|
250
260
|
def __init__(
|
|
251
261
|
self,
|
|
252
|
-
latest_version_or_head_version: Version
|
|
262
|
+
latest_version_or_head_version: Union[Version, HeadVersion],
|
|
253
263
|
/,
|
|
254
264
|
*other_versions: Version,
|
|
255
|
-
api_version_var: ContextVar[VersionType
|
|
265
|
+
api_version_var: Union[ContextVar[Union[VersionType, None]], None] = None,
|
|
256
266
|
) -> None:
|
|
257
267
|
super().__init__()
|
|
258
268
|
|
|
@@ -342,7 +352,7 @@ class VersionBundle:
|
|
|
342
352
|
|
|
343
353
|
async def _migrate_request(
|
|
344
354
|
self,
|
|
345
|
-
body_type: type[BaseModel]
|
|
355
|
+
body_type: Union[type[BaseModel], None],
|
|
346
356
|
head_dependant: Dependant,
|
|
347
357
|
path: str,
|
|
348
358
|
request: FastapiRequest,
|
|
@@ -353,7 +363,7 @@ class VersionBundle:
|
|
|
353
363
|
*,
|
|
354
364
|
exit_stack: AsyncExitStack,
|
|
355
365
|
embed_body_fields: bool,
|
|
356
|
-
background_tasks: BackgroundTasks
|
|
366
|
+
background_tasks: Union[BackgroundTasks, None],
|
|
357
367
|
) -> dict[str, Any]:
|
|
358
368
|
method = request.method
|
|
359
369
|
|
|
@@ -417,22 +427,22 @@ class VersionBundle:
|
|
|
417
427
|
# TODO (https://github.com/zmievsa/cadwyn/issues/113): Refactor this function and all functions it calls.
|
|
418
428
|
def _versioned(
|
|
419
429
|
self,
|
|
420
|
-
head_body_field: type[BaseModel]
|
|
421
|
-
module_body_field_name: str
|
|
430
|
+
head_body_field: Union[type[BaseModel], None],
|
|
431
|
+
module_body_field_name: Union[str, None],
|
|
422
432
|
route: APIRoute,
|
|
423
433
|
head_route: APIRoute,
|
|
424
434
|
dependant_for_request_migrations: Dependant,
|
|
425
435
|
*,
|
|
426
436
|
request_param_name: str,
|
|
427
|
-
background_tasks_param_name: str
|
|
437
|
+
background_tasks_param_name: Union[str, None],
|
|
428
438
|
response_param_name: str,
|
|
429
|
-
) -> Callable[[Endpoint[_P, _R]], Endpoint[_P, _R]]:
|
|
430
|
-
def wrapper(endpoint: Endpoint[_P, _R]) -> Endpoint[_P, _R]:
|
|
439
|
+
) -> "Callable[[Endpoint[_P, _R]], Endpoint[_P, _R]]":
|
|
440
|
+
def wrapper(endpoint: "Endpoint[_P, _R]") -> "Endpoint[_P, _R]":
|
|
431
441
|
@functools.wraps(endpoint)
|
|
432
442
|
async def decorator(*args: Any, **kwargs: Any) -> _R:
|
|
433
443
|
request_param: FastapiRequest = kwargs[request_param_name]
|
|
434
444
|
response_param: FastapiResponse = kwargs[response_param_name]
|
|
435
|
-
background_tasks: BackgroundTasks
|
|
445
|
+
background_tasks: Union[BackgroundTasks, None] = kwargs.get(
|
|
436
446
|
background_tasks_param_name, # pyright: ignore[reportArgumentType]
|
|
437
447
|
)
|
|
438
448
|
method = request_param.method
|
|
@@ -498,9 +508,9 @@ class VersionBundle:
|
|
|
498
508
|
kwargs.pop(response_param_name)
|
|
499
509
|
try:
|
|
500
510
|
if is_async_callable(func_to_get_response_from):
|
|
501
|
-
response_or_response_body: FastapiResponse
|
|
511
|
+
response_or_response_body: Union[FastapiResponse, object] = await func_to_get_response_from(**kwargs)
|
|
502
512
|
else:
|
|
503
|
-
response_or_response_body: FastapiResponse
|
|
513
|
+
response_or_response_body: Union[FastapiResponse, object] = await run_in_threadpool(
|
|
504
514
|
func_to_get_response_from,
|
|
505
515
|
**kwargs,
|
|
506
516
|
)
|
|
@@ -520,11 +530,11 @@ class VersionBundle:
|
|
|
520
530
|
# TODO (https://github.com/zmievsa/cadwyn/issues/126): Add support for migrating `FileResponse`
|
|
521
531
|
# Starlette breaks Liskov Substitution principle and
|
|
522
532
|
# doesn't define `body` for `StreamingResponse` and `FileResponse`
|
|
523
|
-
if isinstance(response_or_response_body, StreamingResponse
|
|
533
|
+
if isinstance(response_or_response_body, (StreamingResponse, FileResponse)):
|
|
524
534
|
body = None
|
|
525
535
|
elif response_or_response_body.body:
|
|
526
536
|
if (isinstance(response_or_response_body, JSONResponse) or raised_exception is not None) and isinstance(
|
|
527
|
-
response_or_response_body.body, str
|
|
537
|
+
response_or_response_body.body, (str, bytes)
|
|
528
538
|
):
|
|
529
539
|
body = json.loads(response_or_response_body.body)
|
|
530
540
|
elif isinstance(response_or_response_body.body, bytes):
|
|
@@ -609,8 +619,8 @@ class VersionBundle:
|
|
|
609
619
|
|
|
610
620
|
async def _convert_endpoint_kwargs_to_version(
|
|
611
621
|
self,
|
|
612
|
-
head_body_field: type[BaseModel]
|
|
613
|
-
body_field_alias: str
|
|
622
|
+
head_body_field: Union[type[BaseModel], None],
|
|
623
|
+
body_field_alias: Union[str, None],
|
|
614
624
|
head_dependant: Dependant,
|
|
615
625
|
request_param_name: str,
|
|
616
626
|
kwargs: dict[str, Any],
|
|
@@ -620,7 +630,7 @@ class VersionBundle:
|
|
|
620
630
|
*,
|
|
621
631
|
exit_stack: AsyncExitStack,
|
|
622
632
|
embed_body_fields: bool,
|
|
623
|
-
background_tasks: BackgroundTasks
|
|
633
|
+
background_tasks: Union[BackgroundTasks, None],
|
|
624
634
|
) -> dict[str, Any]:
|
|
625
635
|
request: FastapiRequest = kwargs[request_param_name]
|
|
626
636
|
if request_param_name == _CADWYN_REQUEST_PARAM_NAME:
|
|
@@ -637,7 +647,7 @@ class VersionBundle:
|
|
|
637
647
|
and body_field_alias is not None
|
|
638
648
|
and body_field_alias in kwargs
|
|
639
649
|
):
|
|
640
|
-
raw_body: BaseModel
|
|
650
|
+
raw_body: Union[BaseModel, None] = kwargs.get(body_field_alias)
|
|
641
651
|
if raw_body is None: # pragma: no cover # This is likely an impossible case but we would like to be safe
|
|
642
652
|
body = None
|
|
643
653
|
# It means we have a dict or a list instead of a full model.
|
|
@@ -673,7 +683,7 @@ class VersionBundle:
|
|
|
673
683
|
|
|
674
684
|
# We use this instead of `.body()` to automatically guess body type and load the correct body, even if it's a form
|
|
675
685
|
async def _get_body(
|
|
676
|
-
request: FastapiRequest, body_field: ModelField
|
|
686
|
+
request: FastapiRequest, body_field: Union[ModelField, None], exit_stack: AsyncExitStack
|
|
677
687
|
): # pragma: no cover # This is from fastapi
|
|
678
688
|
is_body_form = body_field and isinstance(body_field.field_info, params.Form)
|
|
679
689
|
try:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.0
|
|
4
4
|
Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
|
|
5
5
|
Project-URL: Source code, https://github.com/zmievsa/cadwyn
|
|
6
6
|
Project-URL: Documentation, https://docs.cadwyn.dev
|
|
7
7
|
Author-email: Stanislav Zmiev <zmievsa@gmail.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Keywords: api,api-versioning,code-generation,fastapi,hints,json-schema,pydantic,python,python310,python311,python312,python313,stripe,versioning
|
|
10
|
+
Keywords: api,api-versioning,code-generation,fastapi,hints,json-schema,pydantic,python,python310,python311,python312,python313,python39,stripe,versioning
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: Web Environment
|
|
13
13
|
Classifier: Framework :: AsyncIO
|
|
@@ -20,6 +20,7 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
20
20
|
Classifier: Operating System :: OS Independent
|
|
21
21
|
Classifier: Programming Language :: Python
|
|
22
22
|
Classifier: Programming Language :: Python :: 3
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
24
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
25
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
26
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -32,7 +33,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
32
33
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
33
34
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
34
35
|
Classifier: Typing :: Typed
|
|
35
|
-
Requires-Python: >=3.
|
|
36
|
+
Requires-Python: >=3.9
|
|
36
37
|
Requires-Dist: backports-strenum<2,>=1.3.1; python_version < '3.11'
|
|
37
38
|
Requires-Dist: fastapi>=0.112.3
|
|
38
39
|
Requires-Dist: issubclass>=0.1.2
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
cadwyn/__init__.py,sha256=gWsp-oNP5LhBqWF_rsrv6sEYP9KchTu8xhxAR8uaPm0,1035
|
|
2
|
+
cadwyn/__main__.py,sha256=fGoKJPNVueqqXW4rqwmRUBBMoDGeLEyATdT-rel5Pvs,2449
|
|
3
|
+
cadwyn/_asts.py,sha256=QvqZmDdwH8U-Ocpj9vYR6MRrIANF8ugk9oZgAo76qLs,5223
|
|
4
|
+
cadwyn/_importer.py,sha256=QV6HqODCG9K2oL4Vc15fAqL2-plMvUWw_cgaj4Ln4C8,1075
|
|
5
|
+
cadwyn/_render.py,sha256=7jb32Cnbf6xJicYBGRuhGz4U5LUzo8InZbf-gENgnEw,5537
|
|
6
|
+
cadwyn/_utils.py,sha256=q_mTtMKTNTDzqCza67XST-jaPSfuTgnFLmOe0dlGeYY,2295
|
|
7
|
+
cadwyn/applications.py,sha256=YyESoMwQMq9jxqai6lNrsL0BAKxjTKW5lm3Se_U81j4,20391
|
|
8
|
+
cadwyn/changelogs.py,sha256=aBTlsZ8PQpw9t4sSyezNTYDs6CMPtzIGulgAHA1ELPs,19622
|
|
9
|
+
cadwyn/exceptions.py,sha256=gLCikeUPeLJwVjM8_DoSTIFHwmNI7n7vw1atpgHvbMU,1803
|
|
10
|
+
cadwyn/middleware.py,sha256=jtcysj66Fck_3EteK0zLJCOTMms3g6avi3U8lV-roQI,4316
|
|
11
|
+
cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
cadwyn/route_generation.py,sha256=e7mnVqtvTx1Oh_khmrM0dAowhVUOO1K4vaVVEGgXgrY,25230
|
|
13
|
+
cadwyn/routing.py,sha256=EkV38cDQFAtR1M_fGWeq81lYaSPuDK4Pr8fjTTJVZvY,6912
|
|
14
|
+
cadwyn/schema_generation.py,sha256=J9HWrxwmsozR6RwhLDhL5cdy_hnwJ0HmusIKhQ7yt8I,44912
|
|
15
|
+
cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
cadwyn/static/docs.html,sha256=WNm5ANJVy51TcIUFOaqKf1Z8eF86CC85TTHPxACtkzw,3455
|
|
17
|
+
cadwyn/structure/__init__.py,sha256=Wgvjdq3vfl9Yhe-BkcFGAMi_Co11YOfTmJQqgF5Gzx4,655
|
|
18
|
+
cadwyn/structure/common.py,sha256=YuyfYMxkJcj2c5SFh9teBoEC2xLO5_2QjPzYjwdZcTs,478
|
|
19
|
+
cadwyn/structure/data.py,sha256=NWURVnP_84VI2ugp9ppo0Ofyve3pVYjymF9K82Jh-SA,7791
|
|
20
|
+
cadwyn/structure/endpoints.py,sha256=zUgzglNhBPnmWdJ03A8pFT4zPs_lj8nQ7c7Uo2d-ejU,6246
|
|
21
|
+
cadwyn/structure/enums.py,sha256=4FCc9aniLE3VuWAVIacrNP_FWxTIUm9JkeeHA_zZdwQ,1254
|
|
22
|
+
cadwyn/structure/schemas.py,sha256=80AUbko1cksXh7qPBmnknDFLK52xr4kw9HfHeXqcw1I,10813
|
|
23
|
+
cadwyn/structure/versions.py,sha256=9ihqt27upAOWXCGYou2rCJ7ZqgxjJshKOFpx2LuLpBE,33905
|
|
24
|
+
cadwyn-5.1.0.dist-info/METADATA,sha256=VJxbSg9GgUty0MNmjf9gkvLnMrx5R5UFq-FseQ3yWOk,4562
|
|
25
|
+
cadwyn-5.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
cadwyn-5.1.0.dist-info/entry_points.txt,sha256=mGX8wl-Xfhpr5M93SUmkykaqinUaYAvW9rtDSX54gx0,47
|
|
27
|
+
cadwyn-5.1.0.dist-info/licenses/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
28
|
+
cadwyn-5.1.0.dist-info/RECORD,,
|
cadwyn-5.0.0.dist-info/RECORD
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
cadwyn/__init__.py,sha256=gWsp-oNP5LhBqWF_rsrv6sEYP9KchTu8xhxAR8uaPm0,1035
|
|
2
|
-
cadwyn/__main__.py,sha256=fGoKJPNVueqqXW4rqwmRUBBMoDGeLEyATdT-rel5Pvs,2449
|
|
3
|
-
cadwyn/_asts.py,sha256=kd6Ngwr8c-uTNx-R-pP48Scf0OdjrTyEeSYe5DLoGqo,5095
|
|
4
|
-
cadwyn/_importer.py,sha256=QV6HqODCG9K2oL4Vc15fAqL2-plMvUWw_cgaj4Ln4C8,1075
|
|
5
|
-
cadwyn/_render.py,sha256=VArS68879hXRntMKCQJ7LUpCv8aToavHZV1JuBFmtfY,5513
|
|
6
|
-
cadwyn/_utils.py,sha256=rlD1SkswtZ1bWgKj6PLYbVaHYkD-NzY4iUcHdPd2Y68,1475
|
|
7
|
-
cadwyn/applications.py,sha256=bBO5kK-lDbjk3Rrbx8TdSY5EHDq4BkwlOzQruZ4Vj00,20128
|
|
8
|
-
cadwyn/changelogs.py,sha256=-ft0Rpx_xDoVWNuAH8NYmUpE9UnSZfreyL-Qa2fP78Y,20004
|
|
9
|
-
cadwyn/exceptions.py,sha256=gLCikeUPeLJwVjM8_DoSTIFHwmNI7n7vw1atpgHvbMU,1803
|
|
10
|
-
cadwyn/middleware.py,sha256=lP8bFHHhXEMjOyDdljxiC2fH5K826qPe07qERq1nnG8,4274
|
|
11
|
-
cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
cadwyn/route_generation.py,sha256=EclCM9CI05ZRQW1qa052cBnF3Zb8Qj4lbIvHQSy0g_8,25152
|
|
13
|
-
cadwyn/routing.py,sha256=cG3ieQ9NlF-PvslT9fy5AHBNco4lMgDoqQu42klnCLk,6899
|
|
14
|
-
cadwyn/schema_generation.py,sha256=pM5bM0Tfi10ahSWRaD4h3zjzH-ERRkySh1iVURwf010,42089
|
|
15
|
-
cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
cadwyn/static/docs.html,sha256=WNm5ANJVy51TcIUFOaqKf1Z8eF86CC85TTHPxACtkzw,3455
|
|
17
|
-
cadwyn/structure/__init__.py,sha256=Wgvjdq3vfl9Yhe-BkcFGAMi_Co11YOfTmJQqgF5Gzx4,655
|
|
18
|
-
cadwyn/structure/common.py,sha256=3UEbxzAjDfHT2GPMQ3tNA3pEqJd9ZnDclqsj2ZDpzv4,399
|
|
19
|
-
cadwyn/structure/data.py,sha256=uViRW4uOOonXZj90hOlPNk02AIwp0fvDNoF8M5_CEes,7707
|
|
20
|
-
cadwyn/structure/endpoints.py,sha256=8lrc4xanCt7gat106yYRIQC0TNxzFkLF-urIml_d_X0,5934
|
|
21
|
-
cadwyn/structure/enums.py,sha256=bZL-iUOUFi9ZYlMZJw-tAix2yrgCp3gH3N2gwO44LUU,1043
|
|
22
|
-
cadwyn/structure/schemas.py,sha256=v_wDTn84SgHVDFDlTgoalUzBXpDbT5Hl73Skp0UyGAM,10081
|
|
23
|
-
cadwyn/structure/versions.py,sha256=p0Ui00RX8DW9M-wndiaXBK2t85Bd4ypGigPCfVZ9PiE,33167
|
|
24
|
-
cadwyn-5.0.0.dist-info/METADATA,sha256=SNnhEGuX_BVE8C5IM-PDBTyrfBvRb2aR2S_DrwxCoJ4,4504
|
|
25
|
-
cadwyn-5.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
-
cadwyn-5.0.0.dist-info/entry_points.txt,sha256=mGX8wl-Xfhpr5M93SUmkykaqinUaYAvW9rtDSX54gx0,47
|
|
27
|
-
cadwyn-5.0.0.dist-info/licenses/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
28
|
-
cadwyn-5.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|