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 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
- routes=self.routes,
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(routes=routes)
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.unversioned_routes,
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, routes in self.router.versioned_routes.items():
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.versioned_routes: # pragma: no branch
276
- self.router.versioned_routes[header_value_as_dt] = []
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.versioned_routes[header_value_as_dt].append(
279
- Route(path=self.openapi_url, endpoint=self.openapi_jsons, include_in_schema=False)
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
- added_route_count = len(router.routes)
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
- self.router.unversioned_routes.extend(routes)
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.versioned_routes: dict[date, list[BaseRoute]] = {}
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 sorted_versioned_routes(self):
54
- sorted_routes = sorted(self.versioned_routes.items())
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.sorted_versioned_routes.keys())
56
+ return min(self.sorted_versions)
60
57
 
61
- def find_closest_date_but_not_new(self, request_version: date):
62
- routes = list(self.sorted_versioned_routes.keys())
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 routes[index - 1]
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 routes
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.versioned_routes[version_chosen]
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.unversioned_routes
107
- elif header_value in self.versioned_routes:
108
- routes = self.versioned_routes[header_value]
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)
@@ -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 (self.head_version, *self.versions)
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.versions
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.versions
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.versions
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
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cadwyn
3
- Version: 3.11.0
3
+ Version: 3.12.0
4
4
  Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
5
5
  Home-page: https://github.com/zmievsa/cadwyn
6
6
  License: MIT
@@ -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=sz4x_GoD3s1snm3M1Wgx22mdAICQXciVd9rhGWZk0II,12826
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=nswSMfpGMYk-TSbNY6NmH7NuWM0GoWwK_McTzPiKluk,6392
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=aVmI71vBtMpyu9dT5m732feTJWdKOXNpPZQb80G8y9c,36078
34
- cadwyn-3.11.0.dist-info/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
35
- cadwyn-3.11.0.dist-info/METADATA,sha256=6bHtgZeWr378YbnPVUELXHlcyga0DH0UFJbsmK-Nsb8,4360
36
- cadwyn-3.11.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
37
- cadwyn-3.11.0.dist-info/entry_points.txt,sha256=eO05hLn9GoRzzpwT9GONPmXKsonjuMNssM2D2WHWKGk,46
38
- cadwyn-3.11.0.dist-info/RECORD,,
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,,