cadwyn 5.1.2__tar.gz → 5.1.4__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.2 → cadwyn-5.1.4}/CHANGELOG.md +15 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/PKG-INFO +2 -2
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/exceptions.py +1 -2
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/schema_generation.py +14 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/versions.py +8 -6
- {cadwyn-5.1.2 → cadwyn-5.1.4}/pyproject.toml +4 -3
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_data_migrations.py +41 -1
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_router_generation.py +52 -4
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_enum.py +7 -1
- {cadwyn-5.1.2 → cadwyn-5.1.4}/uv.lock +171 -171
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.gitignore +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/LICENSE +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/Makefile +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/README.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/__main__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_asts.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_importer.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_render.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/_utils.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/applications.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/changelogs.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/dependencies.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/middleware.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/py.typed +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/route_generation.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/routing.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/CNAME +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/api_version_parameter_and_context_variables.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/changelogs.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/cli.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/index.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/main_app.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/methodology.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/testing.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/version_changes.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/concepts/where_to_put_the_version_and_how_to_format_it.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/home/CONTRIBUTING.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/index.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/sponsor_logos/monite.png +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/index.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/plugin.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs/theory/literature.md +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/docs_src/ruff.toml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/mkdocs.yml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/ruff.toml +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/scripts/fix_links.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/scripts/split_md.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/utils.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/conftest.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_applications.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_changelog.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_cli.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_render.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_routing.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/test_structure.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/tutorial/main.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.1.2 → cadwyn-5.1.4}/tox.ini +0 -0
|
@@ -5,6 +5,21 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.1.4]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* a bug where a custom HTTP exception was not being copied correctly so its unique properties are ignored
|
|
13
|
+
|
|
14
|
+
## [5.1.3]
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* `__doc__` attribute is now copied from the original enum to the generated enum
|
|
19
|
+
* Python 3.12 type aliases and their typing_extensions backports are now supported (including `pydantic.JsonValue` and other `typing_extensions.TypeAliasType` instances)
|
|
20
|
+
* 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
|
|
21
|
+
* Updated minimum fastapi version to 0.112.4 because embed_body_fields was added in 0.112.4
|
|
22
|
+
|
|
8
23
|
## [5.1.2]
|
|
9
24
|
|
|
10
25
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.1.
|
|
3
|
+
Version: 5.1.4
|
|
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
|
|
@@ -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
|
|
|
@@ -40,6 +40,7 @@ from typing_extensions import (
|
|
|
40
40
|
NewType,
|
|
41
41
|
Self,
|
|
42
42
|
TypeAlias,
|
|
43
|
+
TypeAliasType,
|
|
43
44
|
TypeVar,
|
|
44
45
|
_AnnotatedAlias,
|
|
45
46
|
assert_never,
|
|
@@ -100,6 +101,7 @@ PYDANTIC_DECORATOR_TYPE_TO_DECORATOR_MAP = {
|
|
|
100
101
|
ModelSerializerDecoratorInfo: pydantic.model_serializer,
|
|
101
102
|
ComputedFieldInfo: pydantic.computed_field,
|
|
102
103
|
}
|
|
104
|
+
_PYDANTIC_ALL_EXPORTED_NAMES = set(pydantic.__all__)
|
|
103
105
|
|
|
104
106
|
|
|
105
107
|
VALIDATOR_CONFIG_KEY = "__validators__"
|
|
@@ -580,6 +582,17 @@ class _AnnotationTransformer:
|
|
|
580
582
|
def _change_version_of_a_non_container_annotation(self, annotation: Any) -> Any:
|
|
581
583
|
if isinstance(annotation, (_BaseGenericAlias, types.GenericAlias)):
|
|
582
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
|
+
)
|
|
583
596
|
elif isinstance(annotation, fastapi.params.Security):
|
|
584
597
|
return fastapi.params.Security(
|
|
585
598
|
self.change_version_of_annotation(annotation.dependency),
|
|
@@ -1079,6 +1092,7 @@ class _EnumWrapper(Generic[_T_ENUM]):
|
|
|
1079
1092
|
initialization_namespace = self._get_initialization_namespace_for_enum(self.cls) | raw_member_map
|
|
1080
1093
|
for attr_name, attr in initialization_namespace.items():
|
|
1081
1094
|
enum_dict[attr_name] = attr
|
|
1095
|
+
enum_dict["__doc__"] = self.cls.__doc__
|
|
1082
1096
|
model_copy = cast(type[_T_ENUM], type(self.name, self.cls.__bases__, enum_dict))
|
|
1083
1097
|
model_copy.__cadwyn_original_model__ = self.cls # pyright: ignore[reportAttributeAccessIssue]
|
|
1084
1098
|
return model_copy
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import email.message
|
|
2
2
|
import functools
|
|
3
|
+
import http
|
|
3
4
|
import inspect
|
|
4
5
|
import json
|
|
5
6
|
from collections import defaultdict
|
|
@@ -8,7 +9,7 @@ from contextlib import AsyncExitStack
|
|
|
8
9
|
from contextvars import ContextVar
|
|
9
10
|
from datetime import date
|
|
10
11
|
from enum import Enum
|
|
11
|
-
from typing import TYPE_CHECKING, ClassVar, Union
|
|
12
|
+
from typing import TYPE_CHECKING, ClassVar, Union, cast
|
|
12
13
|
|
|
13
14
|
from fastapi import BackgroundTasks, HTTPException, params
|
|
14
15
|
from fastapi import Request as FastapiRequest
|
|
@@ -616,12 +617,13 @@ class VersionBundle:
|
|
|
616
617
|
detail = response_info.body["detail"]
|
|
617
618
|
else:
|
|
618
619
|
detail = response_info.body
|
|
620
|
+
if detail is None:
|
|
621
|
+
detail = http.HTTPStatus(response_info.status_code).phrase
|
|
622
|
+
raised_exception.detail = cast(str, detail)
|
|
623
|
+
raised_exception.headers = dict(response_info.headers)
|
|
624
|
+
raised_exception.status_code = response_info.status_code
|
|
619
625
|
|
|
620
|
-
raise
|
|
621
|
-
status_code=response_info.status_code,
|
|
622
|
-
detail=detail,
|
|
623
|
-
headers=dict(response_info.headers),
|
|
624
|
-
)
|
|
626
|
+
raise raised_exception
|
|
625
627
|
return response_info._response
|
|
626
628
|
return response_info.body
|
|
627
629
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cadwyn"
|
|
3
|
-
version = "5.1.
|
|
3
|
+
version = "5.1.4"
|
|
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",
|
|
@@ -3,7 +3,7 @@ import re
|
|
|
3
3
|
from collections.abc import Callable, Coroutine
|
|
4
4
|
from contextvars import ContextVar
|
|
5
5
|
from io import StringIO
|
|
6
|
-
from typing import Any, Literal, Union
|
|
6
|
+
from typing import Any, Literal, Optional, Union
|
|
7
7
|
|
|
8
8
|
import fastapi
|
|
9
9
|
import pytest
|
|
@@ -11,6 +11,7 @@ from dirty_equals import IsPartialDict, IsStr
|
|
|
11
11
|
from fastapi import APIRouter, Body, Cookie, File, Header, HTTPException, Query, Request, Response, UploadFile
|
|
12
12
|
from fastapi.responses import JSONResponse
|
|
13
13
|
from fastapi.routing import APIRoute
|
|
14
|
+
from fastapi.testclient import TestClient
|
|
14
15
|
from pydantic import BaseModel, Field, RootModel
|
|
15
16
|
from starlette.responses import StreamingResponse
|
|
16
17
|
|
|
@@ -32,6 +33,7 @@ from cadwyn.structure.data import RequestInfo, ResponseInfo
|
|
|
32
33
|
from cadwyn.structure.schemas import schema
|
|
33
34
|
from cadwyn.structure.versions import Version, VersionBundle
|
|
34
35
|
from tests.conftest import (
|
|
36
|
+
CreateVersionedApp,
|
|
35
37
|
CreateVersionedClients,
|
|
36
38
|
client,
|
|
37
39
|
version_change,
|
|
@@ -1226,3 +1228,41 @@ def test__request_and_response_migrations__with_multiple_schemas_in_converters(
|
|
|
1226
1228
|
resp_2001 = client_2001.post(f"/{endpoint}", json={"i": ["original_request"]})
|
|
1227
1229
|
assert resp_2001.status_code == 200
|
|
1228
1230
|
assert resp_2001.json() == {"i": ["original_request", endpoint]}
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
def test__response_migrations__with_custom_http_exception(
|
|
1234
|
+
create_versioned_app: CreateVersionedApp,
|
|
1235
|
+
router: VersionedAPIRouter,
|
|
1236
|
+
) -> None:
|
|
1237
|
+
class CustomHTTPException(HTTPException):
|
|
1238
|
+
error_code: Optional[str] = None
|
|
1239
|
+
|
|
1240
|
+
def __init__(self, detail: str, error_code: Optional[str] = None):
|
|
1241
|
+
self.error_code = error_code
|
|
1242
|
+
super().__init__(status_code=400, detail=detail)
|
|
1243
|
+
|
|
1244
|
+
def http_exception_handler(request, exc):
|
|
1245
|
+
# Check if the exception has an error_code attribute
|
|
1246
|
+
error_code = exc.error_code if hasattr(exc, "error_code") else "generic_error"
|
|
1247
|
+
|
|
1248
|
+
return JSONResponse(
|
|
1249
|
+
status_code=exc.status_code,
|
|
1250
|
+
content={"code": error_code, "message": exc.detail},
|
|
1251
|
+
)
|
|
1252
|
+
|
|
1253
|
+
# Register exception handler for Cadwyn
|
|
1254
|
+
|
|
1255
|
+
@router.post("/test")
|
|
1256
|
+
async def endpoint():
|
|
1257
|
+
raise CustomHTTPException("Cadwyn error occurred", error_code="cadwyn_error")
|
|
1258
|
+
|
|
1259
|
+
app = create_versioned_app(version_change())
|
|
1260
|
+
app.add_exception_handler(HTTPException, http_exception_handler)
|
|
1261
|
+
with TestClient(app) as client:
|
|
1262
|
+
resp = client.post("/test", headers={"X-API-VERSION": "2000-01-01"})
|
|
1263
|
+
assert resp.status_code == 400
|
|
1264
|
+
assert resp.json() == {"code": "cadwyn_error", "message": "Cadwyn error occurred"}
|
|
1265
|
+
|
|
1266
|
+
resp_2001 = client.post("/test", headers={"X-API-VERSION": "2001-01-01"})
|
|
1267
|
+
assert resp_2001.status_code == 400
|
|
1268
|
+
assert resp_2001.json() == {"code": "cadwyn_error", "message": "Cadwyn error occurred"}
|
|
@@ -14,10 +14,10 @@ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
|
14
14
|
from fastapi.security.http import HTTPBasic
|
|
15
15
|
from fastapi.testclient import TestClient
|
|
16
16
|
from inline_snapshot import snapshot
|
|
17
|
-
from pydantic import BaseModel
|
|
17
|
+
from pydantic import BaseModel, JsonValue
|
|
18
18
|
from pytest_fixture_classes import fixture_class
|
|
19
19
|
from starlette.responses import FileResponse
|
|
20
|
-
from typing_extensions import Any, NewType, TypeAlias, get_args
|
|
20
|
+
from typing_extensions import Any, NewType, TypeAlias, TypeAliasType, get_args
|
|
21
21
|
|
|
22
22
|
from cadwyn import VersionBundle, VersionedAPIRouter
|
|
23
23
|
from cadwyn.dependencies import current_dependency_solver
|
|
@@ -800,7 +800,7 @@ def test__router_generation__using_unversioned_models(
|
|
|
800
800
|
assert routes_2001[3].dependant.body_params[0].type_ is schemas["2001-01-01"][UnversionedSchema3]
|
|
801
801
|
|
|
802
802
|
|
|
803
|
-
def
|
|
803
|
+
def test__router_generation__using_newtype_and_union_typehints(
|
|
804
804
|
router: VersionedAPIRouter,
|
|
805
805
|
create_versioned_api_routes: CreateVersionedAPIRoutes,
|
|
806
806
|
):
|
|
@@ -822,7 +822,7 @@ def test__router_generation__using_weird_typehints(
|
|
|
822
822
|
assert getattr(routes_2001[1].dependant.body_params[1], TYPE_ATTR) == Union[str, int]
|
|
823
823
|
|
|
824
824
|
|
|
825
|
-
def
|
|
825
|
+
def test__router_generation__using_uploadfile_typehint(
|
|
826
826
|
router: VersionedAPIRouter,
|
|
827
827
|
create_versioned_api_routes: CreateVersionedAPIRoutes,
|
|
828
828
|
):
|
|
@@ -837,6 +837,54 @@ def test__router_generation__using_pydantic_typehints__internal_pydantic_typehin
|
|
|
837
837
|
# We are intentionally not checking anything here. Our goal is to validate that there is no exception
|
|
838
838
|
|
|
839
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
|
+
|
|
840
888
|
def test__router_generation__updating_request_depends(
|
|
841
889
|
router: VersionedAPIRouter, create_versioned_app: CreateVersionedApp
|
|
842
890
|
):
|
|
@@ -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}
|