cadwyn 4.6.0a1__tar.gz → 5.0.0a1__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-4.6.0a1 → cadwyn-5.0.0a1}/.pre-commit-config.yaml +5 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/CHANGELOG.md +24 -3
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/PKG-INFO +1 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/_render.py +4 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/applications.py +114 -46
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/changelogs.py +5 -6
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/exceptions.py +2 -3
- cadwyn-5.0.0a1/cadwyn/middleware.py +119 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/route_generation.py +5 -5
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/routing.py +48 -68
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/schema_generation.py +8 -7
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/common.py +1 -2
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/versions.py +38 -41
- cadwyn-4.6.0a1/docs/concepts/api_version_header_and_context_variables.md → cadwyn-5.0.0a1/docs/concepts/api_version_parameter_and_context_variables.md +1 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/main_app.md +1 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/version_changes.md +1 -1
- cadwyn-5.0.0a1/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +93 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +2 -2
- cadwyn-5.0.0a1/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +10 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/quickstart/setup.md +3 -3
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/quickstart/tutorial.md +4 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +6 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +6 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +9 -3
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +3 -1
- cadwyn-5.0.0a1/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +145 -0
- cadwyn-5.0.0a1/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +71 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/tutorial/block003.py +7 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/tutorial/tests/test_block001.py +8 -2
- cadwyn-5.0.0a1/docs_src/quickstart/tutorial/tests/test_block002.py +34 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/tutorial/tests/test_block003.py +22 -6
- cadwyn-5.0.0a1/docs_src/ruff.toml +1 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/mkdocs.yml +14 -5
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/pyproject.toml +2 -1
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/app_for_testing_routing.py +7 -7
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/render/complex/versions.py +2 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/render/versions.py +2 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/versioned_app/app.py +19 -7
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/conftest.py +10 -11
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_applications.py +90 -33
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_changelog.py +3 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_data_migrations.py +74 -83
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_render.py +1 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_router_generation.py +66 -67
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_router_generation_with_from_future_annotations.py +4 -4
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_routing.py +12 -4
- cadwyn-5.0.0a1/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_structure.py +37 -39
- cadwyn-5.0.0a1/tests/tutorial/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/tutorial/main.py +3 -4
- cadwyn-5.0.0a1/tests/versioning_styles/__init__.py +0 -0
- cadwyn-5.0.0a1/tests/versioning_styles/test_versioning_formats.py +48 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/uv.lock +99 -141
- cadwyn-4.6.0a1/cadwyn/middleware.py +0 -80
- cadwyn-4.6.0a1/docs_src/quickstart/tutorial/tests/test_block002.py +0 -24
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/workflows/ci.yaml +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.github/workflows/release.yaml +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/.gitignore +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/LICENSE +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/Makefile +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/README.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/__main__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/_asts.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/_importer.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/_utils.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/py.typed +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/static/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/static/docs.html +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/data.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/enums.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/CNAME +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/changelogs.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/cli.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/index.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/methodology.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/concepts/testing.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/home/CONTRIBUTING.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/how_to/index.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/img/sponsor_logos/monite.png +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/index.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/plugin.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs/theory/literature.md +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-4.6.0a1/docs_src/quickstart → cadwyn-5.0.0a1/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates}/__init__.py +0 -0
- {cadwyn-4.6.0a1/docs_src/quickstart/setup → cadwyn-5.0.0a1/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests}/__init__.py +0 -0
- {cadwyn-4.6.0a1/docs_src/quickstart/setup/tests → cadwyn-5.0.0a1/docs_src/quickstart}/__init__.py +0 -0
- {cadwyn-4.6.0a1/docs_src/quickstart/tutorial → cadwyn-5.0.0a1/docs_src/quickstart/setup}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-4.6.0a1/docs_src/quickstart/tutorial → cadwyn-5.0.0a1/docs_src/quickstart/setup}/tests/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-4.6.0a1/tests/_data → cadwyn-5.0.0a1/docs_src/quickstart/tutorial}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-4.6.0a1/tests/_resources → cadwyn-5.0.0a1/docs_src/quickstart/tutorial/tests}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/ruff.toml +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/scripts/fix_links.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/scripts/split_md.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/__init__.py +0 -0
- {cadwyn-4.6.0a1/tests/_resources/render → cadwyn-5.0.0a1/tests/_data}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-4.6.0a1/tests/_resources/render/complex → cadwyn-5.0.0a1/tests/_resources}/__init__.py +0 -0
- {cadwyn-4.6.0a1/tests/_resources/versioned_app → cadwyn-5.0.0a1/tests/_resources/render}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/render/classes.py +0 -0
- {cadwyn-4.6.0a1/tests/test_schema_generation → cadwyn-5.0.0a1/tests/_resources/render/complex}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/utils.py +0 -0
- {cadwyn-4.6.0a1/tests/tutorial → cadwyn-5.0.0a1/tests/_resources/versioned_app}/__init__.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_cli.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tests/tutorial/test_example.py +0 -0
- {cadwyn-4.6.0a1 → cadwyn-5.0.0a1}/tox.ini +0 -0
|
@@ -5,11 +5,32 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.0.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* Support for URL path version prefixes instead of version headers. You can control it with the `api_version_location` argument of `cadwyn.Cadwyn`.
|
|
13
|
+
* Support for arbitrary strings as versions. You can control the format of the version with the `api_version_format` argument of `cadwyn.Cadwyn`.
|
|
14
|
+
* Extensibility of version picking logic with a new `VersionPickingMiddleware` class that you can pass to the `versioning_middleware_class` argument of `cadwyn.Cadwyn`.
|
|
15
|
+
* `api_version_default_value` argument to `cadwyn.Cadwyn` to set a default version for unversioned requests. It can be a string or an async callable that returns a string.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
* `cadwyn.Version`, `cadwyn.VersionBundle`, and `cadwyn.VersionBundle.api_version_var` now store versions as strings instead of dates. Date types can still be passed to `cadwyn.Version` but there is no guarantee that they will be supported in the future. However, ISO dates with a string type are guaranteed to be supported.
|
|
20
|
+
* Cadwyn's `api_version_header_name` argument is now deprecated in favor of `api_version_parameter_name`
|
|
21
|
+
* `cadwyn.Cadwyn.add_header_versioned_routers` method is now deprecated in favor of `cadwyn.Cadwyn.generate_and_include_versioned_routers`. It will be removed in version 6.0.0
|
|
22
|
+
|
|
23
|
+
## Removed
|
|
24
|
+
|
|
25
|
+
* `HeaderVersioningMiddleware` in favor of `VersionPickingMiddleware` because we now support more than just headers
|
|
26
|
+
|
|
27
|
+
## [4.6.0]
|
|
28
|
+
|
|
8
29
|
### Added
|
|
9
30
|
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
31
|
+
* Support for more field attributes in `schema.had()` and `schema.didnt_have()`: `field_title_generator`, `fail_fast`, `coerce_numbers_to_str`, `union_mode`, `allow_mutation`, `pattern`, `discriminator`
|
|
32
|
+
* Support for forwardrefs in body fields (for example, when you use `from __future__ import annotations` in the file with your routes)
|
|
33
|
+
* Support for forwardrefs in route dependencies
|
|
13
34
|
|
|
14
35
|
## [4.5.0]
|
|
15
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0a1
|
|
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
|
|
@@ -146,7 +146,10 @@ def _generate_field_ast(field: PydanticFieldWrapper) -> ast.Call:
|
|
|
146
146
|
func=ast.Name("Field"),
|
|
147
147
|
args=[],
|
|
148
148
|
keywords=[
|
|
149
|
-
ast.keyword(
|
|
149
|
+
ast.keyword(
|
|
150
|
+
arg=attr,
|
|
151
|
+
value=ast.parse(get_fancy_repr(attr_value), mode="eval").body,
|
|
152
|
+
)
|
|
150
153
|
for attr, attr_value in field.passed_field_attributes.items()
|
|
151
154
|
],
|
|
152
155
|
)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import dataclasses
|
|
2
|
-
import
|
|
3
|
-
from collections.abc import Callable, Coroutine, Sequence
|
|
2
|
+
import warnings
|
|
3
|
+
from collections.abc import Awaitable, Callable, Coroutine, Sequence
|
|
4
4
|
from datetime import date
|
|
5
5
|
from logging import getLogger
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, cast
|
|
7
|
+
from typing import TYPE_CHECKING, Annotated, Any, cast
|
|
8
8
|
|
|
9
|
+
import fastapi
|
|
9
10
|
from fastapi import APIRouter, FastAPI, HTTPException, routing
|
|
10
11
|
from fastapi.datastructures import Default
|
|
11
12
|
from fastapi.openapi.docs import (
|
|
@@ -23,15 +24,26 @@ from starlette.requests import Request
|
|
|
23
24
|
from starlette.responses import JSONResponse, Response
|
|
24
25
|
from starlette.routing import BaseRoute, Route
|
|
25
26
|
from starlette.types import Lifespan
|
|
26
|
-
from typing_extensions import Self
|
|
27
|
+
from typing_extensions import Self, assert_never, deprecated
|
|
27
28
|
|
|
28
29
|
from cadwyn._utils import same_definition_as_in
|
|
29
30
|
from cadwyn.changelogs import CadwynChangelogResource, _generate_changelog
|
|
30
|
-
from cadwyn.
|
|
31
|
+
from cadwyn.exceptions import CadwynStructureError
|
|
32
|
+
from cadwyn.middleware import (
|
|
33
|
+
APIVersionFormat,
|
|
34
|
+
APIVersionLocation,
|
|
35
|
+
HeaderVersionManager,
|
|
36
|
+
URLVersionManager,
|
|
37
|
+
VersionPickingMiddleware,
|
|
38
|
+
_generate_api_version_dependency,
|
|
39
|
+
)
|
|
31
40
|
from cadwyn.route_generation import generate_versioned_routers
|
|
32
|
-
from cadwyn.routing import
|
|
41
|
+
from cadwyn.routing import _RootCadwynAPIRouter
|
|
33
42
|
from cadwyn.structure import VersionBundle
|
|
34
43
|
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from cadwyn.structure.common import VersionType
|
|
46
|
+
|
|
35
47
|
CURR_DIR = Path(__file__).resolve()
|
|
36
48
|
logger = getLogger(__name__)
|
|
37
49
|
|
|
@@ -48,7 +60,18 @@ class Cadwyn(FastAPI):
|
|
|
48
60
|
self,
|
|
49
61
|
*,
|
|
50
62
|
versions: VersionBundle,
|
|
51
|
-
api_version_header_name:
|
|
63
|
+
api_version_header_name: Annotated[
|
|
64
|
+
str | None,
|
|
65
|
+
deprecated(
|
|
66
|
+
"api_version_header_name is deprecated and will be removed in the future. "
|
|
67
|
+
"Use api_version_parameter_name instead."
|
|
68
|
+
),
|
|
69
|
+
] = None,
|
|
70
|
+
api_version_location: APIVersionLocation = "custom_header",
|
|
71
|
+
api_version_format: APIVersionFormat = "date",
|
|
72
|
+
api_version_parameter_name: str = "x-api-version",
|
|
73
|
+
api_version_default_value: str | None | Callable[[Request], Awaitable[str]] = None,
|
|
74
|
+
versioning_middleware_class: type[VersionPickingMiddleware] = VersionPickingMiddleware,
|
|
52
75
|
changelog_url: str | None = "/changelog",
|
|
53
76
|
include_changelog_url_in_schema: bool = True,
|
|
54
77
|
debug: bool = False,
|
|
@@ -100,6 +123,15 @@ class Cadwyn(FastAPI):
|
|
|
100
123
|
self._dependency_overrides_provider = FakeDependencyOverridesProvider({})
|
|
101
124
|
self._cadwyn_initialized = False
|
|
102
125
|
|
|
126
|
+
if api_version_header_name is not None:
|
|
127
|
+
warnings.warn(
|
|
128
|
+
"api_version_header_name is deprecated and will be removed in the future. "
|
|
129
|
+
"Use api_version_parameter_name instead.",
|
|
130
|
+
DeprecationWarning,
|
|
131
|
+
stacklevel=2,
|
|
132
|
+
)
|
|
133
|
+
api_version_parameter_name = api_version_header_name
|
|
134
|
+
|
|
103
135
|
super().__init__(
|
|
104
136
|
debug=debug,
|
|
105
137
|
title=title,
|
|
@@ -137,6 +169,18 @@ class Cadwyn(FastAPI):
|
|
|
137
169
|
separate_input_output_schemas=separate_input_output_schemas,
|
|
138
170
|
**extra,
|
|
139
171
|
)
|
|
172
|
+
|
|
173
|
+
self._versioned_webhook_routers: dict[VersionType, APIRouter] = {}
|
|
174
|
+
self._latest_version_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
|
|
175
|
+
|
|
176
|
+
self.changelog_url = changelog_url
|
|
177
|
+
self.include_changelog_url_in_schema = include_changelog_url_in_schema
|
|
178
|
+
|
|
179
|
+
self.docs_url = docs_url
|
|
180
|
+
self.redoc_url = redoc_url
|
|
181
|
+
self.openapi_url = openapi_url
|
|
182
|
+
self.redoc_url = redoc_url
|
|
183
|
+
|
|
140
184
|
self._kwargs_to_router: dict[str, Any] = {
|
|
141
185
|
"routes": routes,
|
|
142
186
|
"redirect_slashes": redirect_slashes,
|
|
@@ -152,32 +196,48 @@ class Cadwyn(FastAPI):
|
|
|
152
196
|
"responses": responses,
|
|
153
197
|
"generate_unique_id_function": generate_unique_id_function,
|
|
154
198
|
}
|
|
155
|
-
self.
|
|
199
|
+
self.api_version_format = api_version_format
|
|
200
|
+
self.api_version_parameter_name = api_version_parameter_name
|
|
201
|
+
self.api_version_pythonic_parameter_name = api_version_parameter_name.replace("-", "_")
|
|
202
|
+
if api_version_location == "custom_header":
|
|
203
|
+
self._api_version_manager = HeaderVersionManager(api_version_parameter_name=api_version_parameter_name)
|
|
204
|
+
self._api_version_fastapi_depends_class = fastapi.Header
|
|
205
|
+
elif api_version_location == "path":
|
|
206
|
+
self._api_version_manager = URLVersionManager(possible_version_values=self.versions._version_values_set)
|
|
207
|
+
self._api_version_fastapi_depends_class = fastapi.Path
|
|
208
|
+
else:
|
|
209
|
+
assert_never(api_version_location)
|
|
210
|
+
# TODO: Add a test validating the error message when there are no versions
|
|
211
|
+
default_version_example = next(iter(self.versions._version_values_set))
|
|
212
|
+
if api_version_format == "date":
|
|
213
|
+
self.api_version_validation_data_type = date
|
|
214
|
+
elif api_version_format == "string":
|
|
215
|
+
self.api_version_validation_data_type = str
|
|
216
|
+
else:
|
|
217
|
+
assert_never(default_version_example)
|
|
218
|
+
self.router: _RootCadwynAPIRouter = _RootCadwynAPIRouter( # pyright: ignore[reportIncompatibleVariableOverride]
|
|
156
219
|
**self._kwargs_to_router,
|
|
157
|
-
|
|
220
|
+
api_version_parameter_name=api_version_parameter_name,
|
|
158
221
|
api_version_var=self.versions.api_version_var,
|
|
222
|
+
api_version_format=api_version_format,
|
|
159
223
|
)
|
|
160
|
-
self._versioned_webhook_routers: dict[date, APIRouter] = {}
|
|
161
|
-
self._latest_version_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
|
|
162
|
-
|
|
163
|
-
self.changelog_url = changelog_url
|
|
164
|
-
self.include_changelog_url_in_schema = include_changelog_url_in_schema
|
|
165
|
-
|
|
166
|
-
self.docs_url = docs_url
|
|
167
|
-
self.redoc_url = redoc_url
|
|
168
|
-
self.openapi_url = openapi_url
|
|
169
|
-
self.redoc_url = redoc_url
|
|
170
|
-
|
|
171
224
|
unversioned_router = APIRouter(**self._kwargs_to_router)
|
|
172
225
|
self._add_utility_endpoints(unversioned_router)
|
|
173
226
|
self._add_default_versioned_routers()
|
|
174
227
|
self.include_router(unversioned_router)
|
|
175
228
|
self.add_middleware(
|
|
176
|
-
|
|
177
|
-
|
|
229
|
+
versioning_middleware_class,
|
|
230
|
+
api_version_parameter_name=api_version_parameter_name,
|
|
231
|
+
api_version_manager=self._api_version_manager,
|
|
232
|
+
api_version_default_value=api_version_default_value,
|
|
178
233
|
api_version_var=self.versions.api_version_var,
|
|
179
|
-
default_response_class=default_response_class,
|
|
180
234
|
)
|
|
235
|
+
if self.api_version_format == "date" and (
|
|
236
|
+
sorted(self.versions.versions, key=lambda v: v.value, reverse=True) != list(self.versions.versions)
|
|
237
|
+
):
|
|
238
|
+
raise CadwynStructureError(
|
|
239
|
+
"Versions are not sorted correctly. Please sort them in descending order.",
|
|
240
|
+
)
|
|
181
241
|
|
|
182
242
|
@same_definition_as_in(FastAPI.__call__)
|
|
183
243
|
async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
|
|
@@ -193,7 +253,7 @@ class Cadwyn(FastAPI):
|
|
|
193
253
|
versions=self.versions,
|
|
194
254
|
)
|
|
195
255
|
for version, router in generated_routers.endpoints.items():
|
|
196
|
-
self.
|
|
256
|
+
self._add_versioned_routers(router, version=version)
|
|
197
257
|
|
|
198
258
|
for version, router in generated_routers.webhooks.items():
|
|
199
259
|
self._versioned_webhook_routers[version] = router
|
|
@@ -267,26 +327,19 @@ class Cadwyn(FastAPI):
|
|
|
267
327
|
self._latest_version_router.include_router(router)
|
|
268
328
|
|
|
269
329
|
async def openapi_jsons(self, req: Request) -> JSONResponse:
|
|
270
|
-
|
|
271
|
-
not_found_error = HTTPException(
|
|
272
|
-
status_code=404,
|
|
273
|
-
detail=f"OpenApi file of with version `{raw_version}` not found",
|
|
274
|
-
)
|
|
275
|
-
try:
|
|
276
|
-
version = datetime.date.fromisoformat(raw_version) # pyright: ignore[reportArgumentType]
|
|
277
|
-
# TypeError when raw_version is None
|
|
278
|
-
# ValueError when raw_version is of the non-iso format
|
|
279
|
-
except (ValueError, TypeError):
|
|
280
|
-
version = raw_version
|
|
330
|
+
version = req.query_params.get("version") or req.headers.get(self.router.api_version_parameter_name)
|
|
281
331
|
|
|
282
|
-
if
|
|
332
|
+
if version in self.router.versioned_routers:
|
|
283
333
|
routes = self.router.versioned_routers[version].routes
|
|
284
|
-
formatted_version = version
|
|
334
|
+
formatted_version = version
|
|
285
335
|
elif version == "unversioned" and self._there_are_public_unversioned_routes():
|
|
286
336
|
routes = self.router.unversioned_routes
|
|
287
337
|
formatted_version = "unversioned"
|
|
288
338
|
else:
|
|
289
|
-
raise
|
|
339
|
+
raise HTTPException(
|
|
340
|
+
status_code=404,
|
|
341
|
+
detail=f"OpenApi file of with version `{version}` not found",
|
|
342
|
+
)
|
|
290
343
|
|
|
291
344
|
# Add root path to servers when mounted as sub-app or proxy is used
|
|
292
345
|
urls = (server_data.get("url") for server_data in self.servers)
|
|
@@ -296,7 +349,7 @@ class Cadwyn(FastAPI):
|
|
|
296
349
|
self.servers.insert(0, {"url": root_path})
|
|
297
350
|
|
|
298
351
|
webhook_routes = None
|
|
299
|
-
if
|
|
352
|
+
if version in self._versioned_webhook_routers:
|
|
300
353
|
webhook_routes = self._versioned_webhook_routers[version].routes
|
|
301
354
|
|
|
302
355
|
return JSONResponse(
|
|
@@ -354,7 +407,7 @@ class Cadwyn(FastAPI):
|
|
|
354
407
|
base_host = str(req.base_url).rstrip("/")
|
|
355
408
|
root_path = self._extract_root_path(req)
|
|
356
409
|
base_url = base_host + root_path
|
|
357
|
-
table = {version: f"{base_url}{docs_url}?version={version}" for version in self.router.
|
|
410
|
+
table = {version: f"{base_url}{docs_url}?version={version}" for version in self.router.versions}
|
|
358
411
|
if self._there_are_public_unversioned_routes():
|
|
359
412
|
table |= {"unversioned": f"{base_url}{docs_url}?version=unversioned"}
|
|
360
413
|
return self._templates.TemplateResponse(
|
|
@@ -362,6 +415,7 @@ class Cadwyn(FastAPI):
|
|
|
362
415
|
{"request": req, "table": table},
|
|
363
416
|
)
|
|
364
417
|
|
|
418
|
+
@deprecated("Use generate_and_include_versioned_routers and VersionBundle versions instead")
|
|
365
419
|
def add_header_versioned_routers(
|
|
366
420
|
self,
|
|
367
421
|
first_router: APIRouter,
|
|
@@ -370,15 +424,20 @@ class Cadwyn(FastAPI):
|
|
|
370
424
|
) -> list[BaseRoute]:
|
|
371
425
|
"""Add all routes from routers to be routed using header_value and return the added routes"""
|
|
372
426
|
try:
|
|
373
|
-
|
|
427
|
+
date.fromisoformat(header_value)
|
|
374
428
|
except ValueError as e:
|
|
375
429
|
raise ValueError("header_value should be in ISO 8601 format") from e
|
|
376
430
|
|
|
431
|
+
return self._add_versioned_routers(first_router, *other_routers, version=header_value)
|
|
432
|
+
|
|
433
|
+
def _add_versioned_routers(
|
|
434
|
+
self, first_router: APIRouter, *other_routers: APIRouter, version: str
|
|
435
|
+
) -> list[BaseRoute]:
|
|
377
436
|
added_routes: list[BaseRoute] = []
|
|
378
|
-
if
|
|
379
|
-
self.router.versioned_routers[
|
|
437
|
+
if version not in self.router.versioned_routers: # pragma: no branch
|
|
438
|
+
self.router.versioned_routers[version] = APIRouter(**self._kwargs_to_router)
|
|
380
439
|
|
|
381
|
-
versioned_router = self.router.versioned_routers[
|
|
440
|
+
versioned_router = self.router.versioned_routers[version]
|
|
382
441
|
if self.openapi_url is not None: # pragma: no branch
|
|
383
442
|
versioned_router.add_route(
|
|
384
443
|
path=self.openapi_url,
|
|
@@ -389,9 +448,18 @@ class Cadwyn(FastAPI):
|
|
|
389
448
|
|
|
390
449
|
added_route_count = 0
|
|
391
450
|
for router in (first_router, *other_routers):
|
|
392
|
-
self.router.versioned_routers[
|
|
451
|
+
self.router.versioned_routers[version].include_router(
|
|
393
452
|
router,
|
|
394
|
-
dependencies=[
|
|
453
|
+
dependencies=[
|
|
454
|
+
Depends(
|
|
455
|
+
_generate_api_version_dependency(
|
|
456
|
+
api_version_pythonic_parameter_name=self.api_version_pythonic_parameter_name,
|
|
457
|
+
default_value=version,
|
|
458
|
+
fastapi_depends_class=self._api_version_fastapi_depends_class,
|
|
459
|
+
validation_data_type=self.api_version_validation_data_type,
|
|
460
|
+
)
|
|
461
|
+
)
|
|
462
|
+
],
|
|
395
463
|
)
|
|
396
464
|
added_route_count += len(router.routes)
|
|
397
465
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import copy
|
|
2
|
-
import datetime
|
|
3
2
|
import sys
|
|
4
3
|
from enum import auto
|
|
5
4
|
from logging import getLogger
|
|
@@ -22,7 +21,7 @@ from pydantic import BaseModel, Field, RootModel
|
|
|
22
21
|
from cadwyn._asts import GenericAliasUnion
|
|
23
22
|
from cadwyn._utils import Sentinel
|
|
24
23
|
from cadwyn.route_generation import _get_routes
|
|
25
|
-
from cadwyn.routing import
|
|
24
|
+
from cadwyn.routing import _RootCadwynAPIRouter
|
|
26
25
|
from cadwyn.schema_generation import SchemaGenerator, _change_field_in_model, generate_versioned_models
|
|
27
26
|
from cadwyn.structure.versions import PossibleInstructions, VersionBundle, VersionChange, VersionChangeWithSideEffects
|
|
28
27
|
|
|
@@ -62,15 +61,15 @@ def hidden(instruction_or_version_change: T) -> T:
|
|
|
62
61
|
return instruction_or_version_change
|
|
63
62
|
|
|
64
63
|
|
|
65
|
-
def _generate_changelog(versions: VersionBundle, router:
|
|
64
|
+
def _generate_changelog(versions: VersionBundle, router: _RootCadwynAPIRouter) -> "CadwynChangelogResource":
|
|
66
65
|
changelog = CadwynChangelogResource()
|
|
67
66
|
schema_generators = generate_versioned_models(versions)
|
|
68
67
|
for version, older_version in zip(versions, versions.versions[1:], strict=False):
|
|
69
68
|
routes_from_newer_version = router.versioned_routers[version.value].routes
|
|
70
69
|
schemas_from_older_version = get_fields_from_routes(router.versioned_routers[older_version.value].routes)
|
|
71
70
|
version_changelog = CadwynVersion(value=version.value)
|
|
72
|
-
generator_from_newer_version = schema_generators[version.value
|
|
73
|
-
generator_from_older_version = schema_generators[older_version.value
|
|
71
|
+
generator_from_newer_version = schema_generators[version.value]
|
|
72
|
+
generator_from_older_version = schema_generators[older_version.value]
|
|
74
73
|
for version_change in version.changes:
|
|
75
74
|
if version_change.is_hidden_from_changelog:
|
|
76
75
|
continue
|
|
@@ -284,7 +283,7 @@ class CadwynChangelogResource(BaseModel):
|
|
|
284
283
|
|
|
285
284
|
|
|
286
285
|
class CadwynVersion(BaseModel):
|
|
287
|
-
value:
|
|
286
|
+
value: str
|
|
288
287
|
changes: "list[CadwynVersionChange]" = Field(default_factory=list)
|
|
289
288
|
|
|
290
289
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from datetime import date
|
|
3
2
|
from typing import Any
|
|
4
3
|
|
|
5
4
|
from fastapi.routing import APIRoute
|
|
@@ -14,12 +13,12 @@ class CadwynError(Exception):
|
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class CadwynHeadRequestValidationError(CadwynError):
|
|
17
|
-
def __init__(self, errors: list[Any], body: Any, version:
|
|
16
|
+
def __init__(self, errors: list[Any], body: Any, version: str) -> None:
|
|
18
17
|
self.errors = errors
|
|
19
18
|
self.body = body
|
|
20
19
|
self.version = version
|
|
21
20
|
super().__init__(
|
|
22
|
-
f"We failed to migrate the request with version={self.version
|
|
21
|
+
f"We failed to migrate the request with version={self.version}. "
|
|
23
22
|
"This means that there is some error in your migrations or schema structure that makes it impossible "
|
|
24
23
|
"to migrate the request of that version to latest.\n"
|
|
25
24
|
f"body={self.body}\n\nerrors={json.dumps(self.errors, indent=4, ensure_ascii=False)}"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# NOTE: It's OK that any_string might not be correctly sortable such as v10 vs v9.
|
|
2
|
+
# we can simply remove waterfalling from any_string api version style.
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Awaitable, Callable
|
|
7
|
+
from contextvars import ContextVar
|
|
8
|
+
from typing import Annotated, Any, Literal, Protocol
|
|
9
|
+
|
|
10
|
+
from fastapi import Request
|
|
11
|
+
from starlette.middleware.base import BaseHTTPMiddleware, DispatchFunction, RequestResponseEndpoint
|
|
12
|
+
from starlette.types import ASGIApp
|
|
13
|
+
|
|
14
|
+
from cadwyn.structure.common import VersionType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VersionManager(Protocol):
|
|
18
|
+
def get(self, request: Request) -> str | None: ...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
VersionValidatorC = Callable[[str], VersionType]
|
|
22
|
+
VersionDependencyFactoryC = Callable[[], Callable[..., Any]]
|
|
23
|
+
|
|
24
|
+
APIVersionLocation = Literal["custom_header", "path"]
|
|
25
|
+
APIVersionFormat = Literal["date", "string"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HeaderVersionManager:
|
|
29
|
+
__slots__ = ("api_version_parameter_name",)
|
|
30
|
+
|
|
31
|
+
def __init__(self, *, api_version_parameter_name: str) -> None:
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.api_version_parameter_name = api_version_parameter_name
|
|
34
|
+
|
|
35
|
+
def get(self, request: Request) -> str | None:
|
|
36
|
+
return request.headers.get(self.api_version_parameter_name)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class URLVersionManager:
|
|
40
|
+
__slots__ = ("possible_version_values", "url_version_regex")
|
|
41
|
+
|
|
42
|
+
def __init__(self, *, possible_version_values: set[str]) -> None:
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.possible_version_values = possible_version_values
|
|
45
|
+
self.url_version_regex = re.compile(f"/({'|'.join(re.escape(v) for v in possible_version_values)})/")
|
|
46
|
+
|
|
47
|
+
def get(self, request: Request) -> str | None:
|
|
48
|
+
if m := self.url_version_regex.search(request.url.path):
|
|
49
|
+
return m.group(1)
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _generate_api_version_dependency(
|
|
54
|
+
*,
|
|
55
|
+
api_version_pythonic_parameter_name: str,
|
|
56
|
+
default_value: str,
|
|
57
|
+
fastapi_depends_class: Callable[..., Any],
|
|
58
|
+
validation_data_type: Any,
|
|
59
|
+
):
|
|
60
|
+
def api_version_dependency(**kwargs: Any):
|
|
61
|
+
# TODO: What do I return?
|
|
62
|
+
return next(iter(kwargs.values()))
|
|
63
|
+
|
|
64
|
+
api_version_dependency.__signature__ = inspect.Signature(
|
|
65
|
+
parameters=[
|
|
66
|
+
inspect.Parameter(
|
|
67
|
+
api_version_pythonic_parameter_name,
|
|
68
|
+
inspect.Parameter.KEYWORD_ONLY,
|
|
69
|
+
annotation=Annotated[
|
|
70
|
+
validation_data_type, fastapi_depends_class(openapi_examples={"default": {"value": default_value}})
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
return api_version_dependency
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class VersionPickingMiddleware(BaseHTTPMiddleware):
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
app: ASGIApp,
|
|
82
|
+
*,
|
|
83
|
+
api_version_parameter_name: str,
|
|
84
|
+
api_version_default_value: str | None | Callable[[Request], Awaitable[str]],
|
|
85
|
+
api_version_var: ContextVar[VersionType | None],
|
|
86
|
+
api_version_manager: VersionManager,
|
|
87
|
+
dispatch: DispatchFunction | None = None,
|
|
88
|
+
) -> None:
|
|
89
|
+
super().__init__(app, dispatch)
|
|
90
|
+
|
|
91
|
+
self.api_version_parameter_name = api_version_parameter_name
|
|
92
|
+
self._api_version_manager = api_version_manager
|
|
93
|
+
self.api_version_var = api_version_var
|
|
94
|
+
self.api_version_default_value = api_version_default_value
|
|
95
|
+
|
|
96
|
+
async def dispatch(
|
|
97
|
+
self,
|
|
98
|
+
request: Request,
|
|
99
|
+
call_next: RequestResponseEndpoint,
|
|
100
|
+
):
|
|
101
|
+
# We handle api version at middleware level because if we try to add a Dependency to all routes, it won't work:
|
|
102
|
+
# we use this header for routing so the user will simply get a 404 if the header is invalid.
|
|
103
|
+
api_version = self._api_version_manager.get(request)
|
|
104
|
+
|
|
105
|
+
if api_version is None:
|
|
106
|
+
if callable(self.api_version_default_value): # pragma: no cover # TODO
|
|
107
|
+
api_version = await self.api_version_default_value(request)
|
|
108
|
+
else:
|
|
109
|
+
api_version = self.api_version_default_value
|
|
110
|
+
|
|
111
|
+
self.api_version_var.set(api_version)
|
|
112
|
+
response = await call_next(request)
|
|
113
|
+
|
|
114
|
+
if api_version is not None:
|
|
115
|
+
# We return it because we will be returning the **matched** version, not the requested one.
|
|
116
|
+
# In date-based versioning with waterfalling, it makes sense.
|
|
117
|
+
response.headers[self.api_version_parameter_name] = api_version
|
|
118
|
+
|
|
119
|
+
return response
|
|
@@ -36,7 +36,7 @@ from cadwyn.schema_generation import (
|
|
|
36
36
|
generate_versioned_models,
|
|
37
37
|
)
|
|
38
38
|
from cadwyn.structure import Version, VersionBundle
|
|
39
|
-
from cadwyn.structure.common import Endpoint,
|
|
39
|
+
from cadwyn.structure.common import Endpoint, VersionType
|
|
40
40
|
from cadwyn.structure.endpoints import (
|
|
41
41
|
EndpointDidntExistInstruction,
|
|
42
42
|
EndpointExistedInstruction,
|
|
@@ -62,8 +62,8 @@ class _EndpointInfo:
|
|
|
62
62
|
|
|
63
63
|
@dataclass(slots=True, frozen=True)
|
|
64
64
|
class GeneratedRouters(Generic[_R, _WR]):
|
|
65
|
-
endpoints: dict[
|
|
66
|
-
webhooks: dict[
|
|
65
|
+
endpoints: dict[VersionType, _R]
|
|
66
|
+
webhooks: dict[VersionType, _WR]
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
def generate_versioned_routers(
|
|
@@ -125,8 +125,8 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
125
125
|
def transform(self) -> GeneratedRouters[_R, _WR]:
|
|
126
126
|
router = copy_router(self.parent_router)
|
|
127
127
|
webhook_router = copy_router(self.parent_webhooks_router)
|
|
128
|
-
routers: dict[
|
|
129
|
-
webhook_routers: dict[
|
|
128
|
+
routers: dict[VersionType, _R] = {}
|
|
129
|
+
webhook_routers: dict[VersionType, _WR] = {}
|
|
130
130
|
|
|
131
131
|
for version in self.versions:
|
|
132
132
|
self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(router)
|