cadwyn 4.4.0__py3-none-any.whl → 4.4.2__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/route_generation.py +26 -6
- cadwyn/schema_generation.py +20 -7
- cadwyn/structure/versions.py +2 -0
- {cadwyn-4.4.0.dist-info → cadwyn-4.4.2.dist-info}/METADATA +3 -2
- {cadwyn-4.4.0.dist-info → cadwyn-4.4.2.dist-info}/RECORD +8 -8
- {cadwyn-4.4.0.dist-info → cadwyn-4.4.2.dist-info}/WHEEL +0 -0
- {cadwyn-4.4.0.dist-info → cadwyn-4.4.2.dist-info}/entry_points.txt +0 -0
- {cadwyn-4.4.0.dist-info → cadwyn-4.4.2.dist-info}/licenses/LICENSE +0 -0
cadwyn/route_generation.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from collections import defaultdict
|
|
3
3
|
from collections.abc import Callable, Sequence
|
|
4
|
-
from copy import deepcopy
|
|
4
|
+
from copy import copy, deepcopy
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import (
|
|
7
7
|
TYPE_CHECKING,
|
|
@@ -49,6 +49,7 @@ if TYPE_CHECKING:
|
|
|
49
49
|
_Call = TypeVar("_Call", bound=Callable[..., Any])
|
|
50
50
|
_R = TypeVar("_R", bound=APIRouter)
|
|
51
51
|
_WR = TypeVar("_WR", bound=APIRouter, default=APIRouter)
|
|
52
|
+
_RouteT = TypeVar("_RouteT", bound=BaseRoute)
|
|
52
53
|
# This is a hack we do because we can't guarantee how the user will use the router.
|
|
53
54
|
_DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
|
|
54
55
|
|
|
@@ -90,6 +91,25 @@ class VersionedAPIRouter(fastapi.routing.APIRouter):
|
|
|
90
91
|
return endpoint
|
|
91
92
|
|
|
92
93
|
|
|
94
|
+
def copy_router(router: _R) -> _R:
|
|
95
|
+
router = copy(router)
|
|
96
|
+
router.routes = [copy_route(r) for r in router.routes]
|
|
97
|
+
return router
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def copy_route(route: _RouteT) -> _RouteT:
|
|
101
|
+
if not isinstance(route, APIRoute):
|
|
102
|
+
return copy(route)
|
|
103
|
+
|
|
104
|
+
# This is slightly wasteful in terms of resources but it makes it easy for us
|
|
105
|
+
# to make sure that new versions of FastAPI are going to be supported even if
|
|
106
|
+
# APIRoute gets new attributes.
|
|
107
|
+
new_route = deepcopy(route)
|
|
108
|
+
new_route.dependant = copy(route.dependant)
|
|
109
|
+
new_route.dependencies = copy(route.dependencies)
|
|
110
|
+
return new_route
|
|
111
|
+
|
|
112
|
+
|
|
93
113
|
class _EndpointTransformer(Generic[_R, _WR]):
|
|
94
114
|
def __init__(self, parent_router: _R, versions: VersionBundle, webhooks: _WR) -> None:
|
|
95
115
|
super().__init__()
|
|
@@ -103,8 +123,8 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
103
123
|
]
|
|
104
124
|
|
|
105
125
|
def transform(self) -> GeneratedRouters[_R, _WR]:
|
|
106
|
-
router =
|
|
107
|
-
webhook_router =
|
|
126
|
+
router = copy_router(self.parent_router)
|
|
127
|
+
webhook_router = copy_router(self.parent_webhooks_router)
|
|
108
128
|
routers: dict[VersionDate, _R] = {}
|
|
109
129
|
webhook_routers: dict[VersionDate, _WR] = {}
|
|
110
130
|
|
|
@@ -117,8 +137,8 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
117
137
|
routers[version.value] = router
|
|
118
138
|
webhook_routers[version.value] = webhook_router
|
|
119
139
|
# Applying changes for the next version
|
|
120
|
-
router =
|
|
121
|
-
webhook_router =
|
|
140
|
+
router = copy_router(router)
|
|
141
|
+
webhook_router = copy_router(webhook_router)
|
|
122
142
|
self._apply_endpoint_changes_to_router(router.routes + webhook_router.routes, version)
|
|
123
143
|
|
|
124
144
|
if self.routes_that_never_existed:
|
|
@@ -134,7 +154,7 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
134
154
|
if not isinstance(head_route, APIRoute):
|
|
135
155
|
continue
|
|
136
156
|
_add_request_and_response_params(head_route)
|
|
137
|
-
copy_of_dependant =
|
|
157
|
+
copy_of_dependant = copy(head_route.dependant)
|
|
138
158
|
|
|
139
159
|
for older_router in list(routers.values()):
|
|
140
160
|
older_route = older_router.routes[route_index]
|
cadwyn/schema_generation.py
CHANGED
|
@@ -367,6 +367,10 @@ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
|
|
|
367
367
|
return model_copy
|
|
368
368
|
|
|
369
369
|
|
|
370
|
+
def is_regular_function(call: Callable):
|
|
371
|
+
return isinstance(call, types.FunctionType | types.MethodType)
|
|
372
|
+
|
|
373
|
+
|
|
370
374
|
class _CallableWrapper:
|
|
371
375
|
"""__eq__ and __hash__ are needed to make sure that dependency overrides work correctly.
|
|
372
376
|
They are based on putting dependencies (functions) as keys for the dictionary so if we want to be able to
|
|
@@ -376,6 +380,8 @@ class _CallableWrapper:
|
|
|
376
380
|
def __init__(self, original_callable: Callable) -> None:
|
|
377
381
|
super().__init__()
|
|
378
382
|
self._original_callable = original_callable
|
|
383
|
+
if not is_regular_function(original_callable):
|
|
384
|
+
original_callable = original_callable.__call__
|
|
379
385
|
functools.update_wrapper(self, original_callable)
|
|
380
386
|
|
|
381
387
|
@property
|
|
@@ -458,6 +464,12 @@ class _AnnotationTransformer:
|
|
|
458
464
|
def _change_version_of_a_non_container_annotation(self, annotation: Any) -> Any:
|
|
459
465
|
if isinstance(annotation, _BaseGenericAlias | types.GenericAlias):
|
|
460
466
|
return get_origin(annotation)[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
|
|
467
|
+
elif isinstance(annotation, fastapi.params.Security):
|
|
468
|
+
return fastapi.params.Security(
|
|
469
|
+
self.change_version_of_annotation(annotation.dependency),
|
|
470
|
+
scopes=annotation.scopes,
|
|
471
|
+
use_cache=annotation.use_cache,
|
|
472
|
+
)
|
|
461
473
|
elif isinstance(annotation, fastapi.params.Depends):
|
|
462
474
|
return fastapi.params.Depends(
|
|
463
475
|
self.change_version_of_annotation(annotation.dependency),
|
|
@@ -475,7 +487,7 @@ class _AnnotationTransformer:
|
|
|
475
487
|
elif callable(annotation):
|
|
476
488
|
if type(annotation).__module__.startswith(
|
|
477
489
|
("fastapi.", "pydantic.", "pydantic_core.", "starlette.")
|
|
478
|
-
) or isinstance(annotation, fastapi.
|
|
490
|
+
) or isinstance(annotation, fastapi.security.base.SecurityBase):
|
|
479
491
|
return annotation
|
|
480
492
|
|
|
481
493
|
def modifier(annotation: Any):
|
|
@@ -568,8 +580,12 @@ class _AnnotationTransformer:
|
|
|
568
580
|
def _copy_function_through_class_based_wrapper(cls, call: Any):
|
|
569
581
|
"""Separate from copy_endpoint because endpoints MUST be functions in FastAPI, they cannot be cls instances"""
|
|
570
582
|
call = cls._unwrap_callable(call)
|
|
571
|
-
|
|
572
|
-
|
|
583
|
+
if not is_regular_function(call):
|
|
584
|
+
# This means that the callable is actually an instance of a regular class
|
|
585
|
+
actual_call = call.__call__
|
|
586
|
+
else:
|
|
587
|
+
actual_call = call
|
|
588
|
+
if inspect.iscoroutinefunction(actual_call):
|
|
573
589
|
return _AsyncCallableWrapper(call)
|
|
574
590
|
else:
|
|
575
591
|
return _CallableWrapper(call)
|
|
@@ -578,9 +594,6 @@ class _AnnotationTransformer:
|
|
|
578
594
|
def _unwrap_callable(call: Any) -> Any:
|
|
579
595
|
while hasattr(call, "_original_callable"):
|
|
580
596
|
call = call._original_callable
|
|
581
|
-
if not isinstance(call, types.FunctionType | types.MethodType):
|
|
582
|
-
# This means that the callable is actually an instance of a regular class
|
|
583
|
-
call = call.__call__
|
|
584
597
|
|
|
585
598
|
return call
|
|
586
599
|
|
|
@@ -607,7 +620,7 @@ class SchemaGenerator:
|
|
|
607
620
|
|
|
608
621
|
def __getitem__(self, model: type[_T_ANY_MODEL], /) -> type[_T_ANY_MODEL]:
|
|
609
622
|
if not isinstance(model, type) or not issubclass(model, BaseModel | Enum) or model in (BaseModel, RootModel):
|
|
610
|
-
return model
|
|
623
|
+
return model
|
|
611
624
|
model = _unwrap_model(model)
|
|
612
625
|
|
|
613
626
|
if model in self.concrete_models:
|
cadwyn/structure/versions.py
CHANGED
|
@@ -156,6 +156,8 @@ class VersionChange:
|
|
|
156
156
|
"instructions_to_migrate_to_previous_version",
|
|
157
157
|
"__module__",
|
|
158
158
|
"__doc__",
|
|
159
|
+
"__firstlineno__",
|
|
160
|
+
"__static_attributes__",
|
|
159
161
|
}:
|
|
160
162
|
raise CadwynStructureError(
|
|
161
163
|
f"Found: '{attr_name}' attribute of type '{type(attr_value)}' in '{cls.__name__}'."
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 4.4.
|
|
3
|
+
Version: 4.4.2
|
|
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
|
|
7
7
|
Author-email: Stanislav Zmiev <zmievsa@gmail.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Keywords: api,api-versioning,code-generation,fastapi,hints,json-schema,pydantic,python,python310,python311,python312,stripe,versioning
|
|
10
|
+
Keywords: api,api-versioning,code-generation,fastapi,hints,json-schema,pydantic,python,python310,python311,python312,python313,stripe,versioning
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: Web Environment
|
|
13
13
|
Classifier: Framework :: AsyncIO
|
|
@@ -23,6 +23,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.10
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.11
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
26
27
|
Classifier: Topic :: Internet
|
|
27
28
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
28
29
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
@@ -9,9 +9,9 @@ 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=2qUTPHsG6u6yXUA5pLa3ubfZOc9FrISDMELmAFhDSUY,24696
|
|
13
13
|
cadwyn/routing.py,sha256=9AHSojmuLgUAQlLMIqXz-ViZ9n-fljgOsn7oxha7PjM,7341
|
|
14
|
-
cadwyn/schema_generation.py,sha256=
|
|
14
|
+
cadwyn/schema_generation.py,sha256=JuWuBPZTEC_K8BEd67jaXFqD-Y4r40njJLQFNBrU77Y,40465
|
|
15
15
|
cadwyn/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
16
|
cadwyn/static/docs.html,sha256=WNm5ANJVy51TcIUFOaqKf1Z8eF86CC85TTHPxACtkzw,3455
|
|
17
17
|
cadwyn/structure/__init__.py,sha256=vej7TdTMSOg8U8Wk7GTNdA4rc6loA9083FWaTg4jAaY,655
|
|
@@ -20,9 +20,9 @@ cadwyn/structure/data.py,sha256=1ALPhBBCE_t4GrxM0Fa3hQ-jkORJgeWNySnZ42bsi0g,7382
|
|
|
20
20
|
cadwyn/structure/endpoints.py,sha256=9FFnbqPM9v0CP6J6tGhMNKVvWqA9u3ZjI2Fannr2Rl0,5933
|
|
21
21
|
cadwyn/structure/enums.py,sha256=bZL-iUOUFi9ZYlMZJw-tAix2yrgCp3gH3N2gwO44LUU,1043
|
|
22
22
|
cadwyn/structure/schemas.py,sha256=D0BD1D3v9MRdVWchU9JM2zHd0dvB0UgXHDGFCe5aQZc,8209
|
|
23
|
-
cadwyn/structure/versions.py,sha256=
|
|
24
|
-
cadwyn-4.4.
|
|
25
|
-
cadwyn-4.4.
|
|
26
|
-
cadwyn-4.4.
|
|
27
|
-
cadwyn-4.4.
|
|
28
|
-
cadwyn-4.4.
|
|
23
|
+
cadwyn/structure/versions.py,sha256=7vbKSj0XwYNDbDXRAhB4iHT1K1YCRAcvXsQrC6rZn7w,33731
|
|
24
|
+
cadwyn-4.4.2.dist-info/METADATA,sha256=jXeXnBO2beIecKbyGh1RGv_Fq1TQpgc46_dXHIGLjcs,4504
|
|
25
|
+
cadwyn-4.4.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
26
|
+
cadwyn-4.4.2.dist-info/entry_points.txt,sha256=mGX8wl-Xfhpr5M93SUmkykaqinUaYAvW9rtDSX54gx0,47
|
|
27
|
+
cadwyn-4.4.2.dist-info/licenses/LICENSE,sha256=KeCWewiDQYpmSnzF-p_0YpoWiyDcUPaCuG8OWQs4ig4,1072
|
|
28
|
+
cadwyn-4.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|