cadwyn 3.11.0__py3-none-any.whl → 3.12.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/applications.py +111 -26
- cadwyn/routing.py +13 -21
- cadwyn/structure/versions.py +8 -4
- {cadwyn-3.11.0.dist-info → cadwyn-3.12.0.dist-info}/METADATA +1 -1
- {cadwyn-3.11.0.dist-info → cadwyn-3.12.0.dist-info}/RECORD +8 -8
- {cadwyn-3.11.0.dist-info → cadwyn-3.12.0.dist-info}/LICENSE +0 -0
- {cadwyn-3.11.0.dist-info → cadwyn-3.12.0.dist-info}/WHEEL +0 -0
- {cadwyn-3.11.0.dist-info → cadwyn-3.12.0.dist-info}/entry_points.txt +0 -0
cadwyn/applications.py
CHANGED
|
@@ -19,6 +19,7 @@ from starlette.routing import BaseRoute, Route
|
|
|
19
19
|
from starlette.types import Lifespan
|
|
20
20
|
from typing_extensions import Self, deprecated
|
|
21
21
|
|
|
22
|
+
from cadwyn._utils import same_definition_as_in
|
|
22
23
|
from cadwyn.middleware import HeaderVersioningMiddleware, _get_api_version_dependency
|
|
23
24
|
from cadwyn.route_generation import generate_versioned_routers
|
|
24
25
|
from cadwyn.routing import _RootHeaderAPIRouter
|
|
@@ -117,26 +118,34 @@ class Cadwyn(FastAPI):
|
|
|
117
118
|
separate_input_output_schemas=separate_input_output_schemas,
|
|
118
119
|
**extra,
|
|
119
120
|
)
|
|
121
|
+
self._kwargs_to_router: dict[str, Any] = {
|
|
122
|
+
"routes": routes,
|
|
123
|
+
"redirect_slashes": redirect_slashes,
|
|
124
|
+
"dependency_overrides_provider": self,
|
|
125
|
+
"on_startup": on_startup,
|
|
126
|
+
"on_shutdown": on_shutdown,
|
|
127
|
+
"lifespan": lifespan,
|
|
128
|
+
"default_response_class": default_response_class,
|
|
129
|
+
"dependencies": dependencies,
|
|
130
|
+
"callbacks": callbacks,
|
|
131
|
+
"deprecated": deprecated,
|
|
132
|
+
"include_in_schema": include_in_schema,
|
|
133
|
+
"responses": responses,
|
|
134
|
+
"generate_unique_id_function": generate_unique_id_function,
|
|
135
|
+
}
|
|
120
136
|
self.router: _RootHeaderAPIRouter = _RootHeaderAPIRouter( # pyright: ignore[reportIncompatibleVariableOverride]
|
|
121
|
-
|
|
122
|
-
on_startup=on_startup,
|
|
123
|
-
on_shutdown=on_shutdown,
|
|
124
|
-
default_response_class=default_response_class,
|
|
125
|
-
dependencies=dependencies,
|
|
126
|
-
callbacks=callbacks,
|
|
127
|
-
deprecated=deprecated,
|
|
128
|
-
responses=responses,
|
|
137
|
+
**self._kwargs_to_router,
|
|
129
138
|
api_version_header_name=api_version_header_name,
|
|
130
139
|
api_version_var=self.versions.api_version_var,
|
|
131
|
-
lifespan=lifespan,
|
|
132
140
|
)
|
|
141
|
+
|
|
133
142
|
self.docs_url = docs_url
|
|
134
143
|
self.redoc_url = redoc_url
|
|
135
144
|
self.openapi_url = openapi_url
|
|
136
145
|
self.redoc_url = redoc_url
|
|
137
146
|
self.swaggers = {}
|
|
138
147
|
|
|
139
|
-
unversioned_router = APIRouter(
|
|
148
|
+
unversioned_router = APIRouter(**self._kwargs_to_router)
|
|
140
149
|
self._add_openapi_endpoints(unversioned_router)
|
|
141
150
|
self.add_unversioned_routers(unversioned_router)
|
|
142
151
|
self.add_middleware(
|
|
@@ -202,14 +211,14 @@ class Cadwyn(FastAPI):
|
|
|
202
211
|
terms_of_service=self.terms_of_service,
|
|
203
212
|
contact=self.contact,
|
|
204
213
|
license_info=self.license_info,
|
|
205
|
-
routes=self.router.
|
|
214
|
+
routes=self.router.routes,
|
|
206
215
|
tags=self.openapi_tags,
|
|
207
216
|
servers=self.servers,
|
|
208
217
|
)
|
|
209
218
|
if unversioned_routes_openapi["paths"]:
|
|
210
219
|
self.swaggers["unversioned"] = unversioned_routes_openapi
|
|
211
220
|
|
|
212
|
-
for header_value,
|
|
221
|
+
for header_value, router in self.router.versioned_routers.items():
|
|
213
222
|
header_value_str = header_value.isoformat()
|
|
214
223
|
openapi = get_openapi(
|
|
215
224
|
title=self.title,
|
|
@@ -219,7 +228,7 @@ class Cadwyn(FastAPI):
|
|
|
219
228
|
terms_of_service=self.terms_of_service,
|
|
220
229
|
contact=self.contact,
|
|
221
230
|
license_info=self.license_info,
|
|
222
|
-
routes=routes,
|
|
231
|
+
routes=router.routes,
|
|
223
232
|
tags=self.openapi_tags,
|
|
224
233
|
servers=self.servers,
|
|
225
234
|
)
|
|
@@ -272,33 +281,109 @@ class Cadwyn(FastAPI):
|
|
|
272
281
|
except ValueError as e:
|
|
273
282
|
raise ValueError("header_value should be in ISO 8601 format") from e
|
|
274
283
|
|
|
275
|
-
if header_value_as_dt not in self.router.
|
|
276
|
-
self.router.
|
|
284
|
+
if header_value_as_dt not in self.router.versioned_routers: # pragma: no branch
|
|
285
|
+
self.router.versioned_routers[header_value_as_dt] = APIRouter(**self._kwargs_to_router)
|
|
277
286
|
if self.openapi_url is not None: # pragma: no branch
|
|
278
|
-
self.router.
|
|
279
|
-
|
|
287
|
+
self.router.versioned_routers[header_value_as_dt].add_route(
|
|
288
|
+
path=self.openapi_url,
|
|
289
|
+
endpoint=self.openapi_jsons,
|
|
290
|
+
include_in_schema=False,
|
|
280
291
|
)
|
|
281
292
|
|
|
282
293
|
added_routes: list[BaseRoute] = []
|
|
283
294
|
for router in (first_router, *other_routers):
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
self.include_router(
|
|
295
|
+
self.router.versioned_routers[header_value_as_dt].include_router(
|
|
287
296
|
router,
|
|
288
297
|
dependencies=[Depends(_get_api_version_dependency(self.router.api_version_header_name, header_value))],
|
|
289
298
|
)
|
|
290
|
-
added_routes.extend(self.routes[len(self.routes) - added_route_count :])
|
|
291
|
-
for route in added_routes:
|
|
292
|
-
self.router.versioned_routes[header_value_as_dt].append(route)
|
|
293
299
|
|
|
294
300
|
self.enrich_swagger()
|
|
295
301
|
return added_routes
|
|
296
302
|
|
|
303
|
+
@same_definition_as_in(FastAPI.include_router)
|
|
304
|
+
def include_router(self, *args: Any, **kwargs: Any):
|
|
305
|
+
route = super().include_router(*args, **kwargs)
|
|
306
|
+
self.enrich_swagger()
|
|
307
|
+
return route
|
|
308
|
+
|
|
309
|
+
@same_definition_as_in(FastAPI.post)
|
|
310
|
+
def post(self, *args: Any, **kwargs: Any):
|
|
311
|
+
route = super().post(*args, **kwargs)
|
|
312
|
+
self.enrich_swagger()
|
|
313
|
+
return route
|
|
314
|
+
|
|
315
|
+
@same_definition_as_in(FastAPI.get)
|
|
316
|
+
def get(self, *args: Any, **kwargs: Any):
|
|
317
|
+
route = super().get(*args, **kwargs)
|
|
318
|
+
self.enrich_swagger()
|
|
319
|
+
return route
|
|
320
|
+
|
|
321
|
+
@same_definition_as_in(FastAPI.patch)
|
|
322
|
+
def patch(self, *args: Any, **kwargs: Any):
|
|
323
|
+
route = super().patch(*args, **kwargs)
|
|
324
|
+
self.enrich_swagger()
|
|
325
|
+
return route
|
|
326
|
+
|
|
327
|
+
@same_definition_as_in(FastAPI.delete)
|
|
328
|
+
def delete(self, *args: Any, **kwargs: Any):
|
|
329
|
+
route = super().delete(*args, **kwargs)
|
|
330
|
+
self.enrich_swagger()
|
|
331
|
+
return route
|
|
332
|
+
|
|
333
|
+
@same_definition_as_in(FastAPI.put)
|
|
334
|
+
def put(self, *args: Any, **kwargs: Any):
|
|
335
|
+
route = super().put(*args, **kwargs)
|
|
336
|
+
self.enrich_swagger()
|
|
337
|
+
return route
|
|
338
|
+
|
|
339
|
+
@same_definition_as_in(FastAPI.trace)
|
|
340
|
+
def trace(self, *args: Any, **kwargs: Any): # pragma: no cover
|
|
341
|
+
route = super().trace(*args, **kwargs)
|
|
342
|
+
self.enrich_swagger()
|
|
343
|
+
return route
|
|
344
|
+
|
|
345
|
+
@same_definition_as_in(FastAPI.options)
|
|
346
|
+
def options(self, *args: Any, **kwargs: Any):
|
|
347
|
+
route = super().options(*args, **kwargs)
|
|
348
|
+
self.enrich_swagger()
|
|
349
|
+
return route
|
|
350
|
+
|
|
351
|
+
@same_definition_as_in(FastAPI.head)
|
|
352
|
+
def head(self, *args: Any, **kwargs: Any):
|
|
353
|
+
route = super().head(*args, **kwargs)
|
|
354
|
+
self.enrich_swagger()
|
|
355
|
+
return route
|
|
356
|
+
|
|
357
|
+
@same_definition_as_in(FastAPI.add_api_route)
|
|
358
|
+
def add_api_route(self, *args: Any, **kwargs: Any):
|
|
359
|
+
route = super().add_api_route(*args, **kwargs)
|
|
360
|
+
self.enrich_swagger()
|
|
361
|
+
return route
|
|
362
|
+
|
|
363
|
+
@same_definition_as_in(FastAPI.api_route)
|
|
364
|
+
def api_route(self, *args: Any, **kwargs: Any):
|
|
365
|
+
route = super().api_route(*args, **kwargs)
|
|
366
|
+
self.enrich_swagger()
|
|
367
|
+
return route
|
|
368
|
+
|
|
369
|
+
@same_definition_as_in(FastAPI.add_api_websocket_route)
|
|
370
|
+
def add_api_websocket_route(self, *args: Any, **kwargs: Any): # pragma: no cover
|
|
371
|
+
route = super().add_api_websocket_route(*args, **kwargs)
|
|
372
|
+
self.enrich_swagger()
|
|
373
|
+
return route
|
|
374
|
+
|
|
375
|
+
@same_definition_as_in(FastAPI.websocket)
|
|
376
|
+
def websocket(self, *args: Any, **kwargs: Any): # pragma: no cover
|
|
377
|
+
route = super().websocket(*args, **kwargs)
|
|
378
|
+
self.enrich_swagger()
|
|
379
|
+
return route
|
|
380
|
+
|
|
297
381
|
def add_unversioned_routers(self, *routers: APIRouter):
|
|
298
382
|
for router in routers:
|
|
299
|
-
self.include_router(router)
|
|
300
|
-
self.router.unversioned_routes.extend(router.routes)
|
|
383
|
+
self.router.include_router(router)
|
|
301
384
|
self.enrich_swagger()
|
|
302
385
|
|
|
386
|
+
@deprecated("Use add add_unversioned_routers instead")
|
|
303
387
|
def add_unversioned_routes(self, *routes: Route):
|
|
304
|
-
|
|
388
|
+
router = APIRouter(routes=list(routes))
|
|
389
|
+
self.include_router(router)
|
cadwyn/routing.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import bisect
|
|
2
|
-
from collections import OrderedDict
|
|
3
2
|
from collections.abc import Sequence
|
|
4
3
|
from contextvars import ContextVar
|
|
5
4
|
from datetime import date
|
|
@@ -44,32 +43,25 @@ class _RootHeaderAPIRouter(APIRouter):
|
|
|
44
43
|
**kwargs: Any,
|
|
45
44
|
):
|
|
46
45
|
super().__init__(*args, **kwargs)
|
|
47
|
-
self.
|
|
48
|
-
self.unversioned_routes: list[BaseRoute] = []
|
|
46
|
+
self.versioned_routers: dict[date, APIRouter] = {}
|
|
49
47
|
self.api_version_header_name = api_version_header_name.lower()
|
|
50
48
|
self.api_version_var = api_version_var
|
|
51
49
|
|
|
52
50
|
@cached_property
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
return OrderedDict(sorted_routes)
|
|
51
|
+
def sorted_versions(self):
|
|
52
|
+
return sorted(self.versioned_routers.keys())
|
|
56
53
|
|
|
57
54
|
@cached_property
|
|
58
55
|
def min_routes_version(self):
|
|
59
|
-
return min(self.
|
|
56
|
+
return min(self.sorted_versions)
|
|
60
57
|
|
|
61
|
-
def find_closest_date_but_not_new(self, request_version: date):
|
|
62
|
-
|
|
63
|
-
index = bisect.bisect_left(routes, request_version)
|
|
58
|
+
def find_closest_date_but_not_new(self, request_version: date) -> date:
|
|
59
|
+
index = bisect.bisect_left(self.sorted_versions, request_version)
|
|
64
60
|
# as bisect_left returns the index where to insert item x in list a, assuming a is sorted
|
|
65
61
|
# we need to get the previous item and that will be a match
|
|
66
|
-
return
|
|
62
|
+
return self.sorted_versions[index - 1]
|
|
67
63
|
|
|
68
|
-
def pick_version(
|
|
69
|
-
self,
|
|
70
|
-
request_header_value: date,
|
|
71
|
-
) -> list[BaseRoute]:
|
|
72
|
-
routes = []
|
|
64
|
+
def pick_version(self, request_header_value: date) -> list[BaseRoute]:
|
|
73
65
|
request_version = request_header_value.isoformat()
|
|
74
66
|
|
|
75
67
|
if self.min_routes_version > request_header_value:
|
|
@@ -79,13 +71,13 @@ class _RootHeaderAPIRouter(APIRouter):
|
|
|
79
71
|
f"is older than the oldest "
|
|
80
72
|
f"version {self.min_routes_version.isoformat()} ",
|
|
81
73
|
)
|
|
82
|
-
return
|
|
74
|
+
return []
|
|
83
75
|
version_chosen = self.find_closest_date_but_not_new(request_header_value)
|
|
84
76
|
_logger.info(
|
|
85
77
|
f"Partial match. The endpoint with {version_chosen} "
|
|
86
78
|
f"version was selected for API call version {request_version}",
|
|
87
79
|
)
|
|
88
|
-
return self.
|
|
80
|
+
return self.versioned_routers[version_chosen].routes
|
|
89
81
|
|
|
90
82
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
91
83
|
"""
|
|
@@ -103,9 +95,9 @@ class _RootHeaderAPIRouter(APIRouter):
|
|
|
103
95
|
# if header_value is None, then it's an unversioned request and we need to use the unversioned routes
|
|
104
96
|
# if there will be a value, we search for the most suitable version
|
|
105
97
|
if not header_value:
|
|
106
|
-
routes = self.
|
|
107
|
-
elif header_value in self.
|
|
108
|
-
routes = self.
|
|
98
|
+
routes = self.routes
|
|
99
|
+
elif header_value in self.versioned_routers:
|
|
100
|
+
routes = self.versioned_routers[header_value].routes
|
|
109
101
|
else:
|
|
110
102
|
routes = self.pick_version(request_header_value=header_value)
|
|
111
103
|
await self.process_request(scope=scope, receive=receive, send=send, routes=routes)
|
cadwyn/structure/versions.py
CHANGED
|
@@ -343,18 +343,22 @@ class VersionBundle:
|
|
|
343
343
|
f'Received "{head_schemas_package.__name__}" instead.',
|
|
344
344
|
)
|
|
345
345
|
|
|
346
|
+
@functools.cached_property
|
|
347
|
+
def _all_versions(self):
|
|
348
|
+
return (self.head_version, *self.versions)
|
|
349
|
+
|
|
346
350
|
@functools.cached_property
|
|
347
351
|
def versioned_schemas(self) -> dict[IdentifierPythonPath, type[VersionedModel]]:
|
|
348
352
|
altered_schemas = {
|
|
349
353
|
get_cls_pythonpath(instruction.schema): instruction.schema
|
|
350
|
-
for version in
|
|
354
|
+
for version in self._all_versions
|
|
351
355
|
for version_change in version.version_changes
|
|
352
356
|
for instruction in list(version_change.alter_schema_instructions)
|
|
353
357
|
}
|
|
354
358
|
|
|
355
359
|
migrated_schemas = {
|
|
356
360
|
get_cls_pythonpath(schema): schema
|
|
357
|
-
for version in self.
|
|
361
|
+
for version in self._all_versions
|
|
358
362
|
for version_change in version.version_changes
|
|
359
363
|
for schema in list(version_change.alter_request_by_schema_instructions.keys())
|
|
360
364
|
}
|
|
@@ -365,7 +369,7 @@ class VersionBundle:
|
|
|
365
369
|
def versioned_enums(self) -> dict[IdentifierPythonPath, type[Enum]]:
|
|
366
370
|
return {
|
|
367
371
|
get_cls_pythonpath(instruction.enum): instruction.enum
|
|
368
|
-
for version in self.
|
|
372
|
+
for version in self._all_versions
|
|
369
373
|
for version_change in version.version_changes
|
|
370
374
|
for instruction in version_change.alter_enum_instructions
|
|
371
375
|
}
|
|
@@ -377,7 +381,7 @@ class VersionBundle:
|
|
|
377
381
|
# the __init__.py file directly instead of the package itself
|
|
378
382
|
# which results in this extra `.__init__` suffix in the name
|
|
379
383
|
instruction.module.__name__.removesuffix(".__init__"): instruction.module
|
|
380
|
-
for version in self.
|
|
384
|
+
for version in self._all_versions
|
|
381
385
|
for version_change in version.version_changes
|
|
382
386
|
for instruction in version_change.alter_module_instructions
|
|
383
387
|
}
|
|
@@ -4,7 +4,7 @@ cadwyn/_asts.py,sha256=S-x9fVKTENZZxwWfabm0PbztcHyX2MJkI6Cwv5XgVrI,10138
|
|
|
4
4
|
cadwyn/_compat.py,sha256=6QwtzbXn53mIhEFfEizmFjd-f894oLsM6ITxqq2rCpc,5408
|
|
5
5
|
cadwyn/_package_utils.py,sha256=trxTYLmppv-10SKhScfyDQJh21rsQGFoLaOtHycKKR0,1443
|
|
6
6
|
cadwyn/_utils.py,sha256=BFsfZBpdoL5RMAaT1V1cXJVpTZCmwksQ-Le2MTHivGI,4841
|
|
7
|
-
cadwyn/applications.py,sha256=
|
|
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
|
|
@@ -20,7 +20,7 @@ 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
22
|
cadwyn/route_generation.py,sha256=7nHe_IP7Uz1rXmnItJsQ2yIuqe3lRgtOr8_MP_6Vg8c,35704
|
|
23
|
-
cadwyn/routing.py,sha256=
|
|
23
|
+
cadwyn/routing.py,sha256=s_-PxzDq0GT0pW-JtRLQrcR51tDQfd420oyWBP9S-sg,6158
|
|
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
|
|
@@ -30,9 +30,9 @@ cadwyn/structure/endpoints.py,sha256=VngfAydGBwekhV2tBOtNDPVgl3X1IgYxUCw--VZ5cQY
|
|
|
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=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
|
|
File without changes
|