cadwyn 2.3.0rc0__tar.gz → 2.3.1__tar.gz
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-2.3.0rc0 → cadwyn-2.3.1}/PKG-INFO +6 -6
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/README.md +4 -4
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/__init__.py +2 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/main.py +2 -3
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/routing.py +51 -31
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/versions.py +9 -7
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/pyproject.toml +2 -2
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/setup.py +3 -3
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/LICENSE +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/__main__.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/_utils.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/codegen.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/exceptions.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/py.typed +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/common.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/data.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/enums.py +0 -0
- {cadwyn-2.3.0rc0 → cadwyn-2.3.1}/cadwyn/structure/schemas.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Modern Stripe-like API versioning in FastAPI
|
|
5
5
|
Home-page: https://github.com/zmievsa/cadwyn
|
|
6
6
|
License: MIT
|
|
@@ -35,7 +35,7 @@ Requires-Dist: fastapi (>=0.96.1)
|
|
|
35
35
|
Requires-Dist: pydantic (>=1.10.0,<2.0.0)
|
|
36
36
|
Requires-Dist: typer (>=0.7.0); extra == "cli"
|
|
37
37
|
Requires-Dist: typing-extensions
|
|
38
|
-
Requires-Dist: verselect
|
|
38
|
+
Requires-Dist: verselect (>=0.0.6)
|
|
39
39
|
Project-URL: Repository, https://github.com/zmievsa/cadwyn
|
|
40
40
|
Description-Content-Type: text/markdown
|
|
41
41
|
|
|
@@ -73,10 +73,10 @@ Its [approach](./docs/theory.md#ii-migration-based-response-building) will be us
|
|
|
73
73
|
|
|
74
74
|
The [documentation](https://docs.cadwyn.dev) has everything you need to get started. It is recommended to read it in the following order:
|
|
75
75
|
|
|
76
|
-
1. [Tutorial](
|
|
77
|
-
2. [Recipes](
|
|
78
|
-
3. [Reference](
|
|
79
|
-
4. [Theory](
|
|
76
|
+
1. [Tutorial](./tutorial.md)
|
|
77
|
+
2. [Recipes](./recipes.md)
|
|
78
|
+
3. [Reference](./reference.md)
|
|
79
|
+
4. [Theory](./theory.md) <!-- TODO: Move section about cadwyn's approach to the beginning and move other approaches and "how we got here" to another article -->
|
|
80
80
|
|
|
81
81
|
## Similar projects
|
|
82
82
|
|
|
@@ -32,10 +32,10 @@ Its [approach](./docs/theory.md#ii-migration-based-response-building) will be us
|
|
|
32
32
|
|
|
33
33
|
The [documentation](https://docs.cadwyn.dev) has everything you need to get started. It is recommended to read it in the following order:
|
|
34
34
|
|
|
35
|
-
1. [Tutorial](
|
|
36
|
-
2. [Recipes](
|
|
37
|
-
3. [Reference](
|
|
38
|
-
4. [Theory](
|
|
35
|
+
1. [Tutorial](./tutorial.md)
|
|
36
|
+
2. [Recipes](./recipes.md)
|
|
37
|
+
3. [Reference](./reference.md)
|
|
38
|
+
4. [Theory](./theory.md) <!-- TODO: Move section about cadwyn's approach to the beginning and move other approaches and "how we got here" to another article -->
|
|
39
39
|
|
|
40
40
|
## Similar projects
|
|
41
41
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import importlib.metadata
|
|
2
2
|
|
|
3
3
|
from .codegen import generate_code_for_versioned_packages
|
|
4
|
+
from .main import Cadwyn
|
|
4
5
|
from .routing import VersionedAPIRouter, generate_versioned_routers
|
|
5
6
|
from .structure import VersionBundle, internal_body_representation_of
|
|
6
7
|
|
|
7
8
|
__version__ = importlib.metadata.version("cadwyn")
|
|
8
9
|
__all__ = [
|
|
10
|
+
"Cadwyn",
|
|
9
11
|
"VersionedAPIRouter",
|
|
10
12
|
"generate_code_for_versioned_packages",
|
|
11
13
|
"VersionBundle",
|
|
@@ -18,7 +18,7 @@ from cadwyn.routing import generate_versioned_routers
|
|
|
18
18
|
from cadwyn.structure import VersionBundle
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class Cadwyn(HeaderRoutingFastAPI):
|
|
22
22
|
def __init__(
|
|
23
23
|
self,
|
|
24
24
|
*,
|
|
@@ -37,7 +37,7 @@ class _Cadwyn(HeaderRoutingFastAPI):
|
|
|
37
37
|
default_response_class: type[Response] = Default(JSONResponse), # noqa: B008
|
|
38
38
|
redirect_slashes: bool = True,
|
|
39
39
|
docs_url: str | None = "/docs",
|
|
40
|
-
redoc_url:
|
|
40
|
+
redoc_url: None = None,
|
|
41
41
|
swagger_ui_oauth2_redirect_url: str | None = "/docs/oauth2-redirect",
|
|
42
42
|
swagger_ui_init_oauth: dict[str, Any] | None = None,
|
|
43
43
|
middleware: Sequence[Middleware] | None = None,
|
|
@@ -77,7 +77,6 @@ class _Cadwyn(HeaderRoutingFastAPI):
|
|
|
77
77
|
default_response_class=default_response_class,
|
|
78
78
|
redirect_slashes=redirect_slashes,
|
|
79
79
|
docs_url=docs_url,
|
|
80
|
-
redoc_url=redoc_url,
|
|
81
80
|
swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url,
|
|
82
81
|
swagger_ui_init_oauth=swagger_ui_init_oauth,
|
|
83
82
|
middleware=middleware,
|
|
@@ -41,6 +41,7 @@ from starlette.routing import (
|
|
|
41
41
|
request_response,
|
|
42
42
|
)
|
|
43
43
|
from typing_extensions import assert_never
|
|
44
|
+
from verselect.routing import VERSION_HEADER_FORMAT
|
|
44
45
|
|
|
45
46
|
from cadwyn._utils import Sentinel, UnionType, get_another_version_of_module
|
|
46
47
|
from cadwyn.codegen import _get_package_path_from_module, _get_version_dir_path
|
|
@@ -55,12 +56,18 @@ from cadwyn.structure.endpoints import (
|
|
|
55
56
|
)
|
|
56
57
|
from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPONSE_PARAM_NAME, VersionChange
|
|
57
58
|
|
|
59
|
+
__all__ = [
|
|
60
|
+
"generate_versioned_routers",
|
|
61
|
+
"VersionedAPIRouter",
|
|
62
|
+
"VERSION_HEADER_FORMAT",
|
|
63
|
+
]
|
|
64
|
+
|
|
58
65
|
_T = TypeVar("_T", bound=Callable[..., Any])
|
|
59
66
|
_R = TypeVar("_R", bound=fastapi.routing.APIRouter)
|
|
60
67
|
# This is a hack we do because we can't guarantee how the user will use the router.
|
|
61
68
|
_DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
_EndpointPath: TypeAlias = str
|
|
70
|
+
_EndpointMethod: TypeAlias = str
|
|
64
71
|
|
|
65
72
|
|
|
66
73
|
@dataclass(slots=True, frozen=True, eq=True)
|
|
@@ -72,7 +79,7 @@ class _EndpointInfo:
|
|
|
72
79
|
@dataclass(slots=True)
|
|
73
80
|
class _RouterInfo(Generic[_R]):
|
|
74
81
|
router: _R
|
|
75
|
-
routes_with_migrated_requests: dict[
|
|
82
|
+
routes_with_migrated_requests: dict[_EndpointPath, set[_EndpointMethod]]
|
|
76
83
|
route_bodies_with_migrated_requests: set[type[BaseModel]]
|
|
77
84
|
|
|
78
85
|
|
|
@@ -447,41 +454,23 @@ class _AnnotationTransformer:
|
|
|
447
454
|
return self._change_version_of_type(annotation, version_dir)
|
|
448
455
|
elif callable(annotation):
|
|
449
456
|
# TASK: https://github.com/zmievsa/cadwyn/issues/48
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
@functools.wraps(annotation)
|
|
453
|
-
async def new_callable( # pyright: ignore[reportGeneralTypeIssues]
|
|
454
|
-
*args: Any,
|
|
455
|
-
**kwargs: Any,
|
|
456
|
-
) -> Any:
|
|
457
|
-
return await annotation(*args, **kwargs)
|
|
458
|
-
|
|
459
|
-
else:
|
|
460
|
-
|
|
461
|
-
@functools.wraps(annotation)
|
|
462
|
-
def new_callable( # pyright: ignore[reportGeneralTypeIssues]
|
|
463
|
-
*args: Any,
|
|
464
|
-
**kwargs: Any,
|
|
465
|
-
) -> Any:
|
|
466
|
-
return annotation(*args, **kwargs)
|
|
467
|
-
|
|
468
|
-
# Otherwise it will have the same signature as __wrapped__
|
|
469
|
-
new_callable.__alt_wrapped__ = new_callable.__wrapped__ # pyright: ignore[reportGeneralTypeIssues]
|
|
470
|
-
del new_callable.__wrapped__
|
|
457
|
+
annotation_modifying_decorator = _copy_function(annotation)
|
|
471
458
|
old_params = inspect.signature(annotation).parameters
|
|
472
|
-
callable_annotations =
|
|
459
|
+
callable_annotations = annotation_modifying_decorator.__annotations__
|
|
473
460
|
|
|
474
|
-
|
|
475
|
-
new_callable.__annotations__ = self._change_version_of_annotations(
|
|
461
|
+
annotation_modifying_decorator.__annotations__ = self._change_version_of_annotations(
|
|
476
462
|
callable_annotations,
|
|
477
463
|
version_dir,
|
|
478
464
|
)
|
|
479
|
-
|
|
465
|
+
annotation_modifying_decorator.__defaults__ = self._change_version_of_annotations(
|
|
480
466
|
tuple(p.default for p in old_params.values() if p.default is not inspect.Signature.empty),
|
|
481
467
|
version_dir,
|
|
482
468
|
)
|
|
483
|
-
|
|
484
|
-
|
|
469
|
+
annotation_modifying_decorator.__signature__ = _generate_signature(
|
|
470
|
+
annotation_modifying_decorator,
|
|
471
|
+
old_params,
|
|
472
|
+
)
|
|
473
|
+
return annotation_modifying_decorator
|
|
485
474
|
else:
|
|
486
475
|
return annotation
|
|
487
476
|
|
|
@@ -673,7 +662,7 @@ def _get_route_from_func(
|
|
|
673
662
|
return None
|
|
674
663
|
|
|
675
664
|
|
|
676
|
-
def _get_migrated_routes_by_path(version: Version) -> dict[
|
|
665
|
+
def _get_migrated_routes_by_path(version: Version) -> dict[_EndpointPath, set[_EndpointMethod]]:
|
|
677
666
|
request_by_path_migration_instructions = [
|
|
678
667
|
version_change.alter_request_by_path_instructions for version_change in version.version_changes
|
|
679
668
|
]
|
|
@@ -683,3 +672,34 @@ def _get_migrated_routes_by_path(version: Version) -> dict[EndpointPath, set[End
|
|
|
683
672
|
for instruction in instruction_list:
|
|
684
673
|
migrated_routes[path] |= instruction.methods
|
|
685
674
|
return migrated_routes
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _copy_function(function: _T) -> _T:
|
|
678
|
+
while hasattr(function, "__alt_wrapped__"):
|
|
679
|
+
function = function.__alt_wrapped__
|
|
680
|
+
|
|
681
|
+
if inspect.iscoroutinefunction(function):
|
|
682
|
+
|
|
683
|
+
@functools.wraps(function)
|
|
684
|
+
async def annotation_modifying_decorator( # pyright: ignore[reportGeneralTypeIssues]
|
|
685
|
+
*args: Any,
|
|
686
|
+
**kwargs: Any,
|
|
687
|
+
) -> Any:
|
|
688
|
+
return await function(*args, **kwargs)
|
|
689
|
+
|
|
690
|
+
else:
|
|
691
|
+
|
|
692
|
+
@functools.wraps(function)
|
|
693
|
+
def annotation_modifying_decorator(
|
|
694
|
+
*args: Any,
|
|
695
|
+
**kwargs: Any,
|
|
696
|
+
) -> Any:
|
|
697
|
+
return function(*args, **kwargs)
|
|
698
|
+
|
|
699
|
+
# Otherwise it will have the same signature as __wrapped__ due to how inspect module works
|
|
700
|
+
annotation_modifying_decorator.__alt_wrapped__ = ( # pyright: ignore[reportGeneralTypeIssues]
|
|
701
|
+
annotation_modifying_decorator.__wrapped__
|
|
702
|
+
)
|
|
703
|
+
del annotation_modifying_decorator.__wrapped__
|
|
704
|
+
|
|
705
|
+
return cast(_T, annotation_modifying_decorator)
|
|
@@ -207,25 +207,27 @@ class Version:
|
|
|
207
207
|
class VersionBundle:
|
|
208
208
|
def __init__(
|
|
209
209
|
self,
|
|
210
|
-
|
|
210
|
+
latest_version: Version,
|
|
211
|
+
/,
|
|
212
|
+
*other_versions: Version,
|
|
211
213
|
api_version_var: APIVersionVarType,
|
|
212
214
|
) -> None:
|
|
213
215
|
super().__init__()
|
|
214
216
|
|
|
215
|
-
self.versions =
|
|
217
|
+
self.versions = (latest_version, *other_versions)
|
|
216
218
|
self.api_version_var = api_version_var
|
|
217
|
-
if sorted(versions, key=lambda v: v.value, reverse=True) != list(versions):
|
|
218
|
-
raise
|
|
219
|
+
if sorted(self.versions, key=lambda v: v.value, reverse=True) != list(self.versions):
|
|
220
|
+
raise CadwynStructureError(
|
|
219
221
|
"Versions are not sorted correctly. Please sort them in descending order.",
|
|
220
222
|
)
|
|
221
|
-
if versions[-1].version_changes:
|
|
223
|
+
if self.versions[-1].version_changes:
|
|
222
224
|
raise CadwynStructureError(
|
|
223
|
-
f'The first version "{versions[-1].value}" cannot have any version changes. '
|
|
225
|
+
f'The first version "{self.versions[-1].value}" cannot have any version changes. '
|
|
224
226
|
"Version changes are defined to migrate to/from a previous version so you "
|
|
225
227
|
"cannot define one for the very first version.",
|
|
226
228
|
)
|
|
227
229
|
version_values = set()
|
|
228
|
-
for version in versions:
|
|
230
|
+
for version in self.versions:
|
|
229
231
|
if version.value not in version_values:
|
|
230
232
|
version_values.add(version.value)
|
|
231
233
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "cadwyn"
|
|
3
|
-
version = "2.3.
|
|
3
|
+
version = "2.3.1"
|
|
4
4
|
description = "Modern Stripe-like API versioning in FastAPI"
|
|
5
5
|
authors = ["Stanislav Zmiev <zmievsa@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -36,7 +36,7 @@ typing-extensions = "*"
|
|
|
36
36
|
fastapi = ">=0.96.1"
|
|
37
37
|
pydantic = "^1.10.0"
|
|
38
38
|
typer = {version = ">=0.7.0", optional = true}
|
|
39
|
-
verselect
|
|
39
|
+
verselect = ">=0.0.6"
|
|
40
40
|
|
|
41
41
|
[tool.poetry.extras]
|
|
42
42
|
cli = ["typer"]
|
|
@@ -11,7 +11,7 @@ install_requires = \
|
|
|
11
11
|
['fastapi>=0.96.1',
|
|
12
12
|
'pydantic>=1.10.0,<2.0.0',
|
|
13
13
|
'typing-extensions',
|
|
14
|
-
'verselect
|
|
14
|
+
'verselect>=0.0.6']
|
|
15
15
|
|
|
16
16
|
extras_require = \
|
|
17
17
|
{'cli': ['typer>=0.7.0']}
|
|
@@ -21,9 +21,9 @@ entry_points = \
|
|
|
21
21
|
|
|
22
22
|
setup_kwargs = {
|
|
23
23
|
'name': 'cadwyn',
|
|
24
|
-
'version': '2.3.
|
|
24
|
+
'version': '2.3.1',
|
|
25
25
|
'description': 'Modern Stripe-like API versioning in FastAPI',
|
|
26
|
-
'long_description': '# Cadwyn\n\nModern [Stripe-like](https://stripe.com/blog/api-versioning) API versioning in FastAPI\n\n---\n\n<p align="center">\n<a href="https://github.com/zmievsa/cadwyn/actions?query=workflow%3ATests+event%3Apush+branch%3Amain" target="_blank">\n <img src="https://github.com/zmievsa/cadwyn/actions/workflows/test.yaml/badge.svg?branch=main&event=push" alt="Test">\n</a>\n<a href="https://codecov.io/gh/ovsyanka83/cadwyn" target="_blank">\n <img src="https://img.shields.io/codecov/c/github/ovsyanka83/cadwyn?color=%2334D058" alt="Coverage">\n</a>\n<a href="https://pypi.org/project/cadwyn/" target="_blank">\n <img alt="PyPI" src="https://img.shields.io/pypi/v/cadwyn?color=%2334D058&label=pypi%20package" alt="Package version">\n</a>\n<a href="https://pypi.org/project/cadwyn/" target="_blank">\n <img src="https://img.shields.io/pypi/pyversions/cadwyn?color=%2334D058" alt="Supported Python versions">\n</a>\n</p>\n\n## Who is this for?\n\nCadwyn allows you to support a single version of your code, auto-generating the code/routes for older versions. You keep versioning encapsulated in small and independent "version change" modules while your business logic knows nothing about versioning.\n\nIts [approach](./docs/theory.md#ii-migration-based-response-building) will be useful if you want to:\n\n1. Support many (>2) API versions for a long time\n2. Effortlessly backport features and bugfixes to older API versions\n\n## Get started\n\nThe [documentation](https://docs.cadwyn.dev) has everything you need to get started. It is recommended to read it in the following order:\n\n1. [Tutorial](
|
|
26
|
+
'long_description': '# Cadwyn\n\nModern [Stripe-like](https://stripe.com/blog/api-versioning) API versioning in FastAPI\n\n---\n\n<p align="center">\n<a href="https://github.com/zmievsa/cadwyn/actions?query=workflow%3ATests+event%3Apush+branch%3Amain" target="_blank">\n <img src="https://github.com/zmievsa/cadwyn/actions/workflows/test.yaml/badge.svg?branch=main&event=push" alt="Test">\n</a>\n<a href="https://codecov.io/gh/ovsyanka83/cadwyn" target="_blank">\n <img src="https://img.shields.io/codecov/c/github/ovsyanka83/cadwyn?color=%2334D058" alt="Coverage">\n</a>\n<a href="https://pypi.org/project/cadwyn/" target="_blank">\n <img alt="PyPI" src="https://img.shields.io/pypi/v/cadwyn?color=%2334D058&label=pypi%20package" alt="Package version">\n</a>\n<a href="https://pypi.org/project/cadwyn/" target="_blank">\n <img src="https://img.shields.io/pypi/pyversions/cadwyn?color=%2334D058" alt="Supported Python versions">\n</a>\n</p>\n\n## Who is this for?\n\nCadwyn allows you to support a single version of your code, auto-generating the code/routes for older versions. You keep versioning encapsulated in small and independent "version change" modules while your business logic knows nothing about versioning.\n\nIts [approach](./docs/theory.md#ii-migration-based-response-building) will be useful if you want to:\n\n1. Support many (>2) API versions for a long time\n2. Effortlessly backport features and bugfixes to older API versions\n\n## Get started\n\nThe [documentation](https://docs.cadwyn.dev) has everything you need to get started. It is recommended to read it in the following order:\n\n1. [Tutorial](./tutorial.md)\n2. [Recipes](./recipes.md)\n3. [Reference](./reference.md)\n4. [Theory](./theory.md) <!-- TODO: Move section about cadwyn\'s approach to the beginning and move other approaches and "how we got here" to another article -->\n\n## Similar projects\n\nThe following projects are trying to accomplish similar results with a lot more simplistic functionality.\n\n- <https://github.com/sjkaliski/pinned>\n- <https://github.com/phillbaker/gates>\n- <https://github.com/lukepolo/laravel-api-migrations>\n- <https://github.com/tomschlick/request-migrations>\n- <https://github.com/keygen-sh/request_migrations>\n',
|
|
27
27
|
'author': 'Stanislav Zmiev',
|
|
28
28
|
'author_email': 'zmievsa@gmail.com',
|
|
29
29
|
'maintainer': 'None',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|