cadwyn 4.3.0__py3-none-any.whl → 4.4.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
@@ -25,6 +25,7 @@ from starlette.routing import BaseRoute, Route
25
25
  from starlette.types import Lifespan
26
26
  from typing_extensions import Self
27
27
 
28
+ from cadwyn._utils import same_definition_as_in
28
29
  from cadwyn.changelogs import CadwynChangelogResource, _generate_changelog
29
30
  from cadwyn.middleware import HeaderVersioningMiddleware, _get_api_version_dependency
30
31
  from cadwyn.route_generation import generate_versioned_routers
@@ -96,8 +97,8 @@ class Cadwyn(FastAPI):
96
97
  **extra: Any,
97
98
  ) -> None:
98
99
  self.versions = versions
99
- # TODO: Remove argument entirely in any major version.
100
100
  self._dependency_overrides_provider = FakeDependencyOverridesProvider({})
101
+ self._cadwyn_initialized = False
101
102
 
102
103
  super().__init__(
103
104
  debug=debug,
@@ -156,6 +157,8 @@ class Cadwyn(FastAPI):
156
157
  api_version_header_name=api_version_header_name,
157
158
  api_version_var=self.versions.api_version_var,
158
159
  )
160
+ self._versioned_webhook_routers: dict[date, APIRouter] = {}
161
+ self._latest_version_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
159
162
 
160
163
  self.changelog_url = changelog_url
161
164
  self.include_changelog_url_in_schema = include_changelog_url_in_schema
@@ -176,6 +179,26 @@ class Cadwyn(FastAPI):
176
179
  default_response_class=default_response_class,
177
180
  )
178
181
 
182
+ @same_definition_as_in(FastAPI.__call__)
183
+ async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
184
+ if not self._cadwyn_initialized:
185
+ self._cadwyn_initialize()
186
+ self.__call__ = super().__call__
187
+ await self.__call__(scope, receive, send)
188
+
189
+ def _cadwyn_initialize(self) -> None:
190
+ generated_routers = generate_versioned_routers(
191
+ self._latest_version_router,
192
+ webhooks=self.webhooks,
193
+ versions=self.versions,
194
+ )
195
+ for version, router in generated_routers.endpoints.items():
196
+ self.add_header_versioned_routers(router, header_value=version.isoformat())
197
+
198
+ for version, router in generated_routers.webhooks.items():
199
+ self._versioned_webhook_routers[version] = router
200
+ self._cadwyn_initialized = True
201
+
179
202
  def _add_default_versioned_routers(self) -> None:
180
203
  for version in self.versions:
181
204
  self.router.versioned_routers[version.value] = APIRouter(**self._kwargs_to_router)
@@ -240,12 +263,8 @@ class Cadwyn(FastAPI):
240
263
  )
241
264
 
242
265
  def generate_and_include_versioned_routers(self, *routers: APIRouter) -> None:
243
- root_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
244
266
  for router in routers:
245
- root_router.include_router(router)
246
- router_versions = generate_versioned_routers(root_router, versions=self.versions)
247
- for version, router in router_versions.items():
248
- self.add_header_versioned_routers(router, header_value=version.isoformat())
267
+ self._latest_version_router.include_router(router)
249
268
 
250
269
  async def openapi_jsons(self, req: Request) -> JSONResponse:
251
270
  raw_version = req.query_params.get("version") or req.headers.get(self.router.api_version_header_name)
@@ -276,6 +295,10 @@ class Cadwyn(FastAPI):
276
295
  if root_path and root_path not in server_urls and self.root_path_in_servers:
277
296
  self.servers.insert(0, {"url": root_path})
278
297
 
298
+ webhook_routes = None
299
+ if version in self._versioned_webhook_routers:
300
+ webhook_routes = self._versioned_webhook_routers[version].routes
301
+
279
302
  return JSONResponse(
280
303
  get_openapi(
281
304
  title=self.title,
@@ -287,6 +310,7 @@ class Cadwyn(FastAPI):
287
310
  contact=self.contact,
288
311
  license_info=self.license_info,
289
312
  routes=routes,
313
+ webhooks=webhook_routes,
290
314
  tags=self.openapi_tags,
291
315
  servers=self.servers,
292
316
  )
@@ -7,7 +7,6 @@ from typing import (
7
7
  TYPE_CHECKING,
8
8
  Any,
9
9
  Generic,
10
- TypeVar,
11
10
  cast,
12
11
  )
13
12
 
@@ -20,7 +19,7 @@ from fastapi.routing import APIRoute
20
19
  from issubclass import issubclass as lenient_issubclass
21
20
  from pydantic import BaseModel
22
21
  from starlette.routing import BaseRoute
23
- from typing_extensions import assert_never
22
+ from typing_extensions import TypeVar, assert_never
24
23
 
25
24
  from cadwyn._utils import Sentinel
26
25
  from cadwyn.exceptions import (
@@ -48,7 +47,8 @@ if TYPE_CHECKING:
48
47
  from fastapi.dependencies.models import Dependant
49
48
 
50
49
  _Call = TypeVar("_Call", bound=Callable[..., Any])
51
- _R = TypeVar("_R", bound=fastapi.routing.APIRouter)
50
+ _R = TypeVar("_R", bound=APIRouter)
51
+ _WR = TypeVar("_WR", bound=APIRouter, default=APIRouter)
52
52
  # This is a hack we do because we can't guarantee how the user will use the router.
53
53
  _DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
54
54
 
@@ -59,8 +59,21 @@ class _EndpointInfo:
59
59
  endpoint_methods: frozenset[str]
60
60
 
61
61
 
62
- def generate_versioned_routers(router: _R, versions: VersionBundle) -> dict[VersionDate, _R]:
63
- return _EndpointTransformer(router, versions).transform()
62
+ @dataclass(slots=True, frozen=True)
63
+ class GeneratedRouters(Generic[_R, _WR]):
64
+ endpoints: dict[VersionDate, _R]
65
+ webhooks: dict[VersionDate, _WR]
66
+
67
+
68
+ def generate_versioned_routers(
69
+ router: _R,
70
+ versions: VersionBundle,
71
+ *,
72
+ webhooks: _WR | None = None,
73
+ ) -> GeneratedRouters[_R, _WR]:
74
+ if webhooks is None:
75
+ webhooks = cast(_WR, APIRouter())
76
+ return _EndpointTransformer(router, versions, webhooks).transform()
64
77
 
65
78
 
66
79
  class VersionedAPIRouter(fastapi.routing.APIRouter):
@@ -77,30 +90,36 @@ class VersionedAPIRouter(fastapi.routing.APIRouter):
77
90
  return endpoint
78
91
 
79
92
 
80
- class _EndpointTransformer(Generic[_R]):
81
- def __init__(self, parent_router: _R, versions: VersionBundle) -> None:
93
+ class _EndpointTransformer(Generic[_R, _WR]):
94
+ def __init__(self, parent_router: _R, versions: VersionBundle, webhooks: _WR) -> None:
82
95
  super().__init__()
83
96
  self.parent_router = parent_router
84
97
  self.versions = versions
98
+ self.parent_webhooks_router = webhooks
85
99
  self.schema_generators = generate_versioned_models(versions)
86
100
 
87
101
  self.routes_that_never_existed = [
88
102
  route for route in parent_router.routes if isinstance(route, APIRoute) and _DELETED_ROUTE_TAG in route.tags
89
103
  ]
90
104
 
91
- def transform(self) -> dict[VersionDate, _R]:
105
+ def transform(self) -> GeneratedRouters[_R, _WR]:
92
106
  router = deepcopy(self.parent_router)
107
+ webhook_router = deepcopy(self.parent_webhooks_router)
93
108
  routers: dict[VersionDate, _R] = {}
109
+ webhook_routers: dict[VersionDate, _WR] = {}
94
110
 
95
111
  for version in self.versions:
96
112
  self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(router)
113
+ self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(webhook_router)
97
114
 
98
115
  self._validate_all_data_converters_are_applied(router, version)
99
116
 
100
117
  routers[version.value] = router
118
+ webhook_routers[version.value] = webhook_router
101
119
  # Applying changes for the next version
102
120
  router = deepcopy(router)
103
- self._apply_endpoint_changes_to_router(router, version)
121
+ webhook_router = deepcopy(webhook_router)
122
+ self._apply_endpoint_changes_to_router(router.routes + webhook_router.routes, version)
104
123
 
105
124
  if self.routes_that_never_existed:
106
125
  raise RouterGenerationError(
@@ -146,7 +165,13 @@ class _EndpointTransformer(Generic[_R]):
146
165
  for route in router.routes
147
166
  if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
148
167
  ]
149
- return routers
168
+ for _, webhook_router in webhook_routers.items():
169
+ webhook_router.routes = [
170
+ route
171
+ for route in webhook_router.routes
172
+ if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
173
+ ]
174
+ return GeneratedRouters(routers, webhook_routers)
150
175
 
151
176
  def _validate_all_data_converters_are_applied(self, router: APIRouter, version: Version):
152
177
  path_to_route_methods_mapping, head_response_models, head_request_bodies = self._extract_all_routes_identifiers(
@@ -223,10 +248,9 @@ class _EndpointTransformer(Generic[_R]):
223
248
  # TODO (https://github.com/zmievsa/cadwyn/issues/28): Simplify
224
249
  def _apply_endpoint_changes_to_router( # noqa: C901
225
250
  self,
226
- router: fastapi.routing.APIRouter,
251
+ routes: list[BaseRoute] | list[APIRoute],
227
252
  version: Version,
228
253
  ):
229
- routes = router.routes
230
254
  for version_change in version.changes:
231
255
  for instruction in version_change.alter_endpoint_instructions:
232
256
  original_routes = _get_routes(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: cadwyn
3
- Version: 4.3.0
3
+ Version: 4.4.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
@@ -38,10 +38,10 @@ Requires-Dist: issubclass>=0.1.2
38
38
  Requires-Dist: jinja2>=3.1.2
39
39
  Requires-Dist: pydantic>=2.0.0
40
40
  Requires-Dist: starlette>=0.30.0
41
- Requires-Dist: typer>=0.7.0
42
41
  Requires-Dist: typing-extensions>=4.8.0
43
42
  Provides-Extra: standard
44
43
  Requires-Dist: fastapi[standard]>=0.112.3; extra == 'standard'
44
+ Requires-Dist: typer>=0.7.0; extra == 'standard'
45
45
  Description-Content-Type: text/markdown
46
46
 
47
47
  # Cadwyn
@@ -4,12 +4,12 @@ cadwyn/_asts.py,sha256=OVOPjjZNrPHpbicf4Gg5HmWToPOdMo8S56pKDDJATRY,5139
4
4
  cadwyn/_importer.py,sha256=2mZrDHlfY2heZsMBW-9RBpvKsCk9I-Wa8pxZ6f2f8gY,1074
5
5
  cadwyn/_render.py,sha256=LJ-R1TrBgMJpTkJb6pQdRWaMjKyw3R6eTlXXEieqUw0,5466
6
6
  cadwyn/_utils.py,sha256=GK9w_qzyOI_o6UaGVfwLLYhnJFMzXistoYI9fq2E9dE,1159
7
- cadwyn/applications.py,sha256=9gObB244eDTMi9-Nah3F5ErB2bSABkeGHWO7lfBcivM,16013
7
+ cadwyn/applications.py,sha256=worXUWoexSev0cGJYzXMFBxUgibC1AsM35SYtcV996k,16948
8
8
  cadwyn/changelogs.py,sha256=SdrdAKQ01mpzs-EN_zg-D0TY7wxsibjRjLMhGcI4q80,20066
9
9
  cadwyn/exceptions.py,sha256=VlJKRmEGfFTDtHbOWc8kXK4yMi2N172K684Y2UIV8rI,1832
10
10
  cadwyn/middleware.py,sha256=kUZK2dmoricMbv6knPCIHpXEInX2670XIwAj0v_XQxk,3408
11
11
  cadwyn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- cadwyn/route_generation.py,sha256=npAWxhr38_NNZOvXriPt_StdzR4WeL5Xwv505e9Gn9s,22953
12
+ cadwyn/route_generation.py,sha256=870pZK-Gs_pUuRRj9jcJm3AOc-Ua2jtNRoJcwUutLA4,24028
13
13
  cadwyn/routing.py,sha256=9AHSojmuLgUAQlLMIqXz-ViZ9n-fljgOsn7oxha7PjM,7341
14
14
  cadwyn/schema_generation.py,sha256=1eJ--nyG8onUROWegCmQQWJ4iJEu1MrTt-8bnLIQyeQ,39992
15
15
  cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -21,8 +21,8 @@ cadwyn/structure/endpoints.py,sha256=9FFnbqPM9v0CP6J6tGhMNKVvWqA9u3ZjI2Fannr2Rl0
21
21
  cadwyn/structure/enums.py,sha256=bZL-iUOUFi9ZYlMZJw-tAix2yrgCp3gH3N2gwO44LUU,1043
22
22
  cadwyn/structure/schemas.py,sha256=D0BD1D3v9MRdVWchU9JM2zHd0dvB0UgXHDGFCe5aQZc,8209
23
23
  cadwyn/structure/versions.py,sha256=2qe7xFYNdzdWmCZhkf4_zJ7lF0XdtyM1wq6xQ9omb_c,33655
24
- cadwyn-4.3.0.dist-info/METADATA,sha256=FGm-ABByL5zfrawupbtQqfImOLpCQ5cyrZqLTlnCxLk,4422
25
- cadwyn-4.3.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
26
- cadwyn-4.3.0.dist-info/entry_points.txt,sha256=mGX8wl-Xfhpr5M93SUmkykaqinUaYAvW9rtDSX54gx0,47
27
- cadwyn-4.3.0.dist-info/licenses/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
28
- cadwyn-4.3.0.dist-info/RECORD,,
24
+ cadwyn-4.4.0.dist-info/METADATA,sha256=YcNWXemwnjoR8H2ODF7yFTWRBOjbPaxar1vKXe9UmOs,4443
25
+ cadwyn-4.4.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
26
+ cadwyn-4.4.0.dist-info/entry_points.txt,sha256=mGX8wl-Xfhpr5M93SUmkykaqinUaYAvW9rtDSX54gx0,47
27
+ cadwyn-4.4.0.dist-info/licenses/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
28
+ cadwyn-4.4.0.dist-info/RECORD,,
File without changes