cadwyn 4.3.1__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 +30 -6
- cadwyn/route_generation.py +36 -12
- {cadwyn-4.3.1.dist-info → cadwyn-4.4.0.dist-info}/METADATA +1 -1
- {cadwyn-4.3.1.dist-info → cadwyn-4.4.0.dist-info}/RECORD +7 -7
- {cadwyn-4.3.1.dist-info → cadwyn-4.4.0.dist-info}/WHEEL +0 -0
- {cadwyn-4.3.1.dist-info → cadwyn-4.4.0.dist-info}/entry_points.txt +0 -0
- {cadwyn-4.3.1.dist-info → cadwyn-4.4.0.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
|
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
|
)
|
cadwyn/route_generation.py
CHANGED
|
@@ -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=
|
|
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
|
-
|
|
63
|
-
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
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
|
|
@@ -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=
|
|
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=
|
|
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.
|
|
25
|
-
cadwyn-4.
|
|
26
|
-
cadwyn-4.
|
|
27
|
-
cadwyn-4.
|
|
28
|
-
cadwyn-4.
|
|
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
|
|
File without changes
|
|
File without changes
|