cadwyn 5.1.0a1__tar.gz → 5.1.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.1.0a1 → cadwyn-5.1.2}/CHANGELOG.md +16 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/PKG-INFO +1 -1
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/__init__.py +2 -0
- cadwyn-5.1.2/cadwyn/dependencies.py +5 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/schema_generation.py +32 -4
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/versions.py +9 -1
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/pyproject.toml +4 -1
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_router_generation.py +56 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_router_generation_with_from_future_annotations.py +7 -3
- cadwyn-5.1.2/tests/test_schema_generation/test_schema_with_future_annotations.py +38 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/uv.lock +18 -1
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.gitignore +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/LICENSE +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/Makefile +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/README.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/__main__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_asts.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_importer.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_render.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/_utils.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/applications.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/changelogs.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/exceptions.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/middleware.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/py.typed +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/route_generation.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/routing.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/CNAME +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/changelogs.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/cli.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/index.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/main_app.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/methodology.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/testing.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/version_changes.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/home/CONTRIBUTING.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/index.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/sponsor_logos/monite.png +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/index.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/plugin.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs/theory/literature.md +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/docs_src/ruff.toml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/mkdocs.yml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/ruff.toml +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/scripts/fix_links.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/scripts/split_md.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/utils.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/conftest.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_applications.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_changelog.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_cli.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_render.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_routing.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_structure.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/tutorial/main.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.1.0a1 → cadwyn-5.1.2}/tox.ini +0 -0
|
@@ -5,6 +5,22 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.1.2]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* Generators not being called when fastapi validates the initial request
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
* `cadwyn.current_dependency_solver` function to check whether cadwyn or fastapi is currently solving dependencies. It can be used as a dependency for versioned endpoints like so: `current_dependency_solver: Annotated[Literal["fastapi", "cadwyn"], Depends(current_dependency_solver)]`. If your dependency has side effects, you would likely want to only run it once per request. If you want to run it once we migrated the request to the latest version, you should only run it when `current_dependency_solver` is `"cadwyn"`. If you want your dependency to run at the very beginning of handling the request, you should only run it when `current_dependency_solver` is `"fastapi"`.
|
|
17
|
+
|
|
18
|
+
## [5.1.1]
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
* Support for `WithJsonSchema` in schema generation
|
|
23
|
+
|
|
8
24
|
## [5.1.0]
|
|
9
25
|
|
|
10
26
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.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
|
|
@@ -2,6 +2,7 @@ import importlib.metadata
|
|
|
2
2
|
|
|
3
3
|
from .applications import Cadwyn
|
|
4
4
|
from .changelogs import hidden
|
|
5
|
+
from .dependencies import current_dependency_solver
|
|
5
6
|
from .route_generation import VersionedAPIRouter, generate_versioned_routers
|
|
6
7
|
from .schema_generation import generate_versioned_models, migrate_response_body
|
|
7
8
|
from .structure import (
|
|
@@ -32,6 +33,7 @@ __all__ = [
|
|
|
32
33
|
"VersionedAPIRouter",
|
|
33
34
|
"convert_request_to_next_version_for",
|
|
34
35
|
"convert_response_to_previous_version_for",
|
|
36
|
+
"current_dependency_solver",
|
|
35
37
|
"endpoint",
|
|
36
38
|
"enum",
|
|
37
39
|
"generate_versioned_models",
|
|
@@ -20,6 +20,7 @@ import pydantic
|
|
|
20
20
|
import pydantic._internal._decorators
|
|
21
21
|
import typing_extensions
|
|
22
22
|
from fastapi import Response
|
|
23
|
+
from fastapi.dependencies.utils import is_async_gen_callable, is_coroutine_callable, is_gen_callable
|
|
23
24
|
from fastapi.routing import APIRoute
|
|
24
25
|
from pydantic import BaseModel, Field, RootModel
|
|
25
26
|
from pydantic._internal import _decorators
|
|
@@ -77,6 +78,7 @@ from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPON
|
|
|
77
78
|
if TYPE_CHECKING:
|
|
78
79
|
from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
|
|
79
80
|
|
|
81
|
+
|
|
80
82
|
if sys.version_info >= (3, 10):
|
|
81
83
|
from typing import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
|
|
82
84
|
else:
|
|
@@ -264,10 +266,20 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
|
|
|
264
266
|
|
|
265
267
|
wrapped_validator = _wrap_validator(decorator_wrapper.func, decorator_wrapper.shim, decorator_wrapper.info)
|
|
266
268
|
validators[decorator_wrapper.cls_var_name] = wrapped_validator
|
|
269
|
+
|
|
270
|
+
def _rebuild_annotated(name: str):
|
|
271
|
+
if field_info := model.model_fields.get(name):
|
|
272
|
+
if not field_info.metadata:
|
|
273
|
+
return field_info.annotation
|
|
274
|
+
|
|
275
|
+
if sys.version_info >= (3, 13):
|
|
276
|
+
return Annotated.__getitem__((field_info.annotation, *field_info.metadata)) # pyright: ignore[reportAttributeAccessIssue]
|
|
277
|
+
else:
|
|
278
|
+
return Annotated.__class_getitem__((field_info.annotation, *field_info.metadata)) # pyright: ignore[reportAttributeAccessIssue]
|
|
279
|
+
return model.__annotations__[name] # pragma: no cover
|
|
280
|
+
|
|
267
281
|
annotations = {
|
|
268
|
-
name: value
|
|
269
|
-
if not isinstance(value, str)
|
|
270
|
-
else model.model_fields[name].annotation or model.__annotations__[name]
|
|
282
|
+
name: value if not isinstance(value, str) else _rebuild_annotated(name)
|
|
271
283
|
for name, value in model.__annotations__.items()
|
|
272
284
|
}
|
|
273
285
|
|
|
@@ -476,6 +488,7 @@ class _CallableWrapper:
|
|
|
476
488
|
self._original_callable = original_callable
|
|
477
489
|
if not is_regular_function(original_callable):
|
|
478
490
|
original_callable = original_callable.__call__
|
|
491
|
+
|
|
479
492
|
functools.update_wrapper(self, original_callable)
|
|
480
493
|
|
|
481
494
|
@property
|
|
@@ -500,6 +513,17 @@ class _AsyncCallableWrapper(_CallableWrapper):
|
|
|
500
513
|
return await self._original_callable(*args, **kwargs)
|
|
501
514
|
|
|
502
515
|
|
|
516
|
+
class _GeneratorCallableWrapper(_CallableWrapper):
|
|
517
|
+
def __call__(self, *args: Any, **kwargs: Any):
|
|
518
|
+
yield from self._original_callable(*args, **kwargs)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class _AsyncGeneratorCallableWrapper(_CallableWrapper):
|
|
522
|
+
async def __call__(self, *args: Any, **kwargs: Any):
|
|
523
|
+
async for value in self._original_callable(*args, **kwargs):
|
|
524
|
+
yield value
|
|
525
|
+
|
|
526
|
+
|
|
503
527
|
@final
|
|
504
528
|
class _AnnotationTransformer:
|
|
505
529
|
def __init__(self, generator: "SchemaGenerator") -> None:
|
|
@@ -680,8 +704,12 @@ class _AnnotationTransformer:
|
|
|
680
704
|
actual_call = call.__call__
|
|
681
705
|
else:
|
|
682
706
|
actual_call = call
|
|
683
|
-
if
|
|
707
|
+
if is_async_gen_callable(actual_call):
|
|
708
|
+
return _AsyncGeneratorCallableWrapper(call)
|
|
709
|
+
elif is_coroutine_callable(actual_call):
|
|
684
710
|
return _AsyncCallableWrapper(call)
|
|
711
|
+
elif is_gen_callable(actual_call):
|
|
712
|
+
return _GeneratorCallableWrapper(call)
|
|
685
713
|
else:
|
|
686
714
|
return _CallableWrapper(call)
|
|
687
715
|
|
|
@@ -23,7 +23,7 @@ from fastapi.routing import APIRoute, _prepare_response_content
|
|
|
23
23
|
from pydantic import BaseModel
|
|
24
24
|
from pydantic_core import PydanticUndefined
|
|
25
25
|
from starlette._utils import is_async_callable
|
|
26
|
-
from typing_extensions import Any, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
|
|
26
|
+
from typing_extensions import Any, Literal, ParamSpec, TypeAlias, TypeVar, assert_never, deprecated, get_args
|
|
27
27
|
|
|
28
28
|
from cadwyn._utils import classproperty
|
|
29
29
|
from cadwyn.exceptions import (
|
|
@@ -51,6 +51,11 @@ _CADWYN_REQUEST_PARAM_NAME = "cadwyn_request_param"
|
|
|
51
51
|
_CADWYN_RESPONSE_PARAM_NAME = "cadwyn_response_param"
|
|
52
52
|
_P = ParamSpec("_P")
|
|
53
53
|
_R = TypeVar("_R")
|
|
54
|
+
_CURRENT_DEPENDENCY_SOLVER_OPTIONS = Literal["cadwyn", "fastapi"]
|
|
55
|
+
_CURRENT_DEPENDENCY_SOLVER_VAR: ContextVar[_CURRENT_DEPENDENCY_SOLVER_OPTIONS] = ContextVar(
|
|
56
|
+
"cadwyn_dependencies_dry_run"
|
|
57
|
+
)
|
|
58
|
+
|
|
54
59
|
PossibleInstructions: TypeAlias = Union[
|
|
55
60
|
AlterSchemaSubInstruction, AlterEndpointSubInstruction, AlterEnumSubInstruction, SchemaHadInstruction, staticmethod
|
|
56
61
|
]
|
|
@@ -276,6 +281,7 @@ class VersionBundle:
|
|
|
276
281
|
|
|
277
282
|
if api_version_var is None:
|
|
278
283
|
api_version_var = ContextVar("cadwyn_api_version")
|
|
284
|
+
|
|
279
285
|
self.version_values = tuple(version.value for version in self.versions)
|
|
280
286
|
self.reversed_version_values = tuple(reversed(self.version_values))
|
|
281
287
|
self.api_version_var = api_version_var
|
|
@@ -379,6 +385,8 @@ class VersionBundle:
|
|
|
379
385
|
instruction(request_info)
|
|
380
386
|
request.scope["headers"] = tuple((key.encode(), value.encode()) for key, value in request_info.headers.items())
|
|
381
387
|
del request._headers
|
|
388
|
+
# This gives us the ability to tell the user whether cadwyn is running its dependencies or FastAPI
|
|
389
|
+
_CURRENT_DEPENDENCY_SOLVER_VAR.set("cadwyn")
|
|
382
390
|
# Remember this: if len(body_params) == 1, then route.body_schema == route.dependant.body_params[0]
|
|
383
391
|
result = await solve_dependencies(
|
|
384
392
|
request=request,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cadwyn"
|
|
3
|
-
version = "5.1.
|
|
3
|
+
version = "5.1.2"
|
|
4
4
|
description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
|
|
5
5
|
authors = [{ name = "Stanislav Zmiev", email = "zmievsa@gmail.com" }]
|
|
6
6
|
license = "MIT"
|
|
@@ -87,6 +87,7 @@ dev-dependencies = [
|
|
|
87
87
|
"mike >=2.1.2, <3",
|
|
88
88
|
"pdbpp>=0.10.3",
|
|
89
89
|
"markdown-include-variants>=0.0.4",
|
|
90
|
+
"inline-snapshot>=0.20.7",
|
|
90
91
|
]
|
|
91
92
|
|
|
92
93
|
[project.urls]
|
|
@@ -97,6 +98,8 @@ Documentation = "https://docs.cadwyn.dev"
|
|
|
97
98
|
[project.scripts]
|
|
98
99
|
cadwyn = "cadwyn.__main__:app"
|
|
99
100
|
|
|
101
|
+
[tool.inline-snapshot]
|
|
102
|
+
format-command = "ruff format --stdin-filename {filename}"
|
|
100
103
|
|
|
101
104
|
[tool.coverage.run]
|
|
102
105
|
parallel = true
|
|
@@ -13,12 +13,14 @@ from fastapi.routing import APIRoute
|
|
|
13
13
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
14
14
|
from fastapi.security.http import HTTPBasic
|
|
15
15
|
from fastapi.testclient import TestClient
|
|
16
|
+
from inline_snapshot import snapshot
|
|
16
17
|
from pydantic import BaseModel
|
|
17
18
|
from pytest_fixture_classes import fixture_class
|
|
18
19
|
from starlette.responses import FileResponse
|
|
19
20
|
from typing_extensions import Any, NewType, TypeAlias, get_args
|
|
20
21
|
|
|
21
22
|
from cadwyn import VersionBundle, VersionedAPIRouter
|
|
23
|
+
from cadwyn.dependencies import current_dependency_solver
|
|
22
24
|
from cadwyn.exceptions import CadwynError, RouterGenerationError, RouterPathParamsModifiedError
|
|
23
25
|
from cadwyn.route_generation import generate_versioned_routers
|
|
24
26
|
from cadwyn.schema_generation import generate_versioned_models
|
|
@@ -1205,6 +1207,60 @@ def test__basic_router_generation__subclass_of_security_class_based_dependency_w
|
|
|
1205
1207
|
]
|
|
1206
1208
|
|
|
1207
1209
|
|
|
1210
|
+
def test__router_generation__with_generator_dependencies(
|
|
1211
|
+
router: VersionedAPIRouter,
|
|
1212
|
+
create_versioned_app: CreateVersionedApp,
|
|
1213
|
+
):
|
|
1214
|
+
dependency_cache = []
|
|
1215
|
+
|
|
1216
|
+
async def my_async_dependency(current_dependency_runner: Annotated[str, Depends(current_dependency_solver)]):
|
|
1217
|
+
dependency_cache.append(f"{current_dependency_runner} async dependency start")
|
|
1218
|
+
yield "async dependency"
|
|
1219
|
+
dependency_cache.append(f"{current_dependency_runner} async dependency end")
|
|
1220
|
+
|
|
1221
|
+
def my_sync_dependency(current_dependency_runner: Annotated[str, Depends(current_dependency_solver)]):
|
|
1222
|
+
dependency_cache.append(f"{current_dependency_runner} sync dependency start")
|
|
1223
|
+
yield "sync dependency"
|
|
1224
|
+
dependency_cache.append(f"{current_dependency_runner} sync dependency end")
|
|
1225
|
+
|
|
1226
|
+
@router.get("/test")
|
|
1227
|
+
async def test(
|
|
1228
|
+
my_async_dep: Annotated[str, Depends(my_async_dependency)],
|
|
1229
|
+
my_sync_dep: Annotated[str, Depends(my_sync_dependency)],
|
|
1230
|
+
):
|
|
1231
|
+
assert my_async_dep == "async dependency"
|
|
1232
|
+
assert my_sync_dep == "sync dependency"
|
|
1233
|
+
|
|
1234
|
+
client = TestClient(create_versioned_app(version_change()))
|
|
1235
|
+
assert client.get("/test", headers={"x-api-version": "2000-01-01"}).status_code == 200
|
|
1236
|
+
assert dependency_cache == snapshot(
|
|
1237
|
+
[
|
|
1238
|
+
"fastapi async dependency start",
|
|
1239
|
+
"fastapi sync dependency start",
|
|
1240
|
+
"cadwyn async dependency start",
|
|
1241
|
+
"cadwyn sync dependency start",
|
|
1242
|
+
"cadwyn sync dependency end",
|
|
1243
|
+
"cadwyn async dependency end",
|
|
1244
|
+
"fastapi sync dependency end",
|
|
1245
|
+
"fastapi async dependency end",
|
|
1246
|
+
]
|
|
1247
|
+
)
|
|
1248
|
+
dependency_cache.clear()
|
|
1249
|
+
assert client.get("/test", headers={"x-api-version": "2001-01-01"}).status_code == 200
|
|
1250
|
+
assert dependency_cache == snapshot(
|
|
1251
|
+
[
|
|
1252
|
+
"fastapi async dependency start",
|
|
1253
|
+
"fastapi sync dependency start",
|
|
1254
|
+
"cadwyn async dependency start",
|
|
1255
|
+
"cadwyn sync dependency start",
|
|
1256
|
+
"cadwyn sync dependency end",
|
|
1257
|
+
"cadwyn async dependency end",
|
|
1258
|
+
"fastapi sync dependency end",
|
|
1259
|
+
"fastapi async dependency end",
|
|
1260
|
+
]
|
|
1261
|
+
)
|
|
1262
|
+
|
|
1263
|
+
|
|
1208
1264
|
######################
|
|
1209
1265
|
# External lib testing
|
|
1210
1266
|
######################
|
{cadwyn-5.1.0a1 → cadwyn-5.1.2}/tests/test_router_generation_with_from_future_annotations.py
RENAMED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
3
5
|
from fastapi.testclient import TestClient
|
|
4
|
-
from pydantic import BaseModel, Field
|
|
6
|
+
from pydantic import BaseModel, Field, WithJsonSchema
|
|
5
7
|
|
|
6
8
|
from cadwyn.applications import Cadwyn
|
|
7
9
|
from cadwyn.route_generation import VersionedAPIRouter
|
|
@@ -12,6 +14,8 @@ from cadwyn.structure.versions import Version, VersionBundle, VersionChange
|
|
|
12
14
|
class OuterSchema(BaseModel):
|
|
13
15
|
bar: MySchema
|
|
14
16
|
|
|
17
|
+
extra_annotated: Annotated[str, WithJsonSchema({"type": "string", "description": "Hello"})] = ""
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
class MySchema(BaseModel):
|
|
17
21
|
foo: str = Field(coerce_numbers_to_str=True)
|
|
@@ -56,7 +60,7 @@ def test__router_generation__using_forwardref_outer_global_schema_in_body():
|
|
|
56
60
|
unversioned_client = TestClient(app)
|
|
57
61
|
client_2000 = TestClient(app, headers={app.router.api_version_parameter_name: "2000-01-01"})
|
|
58
62
|
client_2001 = TestClient(app, headers={app.router.api_version_parameter_name: "2001-01-01"})
|
|
59
|
-
assert client_2000.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": 1}}
|
|
60
|
-
assert client_2001.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": "1"}}
|
|
63
|
+
assert client_2000.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": 1}, "extra_annotated": ""}
|
|
64
|
+
assert client_2001.post("/test2", json={"bar": {"foo": 1}}).json() == {"bar": {"foo": "1"}, "extra_annotated": ""}
|
|
61
65
|
assert unversioned_client.get("/openapi.json?version=2000-01-01").status_code == 200
|
|
62
66
|
assert unversioned_client.get("/openapi.json?version=2001-01-01").status_code == 200
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from pydantic import BaseModel, WithJsonSchema
|
|
8
|
+
from typing_extensions import TypeAlias
|
|
9
|
+
|
|
10
|
+
from cadwyn.exceptions import InvalidGenerationInstructionError
|
|
11
|
+
from cadwyn.structure.schemas import schema
|
|
12
|
+
from tests.conftest import CreateRuntimeSchemas, assert_models_are_equal, version_change
|
|
13
|
+
|
|
14
|
+
Foo: TypeAlias = int
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MySchema(BaseModel):
|
|
18
|
+
foo: Foo
|
|
19
|
+
bar: Annotated[str, WithJsonSchema({"type": "string", "description": "Hi"})]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test__schema_had_name(create_runtime_schemas: CreateRuntimeSchemas):
|
|
23
|
+
schemas = create_runtime_schemas(version_change(schema(MySchema).had(name="Aww")))
|
|
24
|
+
|
|
25
|
+
assert_models_are_equal(schemas["2000-01-01"][MySchema], MySchema)
|
|
26
|
+
assert schemas["2000-01-01"][MySchema].__name__ == "Aww"
|
|
27
|
+
assert schemas["2000-01-01"][MySchema].__qualname__ == "Aww"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test__schema_had_name__with_the_same_name__should_raise_error(create_runtime_schemas: CreateRuntimeSchemas):
|
|
31
|
+
with pytest.raises(
|
|
32
|
+
InvalidGenerationInstructionError,
|
|
33
|
+
match=re.escape(
|
|
34
|
+
'You tried to change the name of "MySchema" in "MyVersionChange" '
|
|
35
|
+
"but it already has the name you tried to assign."
|
|
36
|
+
),
|
|
37
|
+
):
|
|
38
|
+
create_runtime_schemas(version_change(schema(MySchema).had(name="MySchema")))
|
|
@@ -94,7 +94,7 @@ wheels = [
|
|
|
94
94
|
|
|
95
95
|
[[package]]
|
|
96
96
|
name = "cadwyn"
|
|
97
|
-
version = "5.1.
|
|
97
|
+
version = "5.1.2"
|
|
98
98
|
source = { editable = "." }
|
|
99
99
|
dependencies = [
|
|
100
100
|
{ name = "backports-strenum", marker = "python_full_version < '3.11'" },
|
|
@@ -117,6 +117,7 @@ dev = [
|
|
|
117
117
|
{ name = "better-devtools" },
|
|
118
118
|
{ name = "dirty-equals" },
|
|
119
119
|
{ name = "httpx" },
|
|
120
|
+
{ name = "inline-snapshot" },
|
|
120
121
|
{ name = "markdown-include-variants" },
|
|
121
122
|
{ name = "mdx-include" },
|
|
122
123
|
{ name = "mike" },
|
|
@@ -153,6 +154,7 @@ dev = [
|
|
|
153
154
|
{ name = "better-devtools", specifier = "~=0.13.3" },
|
|
154
155
|
{ name = "dirty-equals", specifier = ">=0.6.0" },
|
|
155
156
|
{ name = "httpx", specifier = ">=0.26.0" },
|
|
157
|
+
{ name = "inline-snapshot", specifier = ">=0.20.7" },
|
|
156
158
|
{ name = "markdown-include-variants", specifier = ">=0.0.4" },
|
|
157
159
|
{ name = "mdx-include", specifier = "~=1.4.2" },
|
|
158
160
|
{ name = "mike", specifier = ">=2.1.2,<3" },
|
|
@@ -597,6 +599,21 @@ wheels = [
|
|
|
597
599
|
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
|
|
598
600
|
]
|
|
599
601
|
|
|
602
|
+
[[package]]
|
|
603
|
+
name = "inline-snapshot"
|
|
604
|
+
version = "0.20.7"
|
|
605
|
+
source = { registry = "https://pypi.org/simple" }
|
|
606
|
+
dependencies = [
|
|
607
|
+
{ name = "asttokens" },
|
|
608
|
+
{ name = "executing" },
|
|
609
|
+
{ name = "rich" },
|
|
610
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
611
|
+
]
|
|
612
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/41/9bd2ecd10ef789e8aff6fb68dcc7677dc31b33b2d27c306c0d40fc982fbc/inline_snapshot-0.20.7.tar.gz", hash = "sha256:d55bbb6254d0727dc304729ca7998cde1c1e984c4bf50281514aa9d727a56cf2", size = 92643 }
|
|
613
|
+
wheels = [
|
|
614
|
+
{ url = "https://files.pythonhosted.org/packages/01/8f/1bf23da63ad1a0b14ca2d9114700123ef76732e375548f4f9ca94052817e/inline_snapshot-0.20.7-py3-none-any.whl", hash = "sha256:2df6dd8710d1f0def2c1f9d6c25fd03d7beba01f3addf52fc370343d9ee9959f", size = 48108 },
|
|
615
|
+
]
|
|
616
|
+
|
|
600
617
|
[[package]]
|
|
601
618
|
name = "issubclass"
|
|
602
619
|
version = "0.1.2"
|
|
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.1.0a1 → cadwyn-5.1.2}/docs/concepts/api_version_parameter_and_context_variables.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
|
{cadwyn-5.1.0a1 → cadwyn-5.1.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.1.0a1 → cadwyn-5.1.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cadwyn-5.1.0a1 → cadwyn-5.1.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
|
|
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
|
|
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
|