cadwyn 5.1.1__tar.gz → 5.1.3__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.1 → cadwyn-5.1.3}/CHANGELOG.md +19 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/PKG-INFO +2 -2
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/__init__.py +2 -0
- cadwyn-5.1.3/cadwyn/dependencies.py +5 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/exceptions.py +1 -2
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/schema_generation.py +33 -1
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/versions.py +9 -1
- {cadwyn-5.1.1 → cadwyn-5.1.3}/pyproject.toml +7 -3
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_router_generation.py +108 -4
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_schema_generation/test_enum.py +7 -1
- {cadwyn-5.1.1 → cadwyn-5.1.3}/uv.lock +185 -168
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.gitignore +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/LICENSE +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/Makefile +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/README.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/__main__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/_asts.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/_importer.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/_render.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/_utils.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/applications.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/changelogs.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/middleware.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/py.typed +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/route_generation.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/routing.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/CNAME +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/changelogs.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/cli.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/index.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/main_app.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/methodology.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/testing.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/version_changes.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/home/CONTRIBUTING.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/index.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/img/sponsor_logos/monite.png +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/index.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/plugin.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs/theory/literature.md +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/docs_src/ruff.toml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/mkdocs.yml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/ruff.toml +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/scripts/fix_links.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/scripts/split_md.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_data/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/utils.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/conftest.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_applications.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_changelog.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_cli.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_render.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_routing.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/test_structure.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/tutorial/main.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.1.1 → cadwyn-5.1.3}/tox.ini +0 -0
|
@@ -5,6 +5,25 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.1.3]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* `__doc__` attribute is now copied from the original enum to the generated enum
|
|
13
|
+
* Python 3.12 type aliases and their typing_extensions backports are now supported (including `pydantic.JsonValue` and other `typing_extensions.TypeAliasType` instances)
|
|
14
|
+
* The bug when solve_dependencies error on the migration of a request to the latest version responds with a non-json serializable error and cadwyn showed a failed to serialize error instead of the actual error
|
|
15
|
+
* Updated minimum fastapi version to 0.112.4 because embed_body_fields was added in 0.112.4
|
|
16
|
+
|
|
17
|
+
## [5.1.2]
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
* Generators not being called when fastapi validates the initial request
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
* `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"`.
|
|
26
|
+
|
|
8
27
|
## [5.1.1]
|
|
9
28
|
|
|
10
29
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.3
|
|
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
|
|
@@ -35,7 +35,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
35
35
|
Classifier: Typing :: Typed
|
|
36
36
|
Requires-Python: >=3.9
|
|
37
37
|
Requires-Dist: backports-strenum<2,>=1.3.1; python_version < '3.11'
|
|
38
|
-
Requires-Dist: fastapi>=0.112.
|
|
38
|
+
Requires-Dist: fastapi>=0.112.4
|
|
39
39
|
Requires-Dist: issubclass>=0.1.2
|
|
40
40
|
Requires-Dist: jinja2>=3.1.2
|
|
41
41
|
Requires-Dist: pydantic>=2.0.0
|
|
@@ -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",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
3
|
from fastapi.routing import APIRoute
|
|
@@ -21,7 +20,7 @@ class CadwynHeadRequestValidationError(CadwynError):
|
|
|
21
20
|
f"We failed to migrate the request with version={self.version}. "
|
|
22
21
|
"This means that there is some error in your migrations or schema structure that makes it impossible "
|
|
23
22
|
"to migrate the request of that version to latest.\n"
|
|
24
|
-
f"body={self.body}\n\nerrors={
|
|
23
|
+
f"body={self.body}\n\nerrors={self.errors}"
|
|
25
24
|
)
|
|
26
25
|
|
|
27
26
|
|
|
@@ -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
|
|
@@ -39,6 +40,7 @@ from typing_extensions import (
|
|
|
39
40
|
NewType,
|
|
40
41
|
Self,
|
|
41
42
|
TypeAlias,
|
|
43
|
+
TypeAliasType,
|
|
42
44
|
TypeVar,
|
|
43
45
|
_AnnotatedAlias,
|
|
44
46
|
assert_never,
|
|
@@ -77,6 +79,7 @@ from cadwyn.structure.versions import _CADWYN_REQUEST_PARAM_NAME, _CADWYN_RESPON
|
|
|
77
79
|
if TYPE_CHECKING:
|
|
78
80
|
from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
|
|
79
81
|
|
|
82
|
+
|
|
80
83
|
if sys.version_info >= (3, 10):
|
|
81
84
|
from typing import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
|
|
82
85
|
else:
|
|
@@ -98,6 +101,7 @@ PYDANTIC_DECORATOR_TYPE_TO_DECORATOR_MAP = {
|
|
|
98
101
|
ModelSerializerDecoratorInfo: pydantic.model_serializer,
|
|
99
102
|
ComputedFieldInfo: pydantic.computed_field,
|
|
100
103
|
}
|
|
104
|
+
_PYDANTIC_ALL_EXPORTED_NAMES = set(pydantic.__all__)
|
|
101
105
|
|
|
102
106
|
|
|
103
107
|
VALIDATOR_CONFIG_KEY = "__validators__"
|
|
@@ -486,6 +490,7 @@ class _CallableWrapper:
|
|
|
486
490
|
self._original_callable = original_callable
|
|
487
491
|
if not is_regular_function(original_callable):
|
|
488
492
|
original_callable = original_callable.__call__
|
|
493
|
+
|
|
489
494
|
functools.update_wrapper(self, original_callable)
|
|
490
495
|
|
|
491
496
|
@property
|
|
@@ -510,6 +515,17 @@ class _AsyncCallableWrapper(_CallableWrapper):
|
|
|
510
515
|
return await self._original_callable(*args, **kwargs)
|
|
511
516
|
|
|
512
517
|
|
|
518
|
+
class _GeneratorCallableWrapper(_CallableWrapper):
|
|
519
|
+
def __call__(self, *args: Any, **kwargs: Any):
|
|
520
|
+
yield from self._original_callable(*args, **kwargs)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class _AsyncGeneratorCallableWrapper(_CallableWrapper):
|
|
524
|
+
async def __call__(self, *args: Any, **kwargs: Any):
|
|
525
|
+
async for value in self._original_callable(*args, **kwargs):
|
|
526
|
+
yield value
|
|
527
|
+
|
|
528
|
+
|
|
513
529
|
@final
|
|
514
530
|
class _AnnotationTransformer:
|
|
515
531
|
def __init__(self, generator: "SchemaGenerator") -> None:
|
|
@@ -566,6 +582,17 @@ class _AnnotationTransformer:
|
|
|
566
582
|
def _change_version_of_a_non_container_annotation(self, annotation: Any) -> Any:
|
|
567
583
|
if isinstance(annotation, (_BaseGenericAlias, types.GenericAlias)):
|
|
568
584
|
return get_origin(annotation)[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
|
|
585
|
+
elif isinstance(annotation, TypeAliasType):
|
|
586
|
+
if (
|
|
587
|
+
annotation.__module__ is not None and (annotation.__module__.startswith("pydantic."))
|
|
588
|
+
) or annotation.__name__ in _PYDANTIC_ALL_EXPORTED_NAMES:
|
|
589
|
+
return annotation
|
|
590
|
+
else:
|
|
591
|
+
return TypeAliasType( # pyright: ignore[reportGeneralTypeIssues]
|
|
592
|
+
name=annotation.__name__,
|
|
593
|
+
value=self.change_version_of_annotation(annotation.__value__),
|
|
594
|
+
type_params=self.change_version_of_annotation(annotation.__type_params__),
|
|
595
|
+
)
|
|
569
596
|
elif isinstance(annotation, fastapi.params.Security):
|
|
570
597
|
return fastapi.params.Security(
|
|
571
598
|
self.change_version_of_annotation(annotation.dependency),
|
|
@@ -690,8 +717,12 @@ class _AnnotationTransformer:
|
|
|
690
717
|
actual_call = call.__call__
|
|
691
718
|
else:
|
|
692
719
|
actual_call = call
|
|
693
|
-
if
|
|
720
|
+
if is_async_gen_callable(actual_call):
|
|
721
|
+
return _AsyncGeneratorCallableWrapper(call)
|
|
722
|
+
elif is_coroutine_callable(actual_call):
|
|
694
723
|
return _AsyncCallableWrapper(call)
|
|
724
|
+
elif is_gen_callable(actual_call):
|
|
725
|
+
return _GeneratorCallableWrapper(call)
|
|
695
726
|
else:
|
|
696
727
|
return _CallableWrapper(call)
|
|
697
728
|
|
|
@@ -1061,6 +1092,7 @@ class _EnumWrapper(Generic[_T_ENUM]):
|
|
|
1061
1092
|
initialization_namespace = self._get_initialization_namespace_for_enum(self.cls) | raw_member_map
|
|
1062
1093
|
for attr_name, attr in initialization_namespace.items():
|
|
1063
1094
|
enum_dict[attr_name] = attr
|
|
1095
|
+
enum_dict["__doc__"] = self.cls.__doc__
|
|
1064
1096
|
model_copy = cast(type[_T_ENUM], type(self.name, self.cls.__bases__, enum_dict))
|
|
1065
1097
|
model_copy.__cadwyn_original_model__ = self.cls # pyright: ignore[reportAttributeAccessIssue]
|
|
1066
1098
|
return model_copy
|
|
@@ -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.3"
|
|
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"
|
|
@@ -51,7 +51,7 @@ classifiers = [
|
|
|
51
51
|
"Topic :: Internet :: WWW/HTTP",
|
|
52
52
|
]
|
|
53
53
|
dependencies = [
|
|
54
|
-
"fastapi >=0.112.
|
|
54
|
+
"fastapi >=0.112.4",
|
|
55
55
|
"starlette >=0.30.0",
|
|
56
56
|
"pydantic >=2.0.0",
|
|
57
57
|
"jinja2 >=3.1.2",
|
|
@@ -73,7 +73,8 @@ dev-dependencies = [
|
|
|
73
73
|
"svcs ~=24.1.0",
|
|
74
74
|
"httpx >=0.26.0",
|
|
75
75
|
"pytest-fixture-classes >=1.0.3",
|
|
76
|
-
|
|
76
|
+
# pytest==8.3.5 has a bug that fails our windows python 3.10 tests
|
|
77
|
+
"pytest==8.3.4",
|
|
77
78
|
"pytest-cov >=4.0.0",
|
|
78
79
|
"dirty-equals >=0.6.0",
|
|
79
80
|
"uvicorn ~=0.23.0",
|
|
@@ -87,6 +88,7 @@ dev-dependencies = [
|
|
|
87
88
|
"mike >=2.1.2, <3",
|
|
88
89
|
"pdbpp>=0.10.3",
|
|
89
90
|
"markdown-include-variants>=0.0.4",
|
|
91
|
+
"inline-snapshot>=0.20.7",
|
|
90
92
|
]
|
|
91
93
|
|
|
92
94
|
[project.urls]
|
|
@@ -97,6 +99,8 @@ Documentation = "https://docs.cadwyn.dev"
|
|
|
97
99
|
[project.scripts]
|
|
98
100
|
cadwyn = "cadwyn.__main__:app"
|
|
99
101
|
|
|
102
|
+
[tool.inline-snapshot]
|
|
103
|
+
format-command = "ruff format --stdin-filename {filename}"
|
|
100
104
|
|
|
101
105
|
[tool.coverage.run]
|
|
102
106
|
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
|
|
16
|
+
from inline_snapshot import snapshot
|
|
17
|
+
from pydantic import BaseModel, JsonValue
|
|
17
18
|
from pytest_fixture_classes import fixture_class
|
|
18
19
|
from starlette.responses import FileResponse
|
|
19
|
-
from typing_extensions import Any, NewType, TypeAlias, get_args
|
|
20
|
+
from typing_extensions import Any, NewType, TypeAlias, TypeAliasType, 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
|
|
@@ -798,7 +800,7 @@ def test__router_generation__using_unversioned_models(
|
|
|
798
800
|
assert routes_2001[3].dependant.body_params[0].type_ is schemas["2001-01-01"][UnversionedSchema3]
|
|
799
801
|
|
|
800
802
|
|
|
801
|
-
def
|
|
803
|
+
def test__router_generation__using_newtype_and_union_typehints(
|
|
802
804
|
router: VersionedAPIRouter,
|
|
803
805
|
create_versioned_api_routes: CreateVersionedAPIRoutes,
|
|
804
806
|
):
|
|
@@ -820,7 +822,7 @@ def test__router_generation__using_weird_typehints(
|
|
|
820
822
|
assert getattr(routes_2001[1].dependant.body_params[1], TYPE_ATTR) == Union[str, int]
|
|
821
823
|
|
|
822
824
|
|
|
823
|
-
def
|
|
825
|
+
def test__router_generation__using_uploadfile_typehint(
|
|
824
826
|
router: VersionedAPIRouter,
|
|
825
827
|
create_versioned_api_routes: CreateVersionedAPIRoutes,
|
|
826
828
|
):
|
|
@@ -835,6 +837,54 @@ def test__router_generation__using_pydantic_typehints__internal_pydantic_typehin
|
|
|
835
837
|
# We are intentionally not checking anything here. Our goal is to validate that there is no exception
|
|
836
838
|
|
|
837
839
|
|
|
840
|
+
def test__router_generation__using_jsonvalue_typehint(
|
|
841
|
+
router: VersionedAPIRouter,
|
|
842
|
+
create_versioned_clients: CreateVersionedClients,
|
|
843
|
+
):
|
|
844
|
+
@router.post("/test")
|
|
845
|
+
async def test(param1: JsonValue = Body()):
|
|
846
|
+
return param1
|
|
847
|
+
|
|
848
|
+
clients = create_versioned_clients(
|
|
849
|
+
version_change(endpoint("/test", ["POST"]).had(response_model=dict[str, str])),
|
|
850
|
+
)
|
|
851
|
+
assert clients["2000-01-01"].post("/test", json={"foo": "bar"}).json() == {"foo": "bar"}
|
|
852
|
+
assert clients["2001-01-01"].post("/test", json={"foo": "bar"}).json() == {"foo": "bar"}
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def test__router_generation__using_typealias_type_typehint(
|
|
856
|
+
router: VersionedAPIRouter,
|
|
857
|
+
create_versioned_clients: CreateVersionedClients,
|
|
858
|
+
):
|
|
859
|
+
class MySchema(BaseModel):
|
|
860
|
+
foo: str
|
|
861
|
+
|
|
862
|
+
typealias = TypeAliasType("typealias", list[MySchema]) # pyright: ignore[reportGeneralTypeIssues]
|
|
863
|
+
|
|
864
|
+
@router.post("/test")
|
|
865
|
+
async def test(param1: typealias = Body()):
|
|
866
|
+
return param1
|
|
867
|
+
|
|
868
|
+
clients = create_versioned_clients(
|
|
869
|
+
version_change(schema(MySchema).field("foo").had(name="baz")),
|
|
870
|
+
)
|
|
871
|
+
assert clients["2000-01-01"].post("/test", json=[{"foo": "bar"}]).json() == snapshot(
|
|
872
|
+
{
|
|
873
|
+
"detail": [
|
|
874
|
+
{
|
|
875
|
+
"type": "missing",
|
|
876
|
+
"loc": ["body", 0, "baz"],
|
|
877
|
+
"msg": "Field required",
|
|
878
|
+
"input": {
|
|
879
|
+
"foo": "bar",
|
|
880
|
+
},
|
|
881
|
+
}
|
|
882
|
+
]
|
|
883
|
+
}
|
|
884
|
+
)
|
|
885
|
+
assert clients["2001-01-01"].post("/test", json=[{"foo": "bar"}]).json() == [{"foo": "bar"}]
|
|
886
|
+
|
|
887
|
+
|
|
838
888
|
def test__router_generation__updating_request_depends(
|
|
839
889
|
router: VersionedAPIRouter, create_versioned_app: CreateVersionedApp
|
|
840
890
|
):
|
|
@@ -1205,6 +1255,60 @@ def test__basic_router_generation__subclass_of_security_class_based_dependency_w
|
|
|
1205
1255
|
]
|
|
1206
1256
|
|
|
1207
1257
|
|
|
1258
|
+
def test__router_generation__with_generator_dependencies(
|
|
1259
|
+
router: VersionedAPIRouter,
|
|
1260
|
+
create_versioned_app: CreateVersionedApp,
|
|
1261
|
+
):
|
|
1262
|
+
dependency_cache = []
|
|
1263
|
+
|
|
1264
|
+
async def my_async_dependency(current_dependency_runner: Annotated[str, Depends(current_dependency_solver)]):
|
|
1265
|
+
dependency_cache.append(f"{current_dependency_runner} async dependency start")
|
|
1266
|
+
yield "async dependency"
|
|
1267
|
+
dependency_cache.append(f"{current_dependency_runner} async dependency end")
|
|
1268
|
+
|
|
1269
|
+
def my_sync_dependency(current_dependency_runner: Annotated[str, Depends(current_dependency_solver)]):
|
|
1270
|
+
dependency_cache.append(f"{current_dependency_runner} sync dependency start")
|
|
1271
|
+
yield "sync dependency"
|
|
1272
|
+
dependency_cache.append(f"{current_dependency_runner} sync dependency end")
|
|
1273
|
+
|
|
1274
|
+
@router.get("/test")
|
|
1275
|
+
async def test(
|
|
1276
|
+
my_async_dep: Annotated[str, Depends(my_async_dependency)],
|
|
1277
|
+
my_sync_dep: Annotated[str, Depends(my_sync_dependency)],
|
|
1278
|
+
):
|
|
1279
|
+
assert my_async_dep == "async dependency"
|
|
1280
|
+
assert my_sync_dep == "sync dependency"
|
|
1281
|
+
|
|
1282
|
+
client = TestClient(create_versioned_app(version_change()))
|
|
1283
|
+
assert client.get("/test", headers={"x-api-version": "2000-01-01"}).status_code == 200
|
|
1284
|
+
assert dependency_cache == snapshot(
|
|
1285
|
+
[
|
|
1286
|
+
"fastapi async dependency start",
|
|
1287
|
+
"fastapi sync dependency start",
|
|
1288
|
+
"cadwyn async dependency start",
|
|
1289
|
+
"cadwyn sync dependency start",
|
|
1290
|
+
"cadwyn sync dependency end",
|
|
1291
|
+
"cadwyn async dependency end",
|
|
1292
|
+
"fastapi sync dependency end",
|
|
1293
|
+
"fastapi async dependency end",
|
|
1294
|
+
]
|
|
1295
|
+
)
|
|
1296
|
+
dependency_cache.clear()
|
|
1297
|
+
assert client.get("/test", headers={"x-api-version": "2001-01-01"}).status_code == 200
|
|
1298
|
+
assert dependency_cache == snapshot(
|
|
1299
|
+
[
|
|
1300
|
+
"fastapi async dependency start",
|
|
1301
|
+
"fastapi sync dependency start",
|
|
1302
|
+
"cadwyn async dependency start",
|
|
1303
|
+
"cadwyn sync dependency start",
|
|
1304
|
+
"cadwyn sync dependency end",
|
|
1305
|
+
"cadwyn async dependency end",
|
|
1306
|
+
"fastapi sync dependency end",
|
|
1307
|
+
"fastapi async dependency end",
|
|
1308
|
+
]
|
|
1309
|
+
)
|
|
1310
|
+
|
|
1311
|
+
|
|
1208
1312
|
######################
|
|
1209
1313
|
# External lib testing
|
|
1210
1314
|
######################
|
|
@@ -11,7 +11,7 @@ from tests.conftest import CreateRuntimeSchemas, serialize_enum, version_change
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class EmptyEnum(Enum):
|
|
14
|
-
|
|
14
|
+
"""My docstring"""
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class EnumWithOneMember(Enum):
|
|
@@ -34,6 +34,12 @@ class EnumWithOneMemberAndMethods(Enum):
|
|
|
34
34
|
return cls.world_member # pyright: ignore[reportAttributeAccessIssue]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def test__enum_had__should_copy_docstring_too(create_runtime_schemas: CreateRuntimeSchemas):
|
|
38
|
+
models = create_runtime_schemas(version_change(enum(EmptyEnum).had(b=auto())))
|
|
39
|
+
assert models["2000-01-01"][EmptyEnum].__doc__ == "My docstring"
|
|
40
|
+
assert models["2001-01-01"][EmptyEnum].__doc__ == "My docstring"
|
|
41
|
+
|
|
42
|
+
|
|
37
43
|
def test__enum_had__original_enum_is_empty(create_runtime_schemas: CreateRuntimeSchemas):
|
|
38
44
|
models = create_runtime_schemas(version_change(enum(EmptyEnum).had(b=auto())))
|
|
39
45
|
assert serialize_enum(models["2000-01-01"][EmptyEnum]) == {"b": 1}
|