cadwyn 5.4.6__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.
- cadwyn/__init__.py +44 -0
- cadwyn/__main__.py +78 -0
- cadwyn/_asts.py +155 -0
- cadwyn/_importer.py +31 -0
- cadwyn/_internal/__init__.py +0 -0
- cadwyn/_internal/context_vars.py +9 -0
- cadwyn/_render.py +155 -0
- cadwyn/_utils.py +79 -0
- cadwyn/applications.py +484 -0
- cadwyn/changelogs.py +503 -0
- cadwyn/dependencies.py +5 -0
- cadwyn/exceptions.py +78 -0
- cadwyn/middleware.py +131 -0
- cadwyn/py.typed +0 -0
- cadwyn/route_generation.py +536 -0
- cadwyn/routing.py +159 -0
- cadwyn/schema_generation.py +1162 -0
- cadwyn/static/__init__.py +0 -0
- cadwyn/static/docs.html +136 -0
- cadwyn/structure/__init__.py +31 -0
- cadwyn/structure/common.py +18 -0
- cadwyn/structure/data.py +249 -0
- cadwyn/structure/endpoints.py +170 -0
- cadwyn/structure/enums.py +42 -0
- cadwyn/structure/schemas.py +338 -0
- cadwyn/structure/versions.py +756 -0
- cadwyn-5.4.6.dist-info/METADATA +90 -0
- cadwyn-5.4.6.dist-info/RECORD +31 -0
- cadwyn-5.4.6.dist-info/WHEEL +4 -0
- cadwyn-5.4.6.dist-info/entry_points.txt +2 -0
- cadwyn-5.4.6.dist-info/licenses/LICENSE +21 -0
cadwyn/changelogs.py
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import sys
|
|
3
|
+
from enum import auto
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast, get_args
|
|
6
|
+
|
|
7
|
+
from fastapi.openapi.constants import REF_TEMPLATE
|
|
8
|
+
from fastapi.openapi.utils import (
|
|
9
|
+
get_fields_from_routes,
|
|
10
|
+
get_openapi,
|
|
11
|
+
)
|
|
12
|
+
from fastapi.routing import APIRoute
|
|
13
|
+
from pydantic import BaseModel, Field, RootModel
|
|
14
|
+
|
|
15
|
+
from cadwyn._asts import GenericAliasUnionArgs
|
|
16
|
+
from cadwyn._utils import ZIP_STRICT_FALSE, Sentinel
|
|
17
|
+
from cadwyn.route_generation import _get_routes
|
|
18
|
+
from cadwyn.routing import _RootCadwynAPIRouter
|
|
19
|
+
from cadwyn.schema_generation import SchemaGenerator, _change_field_in_model, generate_versioned_models
|
|
20
|
+
from cadwyn.structure.versions import PossibleInstructions, VersionBundle, VersionChange, VersionChangeWithSideEffects
|
|
21
|
+
|
|
22
|
+
from .structure.endpoints import (
|
|
23
|
+
EndpointDidntExistInstruction,
|
|
24
|
+
EndpointExistedInstruction,
|
|
25
|
+
EndpointHadInstruction,
|
|
26
|
+
)
|
|
27
|
+
from .structure.enums import EnumDidntHaveMembersInstruction, EnumHadMembersInstruction
|
|
28
|
+
from .structure.schemas import (
|
|
29
|
+
FieldDidntExistInstruction,
|
|
30
|
+
FieldDidntHaveInstruction,
|
|
31
|
+
FieldExistedAsInstruction,
|
|
32
|
+
FieldHadInstruction,
|
|
33
|
+
SchemaHadInstruction,
|
|
34
|
+
ValidatorDidntExistInstruction,
|
|
35
|
+
ValidatorExistedInstruction,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from fastapi._compat import ModelField
|
|
40
|
+
|
|
41
|
+
if sys.version_info >= (3, 11): # pragma: no cover
|
|
42
|
+
from enum import StrEnum
|
|
43
|
+
else: # pragma: no cover
|
|
44
|
+
from backports.strenum import StrEnum
|
|
45
|
+
|
|
46
|
+
_logger = getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
T = TypeVar("T", bound=Union[PossibleInstructions, type[VersionChange]])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def hidden(instruction_or_version_change: T) -> T:
|
|
52
|
+
if isinstance(
|
|
53
|
+
instruction_or_version_change, (staticmethod, ValidatorDidntExistInstruction, ValidatorExistedInstruction)
|
|
54
|
+
):
|
|
55
|
+
return instruction_or_version_change
|
|
56
|
+
|
|
57
|
+
instruction_or_version_change.is_hidden_from_changelog = True
|
|
58
|
+
return instruction_or_version_change
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _generate_changelog(versions: VersionBundle, router: _RootCadwynAPIRouter) -> "CadwynChangelogResource":
|
|
62
|
+
changelog = CadwynChangelogResource()
|
|
63
|
+
schema_generators = generate_versioned_models(versions)
|
|
64
|
+
for version, older_version in zip(versions, versions.versions[1:], **ZIP_STRICT_FALSE):
|
|
65
|
+
routes_from_newer_version = router.versioned_routers[version.value].routes
|
|
66
|
+
schemas_from_older_version = get_fields_from_routes(router.versioned_routers[older_version.value].routes)
|
|
67
|
+
version_changelog = CadwynVersion(value=version.value)
|
|
68
|
+
generator_from_newer_version = schema_generators[version.value]
|
|
69
|
+
generator_from_older_version = schema_generators[older_version.value]
|
|
70
|
+
for version_change in version.changes:
|
|
71
|
+
if version_change.is_hidden_from_changelog:
|
|
72
|
+
continue
|
|
73
|
+
version_change_changelog = CadwynVersionChange(
|
|
74
|
+
description=version_change.description,
|
|
75
|
+
side_effects=isinstance(version_change, VersionChangeWithSideEffects),
|
|
76
|
+
)
|
|
77
|
+
for instruction in [
|
|
78
|
+
*version_change.alter_endpoint_instructions,
|
|
79
|
+
*version_change.alter_enum_instructions,
|
|
80
|
+
*version_change.alter_schema_instructions,
|
|
81
|
+
]:
|
|
82
|
+
if (
|
|
83
|
+
isinstance(instruction, (ValidatorDidntExistInstruction, ValidatorExistedInstruction))
|
|
84
|
+
or instruction.is_hidden_from_changelog
|
|
85
|
+
):
|
|
86
|
+
continue
|
|
87
|
+
changelog_entry = _convert_version_change_instruction_to_changelog_entry(
|
|
88
|
+
instruction,
|
|
89
|
+
version_change,
|
|
90
|
+
generator_from_newer_version,
|
|
91
|
+
generator_from_older_version,
|
|
92
|
+
schemas_from_older_version,
|
|
93
|
+
cast("list[APIRoute]", routes_from_newer_version),
|
|
94
|
+
)
|
|
95
|
+
if changelog_entry is not None: # pragma: no branch # This should never happen
|
|
96
|
+
version_change_changelog.instructions.append(CadwynVersionChangeInstruction(changelog_entry))
|
|
97
|
+
version_changelog.changes.append(version_change_changelog)
|
|
98
|
+
changelog.versions.append(version_changelog)
|
|
99
|
+
return changelog
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _get_older_field_name(
|
|
103
|
+
schema: type[BaseModel], new_field_name: str, generator_from_older_version: SchemaGenerator
|
|
104
|
+
) -> str:
|
|
105
|
+
older_model_wrapper = generator_from_older_version._get_wrapper_for_model(schema)
|
|
106
|
+
newer_names_mapping = {
|
|
107
|
+
field.name_from_newer_version: old_name for old_name, field in older_model_wrapper.fields.items()
|
|
108
|
+
}
|
|
109
|
+
return newer_names_mapping[new_field_name]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _get_affected_model_names(
|
|
113
|
+
instruction: Union[
|
|
114
|
+
FieldExistedAsInstruction,
|
|
115
|
+
FieldDidntExistInstruction,
|
|
116
|
+
FieldHadInstruction,
|
|
117
|
+
FieldDidntHaveInstruction,
|
|
118
|
+
],
|
|
119
|
+
generator_from_newer_version: SchemaGenerator,
|
|
120
|
+
schemas_from_last_version: "list[ModelField]",
|
|
121
|
+
):
|
|
122
|
+
changed_model = generator_from_newer_version._get_wrapper_for_model(instruction.schema)
|
|
123
|
+
annotations = [model.field_info.annotation for model in schemas_from_last_version]
|
|
124
|
+
basemodel_annotations: list[type[BaseModel]] = []
|
|
125
|
+
for annotation in annotations:
|
|
126
|
+
basemodel_annotations.extend(_get_all_pydantic_models_from_generic(annotation))
|
|
127
|
+
models = {generator_from_newer_version._get_wrapper_for_model(annotation) for annotation in basemodel_annotations}
|
|
128
|
+
return [
|
|
129
|
+
model.name
|
|
130
|
+
for model in models
|
|
131
|
+
if changed_model == model
|
|
132
|
+
or (
|
|
133
|
+
instruction.name not in model.fields
|
|
134
|
+
and changed_model in (parents := model._get_parents(generator_from_newer_version.model_bundle.schemas))
|
|
135
|
+
and all(instruction.name not in parent.fields for parent in parents[: parents.index(changed_model)])
|
|
136
|
+
)
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _get_all_pydantic_models_from_generic(annotation: Any) -> list[type[BaseModel]]:
|
|
141
|
+
if not isinstance(annotation, GenericAliasUnionArgs):
|
|
142
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
143
|
+
return [annotation]
|
|
144
|
+
else:
|
|
145
|
+
return []
|
|
146
|
+
sub_annotations = get_args(annotation)
|
|
147
|
+
models = []
|
|
148
|
+
|
|
149
|
+
for sub_annotation in sub_annotations:
|
|
150
|
+
models.extend(_get_all_pydantic_models_from_generic(sub_annotation))
|
|
151
|
+
|
|
152
|
+
return models
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _get_openapi_representation_of_a_field(model: type[BaseModel], field_name: str) -> dict:
|
|
156
|
+
from fastapi._compat import (
|
|
157
|
+
GenerateJsonSchema,
|
|
158
|
+
ModelField,
|
|
159
|
+
get_compat_model_name_map,
|
|
160
|
+
get_definitions,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
class CadwynDummyModelForRepresentation(BaseModel):
|
|
164
|
+
my_field: model
|
|
165
|
+
|
|
166
|
+
model_name_map = get_compat_model_name_map([CadwynDummyModelForRepresentation.model_fields["my_field"]])
|
|
167
|
+
schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)
|
|
168
|
+
_, definitions = get_definitions(
|
|
169
|
+
fields=[ModelField(CadwynDummyModelForRepresentation.model_fields["my_field"], "my_field")],
|
|
170
|
+
schema_generator=schema_generator,
|
|
171
|
+
model_name_map=model_name_map,
|
|
172
|
+
separate_input_output_schemas=False,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return definitions[model.__name__]["properties"][field_name]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ChangelogEntryType(StrEnum):
|
|
179
|
+
endpoint_added = "endpoint.added"
|
|
180
|
+
endpoint_removed = "endpoint.removed"
|
|
181
|
+
endpoint_changed = "endpoint.changed"
|
|
182
|
+
enum_members_added = "enum.members.added"
|
|
183
|
+
enum_members_removed = "enum.members.removed"
|
|
184
|
+
schema_changed = "schema.changed"
|
|
185
|
+
schema_field_removed = "schema.field.removed"
|
|
186
|
+
schema_field_added = "schema.field.added"
|
|
187
|
+
schema_field_attributes_changed = "schema.field.attributes.changed"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class CadwynAttributeChangeStatus(StrEnum):
|
|
191
|
+
added = auto()
|
|
192
|
+
changed = auto()
|
|
193
|
+
removed = auto()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class CadwynEndpointAttributeChange(BaseModel):
|
|
197
|
+
name: str
|
|
198
|
+
new_value: Any
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class CadwynAttributeChange(BaseModel):
|
|
202
|
+
name: str
|
|
203
|
+
status: CadwynAttributeChangeStatus
|
|
204
|
+
old_value: Any
|
|
205
|
+
new_value: Any
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class CadwynFieldAttributesWereChangedChangelogEntry(BaseModel):
|
|
209
|
+
type: Literal[ChangelogEntryType.schema_field_attributes_changed] = (
|
|
210
|
+
ChangelogEntryType.schema_field_attributes_changed
|
|
211
|
+
)
|
|
212
|
+
models: list[str]
|
|
213
|
+
field: str
|
|
214
|
+
attribute_changes: list[CadwynAttributeChange]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class CadwynModelModifiedAttributes(BaseModel):
|
|
218
|
+
name: Union[str, None]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class CadwynSchemaWasChangedChangelogEntry(BaseModel):
|
|
222
|
+
type: Literal[ChangelogEntryType.schema_changed] = ChangelogEntryType.schema_changed
|
|
223
|
+
model: str
|
|
224
|
+
modified_attributes: CadwynModelModifiedAttributes
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class CadwynSchemaFieldWasAddedChangelogEntry(BaseModel):
|
|
228
|
+
type: Literal[ChangelogEntryType.schema_field_added] = ChangelogEntryType.schema_field_added
|
|
229
|
+
models: list[str]
|
|
230
|
+
field: str
|
|
231
|
+
field_info: dict[str, Any]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CadwynSchemaFieldWasRemovedChangelogEntry(BaseModel):
|
|
235
|
+
type: Literal[ChangelogEntryType.schema_field_removed] = ChangelogEntryType.schema_field_removed
|
|
236
|
+
models: list[str]
|
|
237
|
+
field: str
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class CadwynEnumMember(BaseModel):
|
|
241
|
+
name: str
|
|
242
|
+
value: Any
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class CadwynEnumMembersWereAddedChangelogEntry(BaseModel):
|
|
246
|
+
type: Literal[ChangelogEntryType.enum_members_added] = ChangelogEntryType.enum_members_added
|
|
247
|
+
enum: str
|
|
248
|
+
members: list[CadwynEnumMember]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class CadwynEnumMembersWereChangedChangelogEntry(BaseModel):
|
|
252
|
+
type: Literal[ChangelogEntryType.enum_members_removed] = ChangelogEntryType.enum_members_removed
|
|
253
|
+
enum: str
|
|
254
|
+
member_changes: list[CadwynAttributeChange]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class HTTPMethod(StrEnum):
|
|
258
|
+
GET = "GET"
|
|
259
|
+
PUT = "PUT"
|
|
260
|
+
POST = "POST"
|
|
261
|
+
DELETE = "DELETE"
|
|
262
|
+
OPTIONS = "OPTIONS"
|
|
263
|
+
HEAD = "HEAD"
|
|
264
|
+
PATCH = "PATCH"
|
|
265
|
+
TRACE = "TRACE"
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class CadwynEndpointHadChangelogEntry(BaseModel):
|
|
269
|
+
type: Literal[ChangelogEntryType.endpoint_changed] = ChangelogEntryType.endpoint_changed
|
|
270
|
+
path: str
|
|
271
|
+
methods: list[HTTPMethod]
|
|
272
|
+
changes: list[CadwynEndpointAttributeChange]
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class CadwynEndpointWasAddedChangelogEntry(BaseModel):
|
|
276
|
+
type: Literal[ChangelogEntryType.endpoint_added] = ChangelogEntryType.endpoint_added
|
|
277
|
+
path: str
|
|
278
|
+
methods: list[HTTPMethod]
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
class CadwynEndpointWasRemovedChangelogEntry(BaseModel):
|
|
282
|
+
type: Literal[ChangelogEntryType.endpoint_removed] = ChangelogEntryType.endpoint_removed
|
|
283
|
+
path: str
|
|
284
|
+
methods: list[HTTPMethod]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class CadwynChangelogResource(BaseModel):
|
|
288
|
+
versions: "list[CadwynVersion]" = Field(default_factory=list)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class CadwynVersion(BaseModel):
|
|
292
|
+
value: str
|
|
293
|
+
changes: "list[CadwynVersionChange]" = Field(default_factory=list)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class CadwynVersionChange(BaseModel):
|
|
297
|
+
description: str
|
|
298
|
+
side_effects: bool
|
|
299
|
+
instructions: "list[CadwynVersionChangeInstruction]" = Field(default_factory=list)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
CadwynVersionChangeInstruction = RootModel[
|
|
303
|
+
Union[
|
|
304
|
+
CadwynEnumMembersWereAddedChangelogEntry,
|
|
305
|
+
CadwynEnumMembersWereChangedChangelogEntry,
|
|
306
|
+
CadwynEndpointWasAddedChangelogEntry,
|
|
307
|
+
CadwynEndpointWasRemovedChangelogEntry,
|
|
308
|
+
CadwynSchemaFieldWasRemovedChangelogEntry,
|
|
309
|
+
CadwynSchemaFieldWasAddedChangelogEntry,
|
|
310
|
+
CadwynFieldAttributesWereChangedChangelogEntry,
|
|
311
|
+
CadwynEndpointHadChangelogEntry,
|
|
312
|
+
CadwynSchemaWasChangedChangelogEntry,
|
|
313
|
+
]
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _convert_version_change_instruction_to_changelog_entry( # noqa: C901
|
|
318
|
+
instruction: PossibleInstructions,
|
|
319
|
+
version_change: type[VersionChange],
|
|
320
|
+
generator_from_newer_version: SchemaGenerator,
|
|
321
|
+
generator_from_older_version: SchemaGenerator,
|
|
322
|
+
schemas_from_older_version: "list[ModelField]",
|
|
323
|
+
routes_from_newer_version: list[APIRoute],
|
|
324
|
+
):
|
|
325
|
+
if isinstance(instruction, EndpointDidntExistInstruction):
|
|
326
|
+
return CadwynEndpointWasAddedChangelogEntry(
|
|
327
|
+
path=instruction.endpoint_path,
|
|
328
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
329
|
+
)
|
|
330
|
+
elif isinstance(instruction, EndpointExistedInstruction):
|
|
331
|
+
return CadwynEndpointWasRemovedChangelogEntry(
|
|
332
|
+
path=instruction.endpoint_path,
|
|
333
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
334
|
+
)
|
|
335
|
+
elif isinstance(instruction, EndpointHadInstruction):
|
|
336
|
+
if instruction.attributes.include_in_schema is not Sentinel:
|
|
337
|
+
return CadwynEndpointWasRemovedChangelogEntry(
|
|
338
|
+
path=instruction.endpoint_path,
|
|
339
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
renaming_map = {"operation_id": "operationId"}
|
|
343
|
+
|
|
344
|
+
attribute_changes = []
|
|
345
|
+
|
|
346
|
+
for attr in ["path", "methods", "summary", "description", "tags", "deprecated", "operation_id"]:
|
|
347
|
+
attr_value = getattr(instruction.attributes, attr)
|
|
348
|
+
if attr_value is not Sentinel:
|
|
349
|
+
attribute_changes.append(
|
|
350
|
+
CadwynEndpointAttributeChange(name=renaming_map.get(attr, attr), new_value=attr_value)
|
|
351
|
+
)
|
|
352
|
+
if instruction.attributes.name is not Sentinel and instruction.attributes.summary is Sentinel:
|
|
353
|
+
attribute_changes.append(
|
|
354
|
+
CadwynEndpointAttributeChange(name="summary", new_value=instruction.attributes.name)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if any(
|
|
358
|
+
getattr(instruction.attributes, attr) is not Sentinel
|
|
359
|
+
for attr in ["path", "methods", "summary", "description", "tags", "deprecated"]
|
|
360
|
+
):
|
|
361
|
+
pass
|
|
362
|
+
if any(
|
|
363
|
+
attr is not Sentinel
|
|
364
|
+
for attr in [
|
|
365
|
+
instruction.attributes.response_model,
|
|
366
|
+
instruction.attributes.response_class,
|
|
367
|
+
instruction.attributes.responses,
|
|
368
|
+
instruction.attributes.status_code,
|
|
369
|
+
]
|
|
370
|
+
):
|
|
371
|
+
newer_routes = _get_routes(
|
|
372
|
+
routes_from_newer_version,
|
|
373
|
+
instruction.endpoint_path,
|
|
374
|
+
instruction.endpoint_methods,
|
|
375
|
+
instruction.endpoint_func_name,
|
|
376
|
+
is_deleted=False,
|
|
377
|
+
)
|
|
378
|
+
newer_openapi = get_openapi(title="", version="", routes=newer_routes)
|
|
379
|
+
changed_responses = {
|
|
380
|
+
method: route_openapi["responses"]
|
|
381
|
+
for method, route_openapi in newer_openapi["paths"][instruction.endpoint_path].items()
|
|
382
|
+
}
|
|
383
|
+
attribute_changes.append(CadwynEndpointAttributeChange(name="responses", new_value=changed_responses))
|
|
384
|
+
return CadwynEndpointHadChangelogEntry(
|
|
385
|
+
path=instruction.endpoint_path,
|
|
386
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
387
|
+
changes=attribute_changes,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
elif isinstance(instruction, (FieldHadInstruction, FieldDidntHaveInstruction)):
|
|
391
|
+
old_field_name = _get_older_field_name(instruction.schema, instruction.name, generator_from_older_version)
|
|
392
|
+
|
|
393
|
+
if isinstance(instruction, FieldHadInstruction) and instruction.new_name is not Sentinel:
|
|
394
|
+
old_field_name_from_this_instruction = instruction.new_name
|
|
395
|
+
attribute_changes = [
|
|
396
|
+
CadwynAttributeChange(
|
|
397
|
+
name="name",
|
|
398
|
+
status=CadwynAttributeChangeStatus.changed,
|
|
399
|
+
old_value=old_field_name,
|
|
400
|
+
new_value=instruction.name,
|
|
401
|
+
)
|
|
402
|
+
]
|
|
403
|
+
else:
|
|
404
|
+
old_field_name_from_this_instruction = instruction.name
|
|
405
|
+
attribute_changes = []
|
|
406
|
+
newer_model_wrapper = generator_from_newer_version._get_wrapper_for_model(instruction.schema)
|
|
407
|
+
newer_model_wrapper_with_migrated_field = copy.deepcopy(newer_model_wrapper)
|
|
408
|
+
_change_field_in_model(
|
|
409
|
+
newer_model_wrapper_with_migrated_field,
|
|
410
|
+
generator_from_newer_version.model_bundle.schemas,
|
|
411
|
+
alter_schema_instruction=instruction,
|
|
412
|
+
version_change_name=version_change.__name__,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
older_model = newer_model_wrapper_with_migrated_field.generate_model_copy(generator_from_newer_version)
|
|
416
|
+
newer_model = newer_model_wrapper.generate_model_copy(generator_from_newer_version)
|
|
417
|
+
|
|
418
|
+
newer_field_openapi = _get_openapi_representation_of_a_field(newer_model, instruction.name)
|
|
419
|
+
older_field_openapi = _get_openapi_representation_of_a_field(older_model, old_field_name_from_this_instruction)
|
|
420
|
+
|
|
421
|
+
attribute_changes += [
|
|
422
|
+
CadwynAttributeChange(
|
|
423
|
+
name=key,
|
|
424
|
+
status=CadwynAttributeChangeStatus.changed
|
|
425
|
+
if key in newer_field_openapi
|
|
426
|
+
else CadwynAttributeChangeStatus.removed,
|
|
427
|
+
old_value=old_value,
|
|
428
|
+
new_value=newer_field_openapi.get(key),
|
|
429
|
+
)
|
|
430
|
+
for key, old_value in older_field_openapi.items()
|
|
431
|
+
if old_value != newer_field_openapi.get(key)
|
|
432
|
+
]
|
|
433
|
+
attribute_changes += [
|
|
434
|
+
CadwynAttributeChange(
|
|
435
|
+
name=key,
|
|
436
|
+
status=CadwynAttributeChangeStatus.added,
|
|
437
|
+
old_value=None,
|
|
438
|
+
new_value=new_value,
|
|
439
|
+
)
|
|
440
|
+
for key, new_value in newer_field_openapi.items()
|
|
441
|
+
if key not in older_field_openapi
|
|
442
|
+
]
|
|
443
|
+
|
|
444
|
+
return CadwynFieldAttributesWereChangedChangelogEntry(
|
|
445
|
+
models=_get_affected_model_names(instruction, generator_from_newer_version, schemas_from_older_version),
|
|
446
|
+
field=old_field_name,
|
|
447
|
+
attribute_changes=attribute_changes,
|
|
448
|
+
)
|
|
449
|
+
elif isinstance(instruction, EnumDidntHaveMembersInstruction):
|
|
450
|
+
enum = generator_from_newer_version._get_wrapper_for_model(instruction.enum)
|
|
451
|
+
|
|
452
|
+
return CadwynEnumMembersWereAddedChangelogEntry(
|
|
453
|
+
enum=enum.name,
|
|
454
|
+
members=[CadwynEnumMember(name=name, value=value) for name, value in enum.members.items()],
|
|
455
|
+
)
|
|
456
|
+
elif isinstance(instruction, EnumHadMembersInstruction):
|
|
457
|
+
new_enum = generator_from_newer_version[instruction.enum]
|
|
458
|
+
old_enum = generator_from_older_version[instruction.enum]
|
|
459
|
+
|
|
460
|
+
return CadwynEnumMembersWereChangedChangelogEntry(
|
|
461
|
+
enum=new_enum.__name__,
|
|
462
|
+
member_changes=[
|
|
463
|
+
CadwynAttributeChange(
|
|
464
|
+
name=name,
|
|
465
|
+
old_value=old_enum.__members__.get(name),
|
|
466
|
+
new_value=new_enum.__members__.get(name),
|
|
467
|
+
status=CadwynAttributeChangeStatus.changed
|
|
468
|
+
if name in new_enum.__members__
|
|
469
|
+
else CadwynAttributeChangeStatus.removed,
|
|
470
|
+
)
|
|
471
|
+
for name in instruction.members
|
|
472
|
+
],
|
|
473
|
+
)
|
|
474
|
+
elif isinstance(instruction, SchemaHadInstruction):
|
|
475
|
+
model = generator_from_newer_version._get_wrapper_for_model(instruction.schema)
|
|
476
|
+
|
|
477
|
+
return CadwynSchemaWasChangedChangelogEntry(
|
|
478
|
+
model=instruction.name, modified_attributes=CadwynModelModifiedAttributes(name=model.name)
|
|
479
|
+
)
|
|
480
|
+
elif isinstance(instruction, FieldExistedAsInstruction):
|
|
481
|
+
affected_model_names = _get_affected_model_names(
|
|
482
|
+
instruction, generator_from_newer_version, schemas_from_older_version
|
|
483
|
+
)
|
|
484
|
+
return CadwynSchemaFieldWasRemovedChangelogEntry(models=affected_model_names, field=instruction.name)
|
|
485
|
+
elif isinstance(instruction, FieldDidntExistInstruction):
|
|
486
|
+
model = generator_from_newer_version[instruction.schema]
|
|
487
|
+
affected_model_names = _get_affected_model_names(
|
|
488
|
+
instruction, generator_from_newer_version, schemas_from_older_version
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
return CadwynSchemaFieldWasAddedChangelogEntry(
|
|
492
|
+
models=affected_model_names,
|
|
493
|
+
field=instruction.name,
|
|
494
|
+
field_info=_get_openapi_representation_of_a_field(model, instruction.name),
|
|
495
|
+
)
|
|
496
|
+
else: # pragma: no cover
|
|
497
|
+
_logger.warning(
|
|
498
|
+
"Encountered an unknown instruction. "
|
|
499
|
+
"This should not have happened. "
|
|
500
|
+
"Please, contact the author and show him this message and your version bundle: %s.",
|
|
501
|
+
instruction,
|
|
502
|
+
)
|
|
503
|
+
return None
|
cadwyn/dependencies.py
ADDED
cadwyn/exceptions.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from fastapi.routing import APIRoute
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CadwynRenderError(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CadwynError(Exception):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CadwynHeadRequestValidationError(CadwynError):
|
|
15
|
+
def __init__(self, errors: list[Any], body: Any, version: str) -> None:
|
|
16
|
+
self.errors = errors
|
|
17
|
+
self.body = body
|
|
18
|
+
self.version = version
|
|
19
|
+
super().__init__(
|
|
20
|
+
f"We failed to migrate the request with version={self.version}. "
|
|
21
|
+
"This means that there is some error in your migrations or schema structure that makes it impossible "
|
|
22
|
+
"to migrate the request of that version to latest.\n"
|
|
23
|
+
f"body={self.body}\n\nerrors={self.errors}"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LintingError(CadwynError):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SchemaGenerationError(CadwynError):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ModuleIsNotAvailableAsTextError(SchemaGenerationError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class InvalidGenerationInstructionError(SchemaGenerationError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RouterGenerationError(CadwynError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RouterPathParamsModifiedError(RouterGenerationError):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RouteResponseBySchemaConverterDoesNotApplyToAnythingError(RouterGenerationError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RouteRequestBySchemaConverterDoesNotApplyToAnythingError(RouterGenerationError):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RouteByPathConverterDoesNotApplyToAnythingError(RouterGenerationError):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RouteAlreadyExistsError(RouterGenerationError):
|
|
64
|
+
def __init__(self, *routes: APIRoute):
|
|
65
|
+
self.routes = routes
|
|
66
|
+
super().__init__(f"The following routes are duplicates of each other: {routes}")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CadwynStructureError(CadwynError):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ModuleIsNotVersionedError(ValueError):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ImportFromStringError(CadwynError):
|
|
78
|
+
pass
|