cadwyn 5.2.0__tar.gz → 5.2.2__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-5.2.0 → cadwyn-5.2.2}/CHANGELOG.md +12 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/PKG-INFO +1 -1
- cadwyn-5.2.2/cadwyn/_internal/context_vars.py +9 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/applications.py +7 -1
- cadwyn-5.2.2/cadwyn/dependencies.py +5 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/middleware.py +6 -1
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/route_generation.py +59 -25
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/routing.py +10 -1
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/schema_generation.py +1 -2
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/versions.py +19 -23
- {cadwyn-5.2.0 → cadwyn-5.2.2}/pyproject.toml +1 -1
- {cadwyn-5.2.0 → cadwyn-5.2.2}/ruff.toml +1 -1
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_applications.py +13 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_router_generation.py +46 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_routing.py +54 -1
- cadwyn-5.2.2/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/uv.lock +1 -1
- cadwyn-5.2.0/cadwyn/dependencies.py +0 -5
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.gitignore +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/LICENSE +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/Makefile +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/README.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/__main__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_asts.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_importer.py +0 -0
- {cadwyn-5.2.0/cadwyn/static → cadwyn-5.2.2/cadwyn/_internal}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_render.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/_utils.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/changelogs.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/exceptions.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/py.typed +0 -0
- {cadwyn-5.2.0/docs → cadwyn-5.2.2/cadwyn/static}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/CNAME +0 -0
- {cadwyn-5.2.0/docs_src → cadwyn-5.2.2/docs}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/changelogs.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/cli.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/index.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/main_app.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/methodology.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/testing.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/version_changes.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/home/CONTRIBUTING.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/index.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/sponsor_logos/monite.png +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/index.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/plugin.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs/theory/literature.md +0 -0
- {cadwyn-5.2.0/docs_src/how_to → cadwyn-5.2.2/docs_src}/__init__.py +0 -0
- {cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas → cadwyn-5.2.2/docs_src/how_to}/__init__.py +0 -0
- {cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint → cadwyn-5.2.2/docs_src/how_to/change_openapi_schemas}/__init__.py +0 -0
- {cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests → cadwyn-5.2.2/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.2.0/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates → cadwyn-5.2.2/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.2.0/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests → cadwyn-5.2.2/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.2.0/docs_src/quickstart → cadwyn-5.2.2/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.2.0/docs_src/quickstart/setup → cadwyn-5.2.2/docs_src/quickstart}/__init__.py +0 -0
- {cadwyn-5.2.0/docs_src/quickstart/setup/tests → cadwyn-5.2.2/docs_src/quickstart/setup}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.2.0/docs_src/quickstart/tutorial → cadwyn-5.2.2/docs_src/quickstart/setup/tests}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.2.0/docs_src/quickstart/tutorial/tests → cadwyn-5.2.2/docs_src/quickstart/tutorial}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.2.0/tests/_data → cadwyn-5.2.2/docs_src/quickstart/tutorial/tests}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/docs_src/ruff.toml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/mkdocs.yml +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/scripts/fix_links.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/scripts/split_md.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/__init__.py +0 -0
- {cadwyn-5.2.0/tests/_resources → cadwyn-5.2.2/tests/_data}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.2.0/tests/_resources/render → cadwyn-5.2.2/tests/_resources}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.2.0/tests/_resources/render/complex → cadwyn-5.2.2/tests/_resources/render}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.2.0/tests/_resources/versioned_app → cadwyn-5.2.2/tests/_resources/render/complex}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/utils.py +0 -0
- {cadwyn-5.2.0/tests/test_schema_generation → cadwyn-5.2.2/tests/_resources/versioned_app}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/conftest.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_changelog.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_cli.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_render.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.2.0/tests/tutorial → cadwyn-5.2.2/tests/test_schema_generation}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_structure.py +0 -0
- {cadwyn-5.2.0/tests/versioning_styles → cadwyn-5.2.2/tests/tutorial}/__init__.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/tutorial/main.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.2.0 → cadwyn-5.2.2}/tox.ini +0 -0
|
@@ -5,6 +5,18 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.2.2]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* Whenever a route was migrated by path, we used the path and methods of the request. So if the request wanted "/v1/webhook_settings" and we renamed them to "/v1/webhook_subscriptions" -- all migrations for "/v1/webhook_subscriptions" would not get applied to any requests that wanted "/v1/webhook_settings". This effectively means that previously route renamings were incompatible with by path converters. Now we use the head route id instead of the path and methods of the request for matching so we always know which migrations to apply.
|
|
13
|
+
|
|
14
|
+
## [5.2.1]
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* [#268](https://github.com/zmievsa/cadwyn/issues/268) A bug where we received 404 for all unversioned routes when a default api version was passed to Cadwyn at initialization
|
|
19
|
+
|
|
8
20
|
## [5.2.0]
|
|
9
21
|
|
|
10
22
|
### Removed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.2.
|
|
3
|
+
Version: 5.2.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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from contextvars import ContextVar
|
|
2
|
+
|
|
3
|
+
from typing_extensions import Literal
|
|
4
|
+
|
|
5
|
+
DEFAULT_API_VERSION_VAR: "ContextVar[str | None]" = ContextVar("cadwyn_default_api_version")
|
|
6
|
+
CURRENT_DEPENDENCY_SOLVER_OPTIONS = Literal["cadwyn", "fastapi"]
|
|
7
|
+
CURRENT_DEPENDENCY_SOLVER_VAR: ContextVar[CURRENT_DEPENDENCY_SOLVER_OPTIONS] = ContextVar(
|
|
8
|
+
"cadwyn_dependencies_current_dependency_solver"
|
|
9
|
+
)
|
|
@@ -133,6 +133,12 @@ class Cadwyn(FastAPI):
|
|
|
133
133
|
stacklevel=2,
|
|
134
134
|
)
|
|
135
135
|
api_version_parameter_name = api_version_header_name
|
|
136
|
+
if api_version_default_value is not None and api_version_location == "path":
|
|
137
|
+
raise CadwynStructureError(
|
|
138
|
+
"You tried to pass an api_version_default_value while putting the API version in Path. "
|
|
139
|
+
"This is not currently supported by Cadwyn. "
|
|
140
|
+
"Please, open an issue on our github if you'd like to have it."
|
|
141
|
+
)
|
|
136
142
|
|
|
137
143
|
super().__init__(
|
|
138
144
|
debug=debug,
|
|
@@ -231,8 +237,8 @@ class Cadwyn(FastAPI):
|
|
|
231
237
|
versioning_middleware_class,
|
|
232
238
|
api_version_parameter_name=api_version_parameter_name,
|
|
233
239
|
api_version_manager=self._api_version_manager,
|
|
234
|
-
api_version_default_value=api_version_default_value,
|
|
235
240
|
api_version_var=self.versions.api_version_var,
|
|
241
|
+
api_version_default_value=api_version_default_value,
|
|
236
242
|
)
|
|
237
243
|
if self.api_version_format == "date" and (
|
|
238
244
|
sorted(self.versions.versions, key=lambda v: v.value, reverse=True) != list(self.versions.versions)
|
|
@@ -7,10 +7,12 @@ from collections.abc import Awaitable, Callable
|
|
|
7
7
|
from contextvars import ContextVar
|
|
8
8
|
from typing import Annotated, Any, Literal, Protocol, Union
|
|
9
9
|
|
|
10
|
+
import fastapi
|
|
10
11
|
from fastapi import Request
|
|
11
12
|
from starlette.middleware.base import BaseHTTPMiddleware, DispatchFunction, RequestResponseEndpoint
|
|
12
13
|
from starlette.types import ASGIApp
|
|
13
14
|
|
|
15
|
+
from cadwyn._internal.context_vars import DEFAULT_API_VERSION_VAR
|
|
14
16
|
from cadwyn.structure.common import VersionType
|
|
15
17
|
|
|
16
18
|
|
|
@@ -69,6 +71,8 @@ def _generate_api_version_dependency(
|
|
|
69
71
|
annotation=Annotated[
|
|
70
72
|
validation_data_type, fastapi_depends_class(openapi_examples={"default": {"value": default_value}})
|
|
71
73
|
],
|
|
74
|
+
# Path-based parameters do not support a default value in FastAPI :(
|
|
75
|
+
default=default_value if fastapi_depends_class != fastapi.Path else inspect.Signature.empty,
|
|
72
76
|
),
|
|
73
77
|
],
|
|
74
78
|
)
|
|
@@ -103,10 +107,11 @@ class VersionPickingMiddleware(BaseHTTPMiddleware):
|
|
|
103
107
|
api_version = self._api_version_manager.get(request)
|
|
104
108
|
|
|
105
109
|
if api_version is None:
|
|
106
|
-
if callable(self.api_version_default_value):
|
|
110
|
+
if callable(self.api_version_default_value):
|
|
107
111
|
api_version = await self.api_version_default_value(request)
|
|
108
112
|
else:
|
|
109
113
|
api_version = self.api_version_default_value
|
|
114
|
+
DEFAULT_API_VERSION_VAR.set(api_version)
|
|
110
115
|
|
|
111
116
|
self.api_version_var.set(api_version)
|
|
112
117
|
response = await call_next(request)
|
|
@@ -37,11 +37,13 @@ from cadwyn.schema_generation import (
|
|
|
37
37
|
)
|
|
38
38
|
from cadwyn.structure import Version, VersionBundle
|
|
39
39
|
from cadwyn.structure.common import Endpoint, VersionType
|
|
40
|
+
from cadwyn.structure.data import _AlterRequestByPathInstruction, _AlterResponseByPathInstruction
|
|
40
41
|
from cadwyn.structure.endpoints import (
|
|
41
42
|
EndpointDidntExistInstruction,
|
|
42
43
|
EndpointExistedInstruction,
|
|
43
44
|
EndpointHadInstruction,
|
|
44
45
|
)
|
|
46
|
+
from cadwyn.structure.versions import VersionChange
|
|
45
47
|
|
|
46
48
|
if TYPE_CHECKING:
|
|
47
49
|
from fastapi.dependencies.models import Dependant
|
|
@@ -52,6 +54,9 @@ _WR = TypeVar("_WR", bound=APIRouter, default=APIRouter)
|
|
|
52
54
|
_RouteT = TypeVar("_RouteT", bound=BaseRoute)
|
|
53
55
|
# This is a hack we do because we can't guarantee how the user will use the router.
|
|
54
56
|
_DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
|
|
57
|
+
_RoutePath = str
|
|
58
|
+
_RouteMethod = str
|
|
59
|
+
_RouteId = int
|
|
55
60
|
|
|
56
61
|
|
|
57
62
|
@dataclass(**DATACLASS_SLOTS, frozen=True, eq=True)
|
|
@@ -123,6 +128,7 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
123
128
|
]
|
|
124
129
|
|
|
125
130
|
def transform(self) -> GeneratedRouters[_R, _WR]:
|
|
131
|
+
# Copy MUST keep the order and number of routes. Otherwise, a ton of code below will break.
|
|
126
132
|
router = copy_router(self.parent_router)
|
|
127
133
|
webhook_router = copy_router(self.parent_webhooks_router)
|
|
128
134
|
routers: dict[VersionType, _R] = {}
|
|
@@ -132,7 +138,7 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
132
138
|
self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(router)
|
|
133
139
|
self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(webhook_router)
|
|
134
140
|
|
|
135
|
-
self.
|
|
141
|
+
self._attach_routes_to_data_converters(router, self.parent_router, version)
|
|
136
142
|
|
|
137
143
|
routers[version.value] = router
|
|
138
144
|
webhook_routers[version.value] = webhook_router
|
|
@@ -193,9 +199,11 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
193
199
|
]
|
|
194
200
|
return GeneratedRouters(routers, webhook_routers)
|
|
195
201
|
|
|
196
|
-
def
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
def _attach_routes_to_data_converters(self, router: APIRouter, head_router: APIRouter, version: Version):
|
|
203
|
+
# This method is way out of its league in terms of complexity. We gotta refactor it.
|
|
204
|
+
|
|
205
|
+
path_to_route_methods_mapping, head_response_models, head_request_bodies = (
|
|
206
|
+
self._extract_all_routes_identifiers_for_route_to_converter_matching(router)
|
|
199
207
|
)
|
|
200
208
|
|
|
201
209
|
for version_change in version.changes:
|
|
@@ -204,21 +212,10 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
204
212
|
*version_change.alter_request_by_path_instructions.values(),
|
|
205
213
|
]:
|
|
206
214
|
for by_path_converter in by_path_converters:
|
|
207
|
-
|
|
208
|
-
path_to_route_methods_mapping
|
|
215
|
+
self._attach_routes_by_path_converter(
|
|
216
|
+
head_router, path_to_route_methods_mapping, version_change, by_path_converter
|
|
209
217
|
)
|
|
210
218
|
|
|
211
|
-
if missing_methods:
|
|
212
|
-
raise RouteByPathConverterDoesNotApplyToAnythingError(
|
|
213
|
-
f"{by_path_converter.repr_name} "
|
|
214
|
-
f'"{version_change.__name__}.{by_path_converter.transformer.__name__}" '
|
|
215
|
-
f"failed to find routes with the following methods: {list(missing_methods)}. "
|
|
216
|
-
f"This means that you are trying to apply this converter to non-existing endpoint(s). "
|
|
217
|
-
"Please, check whether the path and methods are correct. (hint: path must include "
|
|
218
|
-
"all path variables and have a name that was used in the version that this "
|
|
219
|
-
"VersionChange resides in)"
|
|
220
|
-
)
|
|
221
|
-
|
|
222
219
|
for by_schema_converters in version_change.alter_request_by_schema_instructions.values():
|
|
223
220
|
for by_schema_converter in by_schema_converters:
|
|
224
221
|
if not by_schema_converter.check_usage: # pragma: no cover
|
|
@@ -249,14 +246,50 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
249
246
|
f"{version_change.__name__}.{by_schema_converter.transformer.__name__}"
|
|
250
247
|
)
|
|
251
248
|
|
|
252
|
-
def
|
|
253
|
-
self,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
249
|
+
def _attach_routes_by_path_converter(
|
|
250
|
+
self,
|
|
251
|
+
head_router: APIRouter,
|
|
252
|
+
path_to_route_methods_mapping: dict[_RoutePath, dict[_RouteMethod, set[_RouteId]]],
|
|
253
|
+
version_change: type[VersionChange],
|
|
254
|
+
by_path_converter: Union[_AlterResponseByPathInstruction, _AlterRequestByPathInstruction],
|
|
255
|
+
):
|
|
256
|
+
missing_methods = set()
|
|
257
|
+
for method in by_path_converter.methods:
|
|
258
|
+
if method in path_to_route_methods_mapping[by_path_converter.path]:
|
|
259
|
+
for route_index in path_to_route_methods_mapping[by_path_converter.path][method]:
|
|
260
|
+
route = head_router.routes[route_index]
|
|
261
|
+
if isinstance(by_path_converter, _AlterResponseByPathInstruction):
|
|
262
|
+
version_change._route_to_response_migration_mapping[id(route)].append(by_path_converter)
|
|
263
|
+
else:
|
|
264
|
+
version_change._route_to_request_migration_mapping[id(route)].append(by_path_converter)
|
|
265
|
+
else:
|
|
266
|
+
missing_methods.add(method)
|
|
267
|
+
|
|
268
|
+
if missing_methods:
|
|
269
|
+
raise RouteByPathConverterDoesNotApplyToAnythingError(
|
|
270
|
+
f"{by_path_converter.repr_name} "
|
|
271
|
+
f'"{version_change.__name__}.{by_path_converter.transformer.__name__}" '
|
|
272
|
+
f"failed to find routes with the following methods: {list(missing_methods)}. "
|
|
273
|
+
f"This means that you are trying to apply this converter to non-existing endpoint(s). "
|
|
274
|
+
"Please, check whether the path and methods are correct. (hint: path must include "
|
|
275
|
+
"all path variables and have a name that was used in the version that this "
|
|
276
|
+
"VersionChange resides in)"
|
|
277
|
+
)
|
|
258
278
|
|
|
259
|
-
|
|
279
|
+
def _extract_all_routes_identifiers_for_route_to_converter_matching(
|
|
280
|
+
self, router: APIRouter
|
|
281
|
+
) -> tuple[dict[_RoutePath, dict[_RouteMethod, set[_RouteId]]], set[Any], set[Any]]:
|
|
282
|
+
# int is the index of the route in the router.routes list.
|
|
283
|
+
# So we essentially keep track of which routes have which response models and request bodies.
|
|
284
|
+
# and their indices in the router.routes list. The indices will allow us to match them to the same
|
|
285
|
+
# routes in the head version. This gives us the ability to later apply changes to these routes
|
|
286
|
+
# without thinking about any renamings or response model changes.
|
|
287
|
+
|
|
288
|
+
response_models = set()
|
|
289
|
+
request_bodies = set()
|
|
290
|
+
path_to_route_methods_mapping: dict[str, dict[str, set[int]]] = defaultdict(lambda: defaultdict(set))
|
|
291
|
+
|
|
292
|
+
for index, route in enumerate(router.routes):
|
|
260
293
|
if isinstance(route, APIRoute):
|
|
261
294
|
if route.response_model is not None and lenient_issubclass(route.response_model, BaseModel):
|
|
262
295
|
response_models.add(route.response_model)
|
|
@@ -265,7 +298,8 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
265
298
|
annotation = route.body_field.field_info.annotation
|
|
266
299
|
if annotation is not None and lenient_issubclass(annotation, BaseModel):
|
|
267
300
|
request_bodies.add(annotation)
|
|
268
|
-
|
|
301
|
+
for method in route.methods:
|
|
302
|
+
path_to_route_methods_mapping[route.path][method].add(index)
|
|
269
303
|
|
|
270
304
|
head_response_models = {model.__cadwyn_original_model__ for model in response_models}
|
|
271
305
|
head_request_bodies = {getattr(body, "__cadwyn_original_model__", body) for body in request_bodies}
|
|
@@ -11,6 +11,7 @@ from starlette.responses import RedirectResponse
|
|
|
11
11
|
from starlette.routing import BaseRoute, Match
|
|
12
12
|
from starlette.types import Receive, Scope, Send
|
|
13
13
|
|
|
14
|
+
from cadwyn._internal.context_vars import DEFAULT_API_VERSION_VAR
|
|
14
15
|
from cadwyn._utils import same_definition_as_in
|
|
15
16
|
from cadwyn.middleware import APIVersionFormat
|
|
16
17
|
from cadwyn.structure.common import VersionType
|
|
@@ -70,8 +71,8 @@ class _RootCadwynAPIRouter(APIRouter):
|
|
|
70
71
|
if scope["type"] == "lifespan":
|
|
71
72
|
await self.lifespan(scope, receive, send)
|
|
72
73
|
return
|
|
73
|
-
|
|
74
74
|
version = self.api_version_var.get(None)
|
|
75
|
+
default_version_that_was_picked = DEFAULT_API_VERSION_VAR.get(None)
|
|
75
76
|
|
|
76
77
|
# if version is None, then it's an unversioned request and we need to use the unversioned routes
|
|
77
78
|
# if there will be a value, we search for the most suitable version
|
|
@@ -81,6 +82,14 @@ class _RootCadwynAPIRouter(APIRouter):
|
|
|
81
82
|
routes = self.versioned_routers[version].routes
|
|
82
83
|
else:
|
|
83
84
|
routes = await self._get_routes_from_closest_suitable_version(version)
|
|
85
|
+
if default_version_that_was_picked:
|
|
86
|
+
# We add unversioned routes to versioned routes because otherwise unversioned routes
|
|
87
|
+
# will be completely unavailable when a default version is passed. So routes such as
|
|
88
|
+
# /docs will not be accessible at all.
|
|
89
|
+
|
|
90
|
+
# We use this order because if versioned routes go first and there is a versioned route that is
|
|
91
|
+
# the same as an unversioned route -- the unversioned one becomes impossible to match.
|
|
92
|
+
routes = self.unversioned_routes + routes
|
|
84
93
|
await self.process_request(scope=scope, receive=receive, send=send, routes=routes)
|
|
85
94
|
|
|
86
95
|
@cached_property
|
|
@@ -196,8 +196,7 @@ def migrate_response_body(
|
|
|
196
196
|
response,
|
|
197
197
|
current_version=version,
|
|
198
198
|
head_response_model=latest_response_model,
|
|
199
|
-
|
|
200
|
-
method="GET",
|
|
199
|
+
head_route=None,
|
|
201
200
|
)
|
|
202
201
|
|
|
203
202
|
versioned_response_model: type[pydantic.BaseModel] = generate_versioned_models(versions)[str(version)][
|
|
@@ -24,8 +24,9 @@ from fastapi.routing import APIRoute, _prepare_response_content
|
|
|
24
24
|
from pydantic import BaseModel
|
|
25
25
|
from pydantic_core import PydanticUndefined
|
|
26
26
|
from starlette._utils import is_async_callable
|
|
27
|
-
from typing_extensions import Any,
|
|
27
|
+
from typing_extensions import Any, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
|
|
28
28
|
|
|
29
|
+
from cadwyn._internal.context_vars import CURRENT_DEPENDENCY_SOLVER_VAR
|
|
29
30
|
from cadwyn._utils import classproperty
|
|
30
31
|
from cadwyn.exceptions import (
|
|
31
32
|
CadwynError,
|
|
@@ -52,10 +53,7 @@ _CADWYN_REQUEST_PARAM_NAME = "cadwyn_request_param"
|
|
|
52
53
|
_CADWYN_RESPONSE_PARAM_NAME = "cadwyn_response_param"
|
|
53
54
|
_P = ParamSpec("_P")
|
|
54
55
|
_R = TypeVar("_R")
|
|
55
|
-
|
|
56
|
-
_CURRENT_DEPENDENCY_SOLVER_VAR: ContextVar[_CURRENT_DEPENDENCY_SOLVER_OPTIONS] = ContextVar(
|
|
57
|
-
"cadwyn_dependencies_dry_run"
|
|
58
|
-
)
|
|
56
|
+
_RouteId = int
|
|
59
57
|
|
|
60
58
|
PossibleInstructions: TypeAlias = Union[
|
|
61
59
|
AlterSchemaSubInstruction, AlterEndpointSubInstruction, AlterEnumSubInstruction, SchemaHadInstruction, staticmethod
|
|
@@ -88,6 +86,8 @@ class VersionChange:
|
|
|
88
86
|
alter_response_by_schema_instructions: ClassVar[dict[type, list[_AlterResponseBySchemaInstruction]]] = Sentinel
|
|
89
87
|
alter_response_by_path_instructions: ClassVar[dict[str, list[_AlterResponseByPathInstruction]]] = Sentinel
|
|
90
88
|
_bound_version_bundle: "Union[VersionBundle, None]"
|
|
89
|
+
_route_to_request_migration_mapping: ClassVar[dict[_RouteId, list[_AlterRequestByPathInstruction]]] = Sentinel
|
|
90
|
+
_route_to_response_migration_mapping: ClassVar[dict[_RouteId, list[_AlterResponseByPathInstruction]]] = Sentinel
|
|
91
91
|
|
|
92
92
|
def __init_subclass__(cls, _abstract: bool = False) -> None:
|
|
93
93
|
super().__init_subclass__()
|
|
@@ -99,6 +99,8 @@ class VersionChange:
|
|
|
99
99
|
cls._extract_body_instructions_into_correct_containers()
|
|
100
100
|
cls._check_no_subclassing()
|
|
101
101
|
cls._bound_version_bundle = None
|
|
102
|
+
cls._route_to_request_migration_mapping = defaultdict(list)
|
|
103
|
+
cls._route_to_response_migration_mapping = defaultdict(list)
|
|
102
104
|
|
|
103
105
|
@classmethod
|
|
104
106
|
def _extract_body_instructions_into_correct_containers(cls):
|
|
@@ -361,7 +363,6 @@ class VersionBundle:
|
|
|
361
363
|
self,
|
|
362
364
|
body_type: Union[type[BaseModel], None],
|
|
363
365
|
head_dependant: Dependant,
|
|
364
|
-
path: str,
|
|
365
366
|
request: FastapiRequest,
|
|
366
367
|
response: FastapiResponse,
|
|
367
368
|
request_info: RequestInfo,
|
|
@@ -372,22 +373,21 @@ class VersionBundle:
|
|
|
372
373
|
embed_body_fields: bool,
|
|
373
374
|
background_tasks: Union[BackgroundTasks, None],
|
|
374
375
|
) -> dict[str, Any]:
|
|
375
|
-
method = request.method
|
|
376
|
-
|
|
377
376
|
start = self.reversed_version_values.index(current_version)
|
|
377
|
+
head_route_id = id(head_route)
|
|
378
378
|
for v in self.reversed_versions[start + 1 :]:
|
|
379
379
|
for version_change in v.changes:
|
|
380
380
|
if body_type is not None and body_type in version_change.alter_request_by_schema_instructions:
|
|
381
381
|
for instruction in version_change.alter_request_by_schema_instructions[body_type]:
|
|
382
382
|
instruction(request_info)
|
|
383
|
-
if
|
|
384
|
-
for instruction in version_change.
|
|
385
|
-
|
|
386
|
-
|
|
383
|
+
if head_route_id in version_change._route_to_request_migration_mapping:
|
|
384
|
+
for instruction in version_change._route_to_request_migration_mapping[head_route_id]:
|
|
385
|
+
instruction(request_info)
|
|
386
|
+
|
|
387
387
|
request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
|
|
388
388
|
del request._headers
|
|
389
389
|
# This gives us the ability to tell the user whether cadwyn is running its dependencies or FastAPI
|
|
390
|
-
|
|
390
|
+
CURRENT_DEPENDENCY_SOLVER_VAR.set("cadwyn")
|
|
391
391
|
# Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
|
|
392
392
|
result = await solve_dependencies(
|
|
393
393
|
request=request,
|
|
@@ -409,10 +409,10 @@ class VersionBundle:
|
|
|
409
409
|
self,
|
|
410
410
|
response_info: ResponseInfo,
|
|
411
411
|
current_version: VersionType,
|
|
412
|
-
head_response_model: type[BaseModel],
|
|
413
|
-
|
|
414
|
-
method: str,
|
|
412
|
+
head_response_model: Union[type[BaseModel], None],
|
|
413
|
+
head_route: Union[APIRoute, None],
|
|
415
414
|
) -> ResponseInfo:
|
|
415
|
+
head_route_id = id(head_route)
|
|
416
416
|
end = self.version_values.index(current_version)
|
|
417
417
|
for v in self.versions[:end]:
|
|
418
418
|
for version_change in v.changes:
|
|
@@ -423,10 +423,8 @@ class VersionBundle:
|
|
|
423
423
|
version_change.alter_response_by_schema_instructions[head_response_model]
|
|
424
424
|
)
|
|
425
425
|
|
|
426
|
-
if
|
|
427
|
-
|
|
428
|
-
if method in instruction.methods: # pragma: no branch # Safe branch to skip
|
|
429
|
-
migrations_to_apply.append(instruction) # noqa: PERF401
|
|
426
|
+
if head_route_id in version_change._route_to_response_migration_mapping:
|
|
427
|
+
migrations_to_apply.extend(version_change._route_to_response_migration_mapping[head_route_id])
|
|
430
428
|
|
|
431
429
|
for migration in migrations_to_apply:
|
|
432
430
|
if response_info.status_code < 300 or migration.migrate_http_errors:
|
|
@@ -579,8 +577,7 @@ class VersionBundle:
|
|
|
579
577
|
response_info,
|
|
580
578
|
api_version,
|
|
581
579
|
head_route.response_model,
|
|
582
|
-
|
|
583
|
-
method,
|
|
580
|
+
head_route,
|
|
584
581
|
)
|
|
585
582
|
if isinstance(response_or_response_body, FastapiResponse):
|
|
586
583
|
# a webserver (uvicorn for instance) calculates the body at the endpoint level.
|
|
@@ -674,7 +671,6 @@ class VersionBundle:
|
|
|
674
671
|
new_kwargs = await self._migrate_request(
|
|
675
672
|
head_body_field,
|
|
676
673
|
head_dependant,
|
|
677
|
-
route.path,
|
|
678
674
|
request,
|
|
679
675
|
response,
|
|
680
676
|
request_info,
|
|
@@ -8,6 +8,7 @@ from fastapi.testclient import TestClient
|
|
|
8
8
|
from pydantic import BaseModel
|
|
9
9
|
|
|
10
10
|
from cadwyn import Cadwyn
|
|
11
|
+
from cadwyn.exceptions import CadwynStructureError
|
|
11
12
|
from cadwyn.route_generation import VersionedAPIRouter
|
|
12
13
|
from cadwyn.structure.endpoints import endpoint
|
|
13
14
|
from cadwyn.structure.schemas import schema
|
|
@@ -405,3 +406,15 @@ def test__api_version_header_name_is_deprecated_and_translates_to_api_version_pa
|
|
|
405
406
|
with pytest.warns(DeprecationWarning):
|
|
406
407
|
cadwyn = Cadwyn(api_version_header_name="x-api-version", versions=VersionBundle(Version("2022-11-16")))
|
|
407
408
|
assert cadwyn.api_version_parameter_name == "x-api-version"
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test__api_version_default_value_with_path_location__should_raise_error():
|
|
412
|
+
with pytest.raises(
|
|
413
|
+
CadwynStructureError,
|
|
414
|
+
match="You tried to pass an api_version_default_value while putting the API version in Path",
|
|
415
|
+
):
|
|
416
|
+
Cadwyn(
|
|
417
|
+
versions=VersionBundle(HeadVersion(), Version("2022-11-16")),
|
|
418
|
+
api_version_default_value="2022-11-16",
|
|
419
|
+
api_version_location="path",
|
|
420
|
+
)
|
|
@@ -20,11 +20,13 @@ from starlette.responses import FileResponse
|
|
|
20
20
|
from typing_extensions import Any, NewType, TypeAlias, TypeAliasType, get_args
|
|
21
21
|
|
|
22
22
|
from cadwyn import VersionBundle, VersionedAPIRouter
|
|
23
|
+
from cadwyn.applications import Cadwyn
|
|
23
24
|
from cadwyn.dependencies import current_dependency_solver
|
|
24
25
|
from cadwyn.exceptions import CadwynError, RouterGenerationError, RouterPathParamsModifiedError
|
|
25
26
|
from cadwyn.route_generation import generate_versioned_routers
|
|
26
27
|
from cadwyn.schema_generation import generate_versioned_models
|
|
27
28
|
from cadwyn.structure import Version, convert_request_to_next_version_for, endpoint, schema
|
|
29
|
+
from cadwyn.structure.data import RequestInfo, ResponseInfo, convert_response_to_previous_version_for
|
|
28
30
|
from cadwyn.structure.enums import enum
|
|
29
31
|
from cadwyn.structure.versions import VersionChange
|
|
30
32
|
from tests._data.unversioned_schema_dir import UnversionedSchema2
|
|
@@ -252,6 +254,50 @@ def test__endpoint_had_another_path_variable(
|
|
|
252
254
|
)
|
|
253
255
|
|
|
254
256
|
|
|
257
|
+
def test__endpoint_had__another_path_with_the_other_migration_at_the_same_time__should_require_old_name(
|
|
258
|
+
create_versioned_app: CreateVersionedApp,
|
|
259
|
+
):
|
|
260
|
+
router = VersionedAPIRouter()
|
|
261
|
+
|
|
262
|
+
@router.post("/A")
|
|
263
|
+
async def test_endpoint_post(body: list[str]):
|
|
264
|
+
return body
|
|
265
|
+
|
|
266
|
+
@router.put("/A")
|
|
267
|
+
async def test_endpoint_put(body: list[str]):
|
|
268
|
+
return body
|
|
269
|
+
|
|
270
|
+
@convert_response_to_previous_version_for("/A", ["POST"])
|
|
271
|
+
def response_migration(response: ResponseInfo):
|
|
272
|
+
response.body.append("response")
|
|
273
|
+
|
|
274
|
+
@convert_request_to_next_version_for("/A", ["POST"])
|
|
275
|
+
def request_migration(request: RequestInfo):
|
|
276
|
+
request.body.append("request")
|
|
277
|
+
|
|
278
|
+
app = Cadwyn(
|
|
279
|
+
versions=VersionBundle(
|
|
280
|
+
Version(
|
|
281
|
+
"2001-01-01",
|
|
282
|
+
version_change(
|
|
283
|
+
endpoint("/A", ["POST"]).had(path="/B"),
|
|
284
|
+
request_migration=request_migration,
|
|
285
|
+
response_migration=response_migration,
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
Version("2000-01-01"),
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
app.generate_and_include_versioned_routers(router)
|
|
292
|
+
|
|
293
|
+
with TestClient(app) as client:
|
|
294
|
+
assert client.post("/B", headers={"X-API-VERSION": "2000-01-01"}, json=[]).json() == ["request", "response"]
|
|
295
|
+
assert client.post("/A", headers={"X-API-VERSION": "2001-01-01"}, json=[]).json() == []
|
|
296
|
+
|
|
297
|
+
assert client.put("/A", headers={"X-API-VERSION": "2000-01-01"}, json=[]).json() == []
|
|
298
|
+
assert client.put("/A", headers={"X-API-VERSION": "2001-01-01"}, json=[]).json() == []
|
|
299
|
+
|
|
300
|
+
|
|
255
301
|
def test__endpoint_had_dependencies(
|
|
256
302
|
test_endpoint: Endpoint,
|
|
257
303
|
test_path: str,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from starlette.requests import Request
|
|
3
5
|
from starlette.responses import PlainTextResponse
|
|
@@ -5,7 +7,8 @@ from starlette.routing import Match, NoMatchFound
|
|
|
5
7
|
from starlette.testclient import TestClient
|
|
6
8
|
|
|
7
9
|
from cadwyn import Cadwyn
|
|
8
|
-
from cadwyn.
|
|
10
|
+
from cadwyn.route_generation import VersionedAPIRouter
|
|
11
|
+
from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
|
|
9
12
|
from tests._resources.app_for_testing_routing import mixed_hosts_app
|
|
10
13
|
|
|
11
14
|
|
|
@@ -153,3 +156,53 @@ def test__host_routing__partial_match__404():
|
|
|
153
156
|
|
|
154
157
|
response = client.get("/v1/doggies/tom")
|
|
155
158
|
assert response.status_code == 200
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def get_default_version(req: Request):
|
|
162
|
+
return "2023-04-14"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@pytest.mark.parametrize("default_version", ["2023-04-14", get_default_version])
|
|
166
|
+
def test__get_unversioned_endpoints__with_default_version(default_version: "str | Callable"):
|
|
167
|
+
app = Cadwyn(
|
|
168
|
+
versions=VersionBundle(HeadVersion(), Version("2023-04-14"), Version("2022-11-16")),
|
|
169
|
+
api_version_default_value=default_version,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
router = VersionedAPIRouter()
|
|
173
|
+
|
|
174
|
+
@app.get("/my_duplicated_route")
|
|
175
|
+
def get_my_unversioned_number():
|
|
176
|
+
return 11
|
|
177
|
+
|
|
178
|
+
@router.get("/my_duplicated_route")
|
|
179
|
+
def get_my_versioned_number():
|
|
180
|
+
return 83
|
|
181
|
+
|
|
182
|
+
@router.get("/my_single_route")
|
|
183
|
+
def get_my_versioned_number_2():
|
|
184
|
+
return 52
|
|
185
|
+
|
|
186
|
+
app.generate_and_include_versioned_routers(router)
|
|
187
|
+
|
|
188
|
+
with TestClient(app) as client:
|
|
189
|
+
resp = client.get("/docs")
|
|
190
|
+
assert resp.status_code == 200, resp.json()
|
|
191
|
+
|
|
192
|
+
resp = client.get("/docs?version=2023-04-14")
|
|
193
|
+
assert resp.status_code == 200, resp.json()
|
|
194
|
+
|
|
195
|
+
resp = client.get("/docs?version=2022-11-16")
|
|
196
|
+
assert resp.status_code == 200, resp.json()
|
|
197
|
+
|
|
198
|
+
resp = client.get("/my_duplicated_route")
|
|
199
|
+
assert resp.status_code == 200, resp.json()
|
|
200
|
+
assert resp.json() == 11
|
|
201
|
+
|
|
202
|
+
resp = client.get("/my_duplicated_route", headers={"X-API-VERSION": "2023-04-14"})
|
|
203
|
+
assert resp.status_code == 200, resp.json()
|
|
204
|
+
assert resp.json() == 83
|
|
205
|
+
|
|
206
|
+
resp = client.get("/my_single_route")
|
|
207
|
+
assert resp.status_code == 200, resp.json()
|
|
208
|
+
assert resp.json() == 52
|
|
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
|
|
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
|
|
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
|
|
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
|
{cadwyn-5.2.0 → cadwyn-5.2.2}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cadwyn-5.2.0 → cadwyn-5.2.2}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cadwyn-5.2.0/docs_src/how_to/change_openapi_schemas → cadwyn-5.2.2/docs_src/how_to}/__init__.py
RENAMED
|
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
|
|
File without changes
|
{cadwyn-5.2.0/docs_src/quickstart/setup/tests → cadwyn-5.2.2/docs_src/quickstart/setup}/__init__.py
RENAMED
|
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
|
|
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
|
{cadwyn-5.2.0/tests/_resources/render/complex → cadwyn-5.2.2/tests/_resources/render}/__init__.py
RENAMED
|
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
|
|
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
|
{cadwyn-5.2.0 → cadwyn-5.2.2}/tests/test_schema_generation/test_schema_with_future_annotations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|