cadwyn 5.4.5__tar.gz → 5.6.0__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.4.5 → cadwyn-5.6.0}/CHANGELOG.md +22 -3
- {cadwyn-5.4.5 → cadwyn-5.6.0}/PKG-INFO +3 -3
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/changelogs.py +19 -17
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/schemas.py +4 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/version_changes.md +13 -13
- cadwyn-5.6.0/docs/home/CONTRIBUTING.md +71 -0
- cadwyn-5.6.0/docs/how_to/change_business_logic/index.md +28 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/pyproject.toml +3 -3
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_render.py +0 -2
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_schema_generation/test_schema_field.py +55 -11
- {cadwyn-5.4.5 → cadwyn-5.6.0}/uv.lock +3 -3
- cadwyn-5.4.5/docs/home/CONTRIBUTING.md +0 -72
- cadwyn-5.4.5/docs/how_to/change_business_logic/index.md +0 -28
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.gitignore +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/LICENSE +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/Makefile +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/README.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/__main__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/_asts.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/_importer.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/_internal/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/_internal/context_vars.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/_render.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/_utils.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/applications.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/dependencies.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/exceptions.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/middleware.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/py.typed +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/route_generation.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/routing.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/schema_generation.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/cadwyn/structure/versions.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/CNAME +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/api_version_parameter.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/changelogs.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/cli.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/index.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/main_app.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/methodology.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/concepts/testing.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/index.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/logos/cadwyn_icon_transparent.svg +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/logos/cadwyn_transparent.svg +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/logos/cadwyn_with_background.svg +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/index.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/plugin.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/stylesheets/extra.css +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs/theory/literature.md +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/docs_src/ruff.toml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/mkdocs.yml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/ruff.toml +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/scripts/fix_links.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/scripts/split_md.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_data/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/utils.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/conftest.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_applications.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_changelog.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_cli.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_router_generation.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_routing.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/test_structure.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/tutorial/main.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.4.5 → cadwyn-5.6.0}/tox.ini +0 -0
|
@@ -5,6 +5,26 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.6.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* `schema(...).had(json_schema_extra=...)` support by @csemanish12
|
|
13
|
+
|
|
14
|
+
## [5.5.0]
|
|
15
|
+
|
|
16
|
+
* Fix the rest of the issues from fastapi==0.119.0
|
|
17
|
+
|
|
18
|
+
## [5.4.5]
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
* Many issues after GenerateJsonSchema getting removed in fastapi==0.119.0
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
* A lot of documentation improvements by @Shigerman
|
|
27
|
+
|
|
8
28
|
## [5.4.4]
|
|
9
29
|
|
|
10
30
|
### Fixed
|
|
@@ -33,9 +53,8 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
33
53
|
|
|
34
54
|
### Added
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
56
|
+
* `typing_inspection` dependency from pydantic team for complex isinstance checks that must be the same between `typing` and `typing_extensions`
|
|
57
|
+
* Support for `pydantic>=2.12.0` FieldInfo refactoring from <https://github.com/pydantic/pydantic/pull/11898>
|
|
39
58
|
|
|
40
59
|
## [5.3.3]
|
|
41
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.6.0
|
|
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,14 +35,14 @@ 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.
|
|
38
|
+
Requires-Dist: fastapi>=0.119.0
|
|
39
39
|
Requires-Dist: jinja2>=3.1.2
|
|
40
40
|
Requires-Dist: pydantic>=2.11.0
|
|
41
41
|
Requires-Dist: starlette>=0.30.0
|
|
42
42
|
Requires-Dist: typing-extensions>=4.8.0
|
|
43
43
|
Requires-Dist: typing-inspection>=0.4.0
|
|
44
44
|
Provides-Extra: standard
|
|
45
|
-
Requires-Dist: fastapi[standard]>=0.
|
|
45
|
+
Requires-Dist: fastapi[standard]>=0.119.0; extra == 'standard'
|
|
46
46
|
Requires-Dist: typer>=0.7.0; extra == 'standard'
|
|
47
47
|
Description-Content-Type: text/markdown
|
|
48
48
|
|
|
@@ -2,9 +2,13 @@ import copy
|
|
|
2
2
|
import sys
|
|
3
3
|
from enum import auto
|
|
4
4
|
from logging import getLogger
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Any, Literal, TypeVar, Union, cast, get_args
|
|
6
6
|
|
|
7
|
-
from fastapi.
|
|
7
|
+
from fastapi._compat import (
|
|
8
|
+
get_compat_model_name_map,
|
|
9
|
+
get_definitions,
|
|
10
|
+
)
|
|
11
|
+
from fastapi._compat.v2 import ModelField
|
|
8
12
|
from fastapi.openapi.utils import (
|
|
9
13
|
get_fields_from_routes,
|
|
10
14
|
get_openapi,
|
|
@@ -35,9 +39,6 @@ from .structure.schemas import (
|
|
|
35
39
|
ValidatorExistedInstruction,
|
|
36
40
|
)
|
|
37
41
|
|
|
38
|
-
if TYPE_CHECKING:
|
|
39
|
-
from fastapi._compat import ModelField
|
|
40
|
-
|
|
41
42
|
if sys.version_info >= (3, 11): # pragma: no cover
|
|
42
43
|
from enum import StrEnum
|
|
43
44
|
else: # pragma: no cover
|
|
@@ -89,7 +90,7 @@ def _generate_changelog(versions: VersionBundle, router: _RootCadwynAPIRouter) -
|
|
|
89
90
|
version_change,
|
|
90
91
|
generator_from_newer_version,
|
|
91
92
|
generator_from_older_version,
|
|
92
|
-
schemas_from_older_version,
|
|
93
|
+
schemas_from_older_version, # pyright: ignore[reportArgumentType]
|
|
93
94
|
cast("list[APIRoute]", routes_from_newer_version),
|
|
94
95
|
)
|
|
95
96
|
if changelog_entry is not None: # pragma: no branch # This should never happen
|
|
@@ -153,21 +154,22 @@ def _get_all_pydantic_models_from_generic(annotation: Any) -> list[type[BaseMode
|
|
|
153
154
|
|
|
154
155
|
|
|
155
156
|
def _get_openapi_representation_of_a_field(model: type[BaseModel], field_name: str) -> dict:
|
|
156
|
-
from fastapi._compat import (
|
|
157
|
-
GenerateJsonSchema,
|
|
158
|
-
ModelField,
|
|
159
|
-
get_compat_model_name_map,
|
|
160
|
-
get_definitions,
|
|
161
|
-
)
|
|
162
|
-
|
|
163
157
|
class CadwynDummyModelForRepresentation(BaseModel):
|
|
164
158
|
my_field: model
|
|
165
159
|
|
|
166
|
-
model_name_map = get_compat_model_name_map(
|
|
167
|
-
|
|
160
|
+
model_name_map = get_compat_model_name_map(
|
|
161
|
+
[
|
|
162
|
+
CadwynDummyModelForRepresentation.model_fields["my_field"], # pyright: ignore[reportArgumentType]
|
|
163
|
+
]
|
|
164
|
+
)
|
|
165
|
+
|
|
168
166
|
_, definitions = get_definitions(
|
|
169
|
-
fields=[
|
|
170
|
-
|
|
167
|
+
fields=[
|
|
168
|
+
ModelField(
|
|
169
|
+
CadwynDummyModelForRepresentation.model_fields["my_field"],
|
|
170
|
+
"my_field",
|
|
171
|
+
), # pyright: ignore[reportArgumentType]
|
|
172
|
+
],
|
|
171
173
|
model_name_map=model_name_map,
|
|
172
174
|
separate_input_output_schemas=False,
|
|
173
175
|
)
|
|
@@ -58,6 +58,7 @@ PossibleFieldAttributes = Literal[
|
|
|
58
58
|
"allow_mutation",
|
|
59
59
|
"pattern",
|
|
60
60
|
"discriminator",
|
|
61
|
+
"json_schema_extra",
|
|
61
62
|
]
|
|
62
63
|
|
|
63
64
|
|
|
@@ -100,6 +101,7 @@ class FieldChanges:
|
|
|
100
101
|
allow_mutation: bool
|
|
101
102
|
pattern: str
|
|
102
103
|
discriminator: str
|
|
104
|
+
json_schema_extra: Union[dict[str, Any], Callable[[dict[str, Any]], None], None]
|
|
103
105
|
|
|
104
106
|
|
|
105
107
|
@dataclass(**DATACLASS_SLOTS)
|
|
@@ -178,6 +180,7 @@ class AlterFieldInstructionFactory:
|
|
|
178
180
|
allow_mutation: bool = Sentinel,
|
|
179
181
|
pattern: str = Sentinel,
|
|
180
182
|
discriminator: str = Sentinel,
|
|
183
|
+
json_schema_extra: Union[dict[str, Any], Callable[[dict[str, Any]], None], None] = Sentinel,
|
|
181
184
|
) -> FieldHadInstruction:
|
|
182
185
|
return FieldHadInstruction(
|
|
183
186
|
is_hidden_from_changelog=False,
|
|
@@ -222,6 +225,7 @@ class AlterFieldInstructionFactory:
|
|
|
222
225
|
allow_mutation=allow_mutation,
|
|
223
226
|
pattern=pattern,
|
|
224
227
|
discriminator=discriminator,
|
|
228
|
+
json_schema_extra=json_schema_extra,
|
|
225
229
|
),
|
|
226
230
|
)
|
|
227
231
|
|
|
@@ -132,12 +132,12 @@ In Cadwyn, you use the latest version. This attribute is a way for you to descri
|
|
|
132
132
|
|
|
133
133
|
This approach of *maintaining the present and describing the past* might appear weird. You just need to form the correct mindset which is counter-intuitive at first but after just one or two attempts at versioning you will see how much sense this approach makes.
|
|
134
134
|
|
|
135
|
-
Imagine you
|
|
135
|
+
Imagine you need to know what your code looked like two weeks ago. You would use `git checkout` or `git reset` with an older commit because `git` stores the latest version of your code (which is also called HEAD) and the diffs between it and each previous version as a chain of changes. This is exactly how Cadwyn works! We store the latest version and use the diffs to regenerate the older versions.
|
|
136
136
|
|
|
137
137
|
<details>
|
|
138
138
|
<summary>Note to curious readers</summary>
|
|
139
139
|
|
|
140
|
-
Git doesn't actually work this way internally. My description is closer to how SVN works. It is just a really
|
|
140
|
+
Git doesn't actually work this way internally. My description is closer to how SVN works. It is just a really simple metaphor to explain a concept.
|
|
141
141
|
</details>
|
|
142
142
|
|
|
143
143
|
### Data migrations
|
|
@@ -167,7 +167,7 @@ class RemoveTaxIdEndpoints(VersionChange):
|
|
|
167
167
|
request.body["created_at"] = request.body.pop("creation_date")
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
Did you notice how the schema for `InvoiceCreateRequest` is specified in our migration? This
|
|
170
|
+
Did you notice how the schema for `InvoiceCreateRequest` is specified in our migration? This signals Cadwyn to apply it to all routes with this schema as their body.
|
|
171
171
|
|
|
172
172
|
Now we have not only described how schemas changed but we have also described how to migrate a request of the old version to the new version. When Cadwyn receives a request targeting a particular version, the request is first validated against the schema of that particular version. Then Cadwyn applies all request migrations until the latest version to migrate the request to latest. So now your business logic receives the latest version of the request yet for your clients you have two versions of your API -- you have added variability without introducing any complexity into your business logic.
|
|
173
173
|
|
|
@@ -204,13 +204,13 @@ class RemoveTaxIdEndpoints(VersionChange):
|
|
|
204
204
|
response.body["creation_date"] = response.body.pop("created_at")
|
|
205
205
|
```
|
|
206
206
|
|
|
207
|
-
Did you notice how the schema for `InvoiceResource` is specified in our migration? This
|
|
207
|
+
Did you notice how the schema for `InvoiceResource` is specified in our migration? This signals Cadwyn to apply it to all routes with this schema as their `response_model`. Notice also that we now use `BaseInvoice` in our instructions -- imagine it is the parent of both `InvoiceCreateRequest` and `InvoiceResource` so renaming it there will rename it in these schemas as well. You can, however, apply the instructions to both individual schemas instead of their parent if you want to.
|
|
208
208
|
|
|
209
|
-
Now our request comes, Cadwyn migrates it to the latest version using our request migration, then we do our business logic, return the latest response from it, and Cadwyn migrates it back to the request version. Does our business logic or database know about the fact that we have two versions? No, not at all! It is zero-cost.
|
|
209
|
+
Now our request comes, Cadwyn migrates it to the latest version using our request migration, then we do our business logic, return the latest response from it, and Cadwyn migrates it back to the request version. Does our business logic or database know about the fact that we have two versions? No, not at all! It is zero-cost. Consider the benefits of supporting not just two, but two hundred versions.
|
|
210
210
|
|
|
211
211
|

|
|
212
212
|
|
|
213
|
-
**Notice** how
|
|
213
|
+
**Notice** how the **latest** versions of our schemas are used in our migration -- this pattern can be found everywhere in Cadwyn. The latest version of your schemas is used to describe what happened to all other versions because other versions might not exist when you are defining migrations for them.
|
|
214
214
|
|
|
215
215
|
#### Path-based migration specification
|
|
216
216
|
|
|
@@ -260,13 +260,13 @@ from cadwyn import (
|
|
|
260
260
|
|
|
261
261
|
|
|
262
262
|
class RemoveTaxIdEndpoints(VersionChange):
|
|
263
|
-
description = "
|
|
263
|
+
description = "Replace status code 400 with 404 in 'GET /v1/invoices' if invoice is not found"
|
|
264
264
|
instructions_to_migrate_to_previous_version = ()
|
|
265
265
|
|
|
266
266
|
@convert_response_to_previous_version_for(
|
|
267
267
|
"/v1/invoices", ["GET"], migrate_http_errors=True
|
|
268
268
|
)
|
|
269
|
-
def
|
|
269
|
+
def replace_400_with_404(response: ResponseInfo):
|
|
270
270
|
if response.status_code == 400:
|
|
271
271
|
response.status_code = 404
|
|
272
272
|
```
|
|
@@ -292,9 +292,9 @@ Cadwyn can migrate more than just request bodies.
|
|
|
292
292
|
|
|
293
293
|
#### Internal representations
|
|
294
294
|
|
|
295
|
-
We have only reviewed
|
|
295
|
+
We have only reviewed simple cases so far. But what happens when you cannot just migrate your data that easily? It can happen because your earlier versions had **more data** than your newer versions. Or that data had more formats.
|
|
296
296
|
|
|
297
|
-
|
|
297
|
+
Imagine that previously the `User` schema had a list of addresses but now we want to make a breaking change and turn them into a single address. The naive migration will just take the first address from the list for requests and turn that address into a list for responses like so:
|
|
298
298
|
|
|
299
299
|
```python
|
|
300
300
|
from cadwyn import (
|
|
@@ -328,7 +328,7 @@ class RemoveTaxIdEndpoints(VersionChange):
|
|
|
328
328
|
response.body["addresses"] = [response.body.pop("address")]
|
|
329
329
|
```
|
|
330
330
|
|
|
331
|
-
But this will not work.
|
|
331
|
+
But this will not work. If the user from the old version requests to save three addresses, only one will actually be saved. Old data is also going to be affected -- if old users had multiple addresses, we will only be able to return one of them. This is bad -- we have made a breaking change!
|
|
332
332
|
|
|
333
333
|
In order to solve this issue, Cadwyn uses a concept of **internal representations**. An internal representation of your data is like a database entry of your data -- it is its **latest** version plus all the fields that are incompatible with the latest API version. If we were talking about classes, then internal representation would be a child of your latest schemas -- it has all the same data and a little more, it expands its functionality. Essentially your internal representation of user object can contain much more data than your latest schemas.
|
|
334
334
|
|
|
@@ -357,11 +357,11 @@ class RemoveTaxIdEndpoints(VersionChange):
|
|
|
357
357
|
)
|
|
358
358
|
```
|
|
359
359
|
|
|
360
|
-
Yes, we do not need any of the migrations anymore because responses are handled automatically. See how-to section for an example of
|
|
360
|
+
Yes, we do not need any of the migrations anymore because responses are handled automatically. See the how-to section for an example of achieving the same result with requests.
|
|
361
361
|
|
|
362
362
|
#### Manual body migrations
|
|
363
363
|
|
|
364
|
-
Oftentimes you will
|
|
364
|
+
Oftentimes you will need to migrate your data outside of routing, manually. For example, when you need to send a versioned response to your client via webhook or inside a worker/cronjob. In these instances, you can use `cadwyn.VersionBundle.migrate_response_body`:
|
|
365
365
|
|
|
366
366
|
```python
|
|
367
367
|
from users import UserResource
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Contribution Guide
|
|
2
|
+
|
|
3
|
+
## Setting up the environment
|
|
4
|
+
|
|
5
|
+
* The minimum supported version is Python 3.10. It is recommended to manage multiple Python versions on your system with [uv](https://docs.astral.sh/uv/)
|
|
6
|
+
* We maintain a Makefile with several commands to help with common tasks
|
|
7
|
+
|
|
8
|
+
1. Install [uv](https://docs.astral.sh/uv/)
|
|
9
|
+
2. Run `uv sync` to create a virtual environment and install the dependencies
|
|
10
|
+
3. Install [pre-commit](https://pre-commit.com/) using uv: `uv tool install pre-commit`
|
|
11
|
+
4. Run `pre-commit install --install-hooks` to install pre-commit hooks
|
|
12
|
+
|
|
13
|
+
## Code contributions
|
|
14
|
+
|
|
15
|
+
### Workflow
|
|
16
|
+
|
|
17
|
+
1. [Fork](https://github.com/zmievsa/cadwyn/fork) the [Cadwyn repository](https://github.com/zmievsa/cadwyn)
|
|
18
|
+
2. Clone your fork locally with git
|
|
19
|
+
3. [Set up the environment](#setting-up-the-environment)
|
|
20
|
+
4. Make your changes
|
|
21
|
+
5. Commit your changes to git
|
|
22
|
+
6. Push the changes
|
|
23
|
+
7. Open a [pull request](https://docs.github.com/en/pull-requests). Give the pull request a descriptive title indicating what was changed
|
|
24
|
+
|
|
25
|
+
## Guidelines for writing code
|
|
26
|
+
|
|
27
|
+
* Code should be [Pythonic and zen](https://peps.python.org/pep-0020/)
|
|
28
|
+
* All code should be fully [typed](https://peps.python.org/pep-0484/). This is enforced via [ruff](https://github.com/astral-sh/ruff) but the type hinting itself will be enforced by [pyright](https://github.com/microsoft/pyright/) in the future
|
|
29
|
+
* When complex types are required, use [type aliases](https://docs.python.org/3/library/typing.html#type-aliases)
|
|
30
|
+
* If something cannot be typed correctly due to the limitations of the type checkers, use [typing.cast](https://docs.python.org/3/library/typing.html#typing.cast) to resolve the issue. However, use `typing.cast` only as a last resort, after exhausting all other options of [type narrowing](https://mypy.readthedocs.io/en/stable/type_narrowing.html), such as [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) checks and [type guards](https://docs.python.org/3/library/typing.html#typing.TypeGuard)
|
|
31
|
+
* Use `pyright: ignore` once you have verified that the line is correct, but pyright has issues with it
|
|
32
|
+
* If you are adding or modifying existing code, make sure that it's fully tested. 100% test coverage is mandatory, and will be checked on the PR using [Github Actions](https://github.com/features/actions)
|
|
33
|
+
* When adding a new public interface, make sure you have included it in the concept documentation located in `docs/concepts.md`. If applicable, add or modify examples in the docs related to the new functionality
|
|
34
|
+
|
|
35
|
+
### Writing and running tests
|
|
36
|
+
|
|
37
|
+
Tests are contained within the `tests` directory, and follow roughly the same
|
|
38
|
+
directory structure as the `cadwyn` module. If you are adding a test
|
|
39
|
+
case, it should be located within the correct submodule of `tests`. E.g.
|
|
40
|
+
tests for `cadwyn/codegen.py` reside in `tests/codegen`.
|
|
41
|
+
|
|
42
|
+
`make test` to run tests located in `tests`
|
|
43
|
+
|
|
44
|
+
### Running type checkers
|
|
45
|
+
|
|
46
|
+
We use [pyright](https://github.com/microsoft/pyright/) to enforce type safety.
|
|
47
|
+
You can run it with:
|
|
48
|
+
|
|
49
|
+
`uv run pyright .`
|
|
50
|
+
|
|
51
|
+
## Project documentation
|
|
52
|
+
|
|
53
|
+
The documentation is located in the `/docs` directory and uses
|
|
54
|
+
[Markdown](https://www.markdownguide.org/).
|
|
55
|
+
|
|
56
|
+
### Docs theme and appearance
|
|
57
|
+
|
|
58
|
+
We welcome contributions that improve the appearance and usability of the docs. We use [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) If you wish to contribute to the docs style / setup, or static site generation, consult the theme docs as a first step.
|
|
59
|
+
|
|
60
|
+
### Running the docs locally
|
|
61
|
+
|
|
62
|
+
After improving the docs, serve the documentation with `mkdocs serve`
|
|
63
|
+
|
|
64
|
+
### Writing and editing docs
|
|
65
|
+
|
|
66
|
+
We welcome contributions that improve the content of the docs. Feel free to add examples, clarify text, restructure the docs, etc., but make sure to follow these guidelines:
|
|
67
|
+
|
|
68
|
+
* Write text in idiomatic, simple English
|
|
69
|
+
* Opt for [Oxford commas](https://en.wikipedia.org/wiki/Serial_comma) when listing a series of terms
|
|
70
|
+
* Keep examples simple and self contained
|
|
71
|
+
* Provide links where applicable
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
# Change the business logic in a new version
|
|
3
|
+
|
|
4
|
+
First, ask yourself: are you sure a behavioral change is really necessary? Are you sure it is not possible to keep the same logic for both versions? Or at least make the behavior depend on the received data? Behavioral changes (or **side effects**) are the least maintainable part of almost any versioning approach. They produce the largest footprint on your code. So if you are not careful, your logic will be littered with version checks.
|
|
5
|
+
|
|
6
|
+
But if you are certain that you need to make a breaking behavioral change, Cadwyn has all the tools to minimize its impact as much as possible.
|
|
7
|
+
|
|
8
|
+
## Calling endpoint causes unexpected data modifications
|
|
9
|
+
|
|
10
|
+
Use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
11
|
+
|
|
12
|
+
## Calling endpoint doesn't cause expected data modifications
|
|
13
|
+
|
|
14
|
+
Use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
15
|
+
|
|
16
|
+
## Calling endpoint doesn't cause expected additional actions (e.g. Webhooks)
|
|
17
|
+
|
|
18
|
+
Use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
19
|
+
|
|
20
|
+
## Errors
|
|
21
|
+
|
|
22
|
+
### Change the status code or a message in an HTTP error
|
|
23
|
+
|
|
24
|
+
You can [migrate anything about the error](../../concepts/version_changes.md#migration-of-http-errors) in a version change.
|
|
25
|
+
|
|
26
|
+
### Introduce a new error or remove an old error
|
|
27
|
+
|
|
28
|
+
Use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cadwyn"
|
|
3
|
-
version = "5.
|
|
3
|
+
version = "5.6.0"
|
|
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.
|
|
54
|
+
"fastapi >=0.119.0",
|
|
55
55
|
"starlette >=0.30.0",
|
|
56
56
|
"pydantic >=2.11.0",
|
|
57
57
|
"jinja2 >=3.1.2",
|
|
@@ -62,7 +62,7 @@ dependencies = [
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
[project.optional-dependencies]
|
|
65
|
-
standard = ["fastapi[standard]>=0.
|
|
65
|
+
standard = ["fastapi[standard]>=0.119.0", "typer>=0.7.0"]
|
|
66
66
|
|
|
67
67
|
[tool.uv]
|
|
68
68
|
dev-dependencies = [
|
|
@@ -10,8 +10,6 @@ from cadwyn.structure.versions import Version, VersionBundle
|
|
|
10
10
|
from tests.test_cli import code
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
# TODO: Return this test once https://github.com/pydantic/pydantic/pull/11898 is merged
|
|
14
|
-
@pytest.mark.xfail
|
|
15
13
|
def test__render_model__with_weird_types():
|
|
16
14
|
result = render_model_by_path(
|
|
17
15
|
"tests._resources.render.complex.classes:ModelWithWeirdFields",
|
|
@@ -10,6 +10,7 @@ from cadwyn.exceptions import (
|
|
|
10
10
|
CadwynStructureError,
|
|
11
11
|
InvalidGenerationInstructionError,
|
|
12
12
|
)
|
|
13
|
+
from cadwyn.schema_generation import SchemaGenerator
|
|
13
14
|
from cadwyn.structure import schema
|
|
14
15
|
from tests.conftest import (
|
|
15
16
|
CreateRuntimeSchemas,
|
|
@@ -68,16 +69,17 @@ def test__field_existed_as__original_schema_has_a_field(create_runtime_schemas:
|
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
def test__field_existed_as__extras_are_added(create_runtime_schemas: CreateRuntimeSchemas):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
with pytest.warns(DeprecationWarning):
|
|
73
|
+
schemas = create_runtime_schemas(
|
|
74
|
+
version_change(
|
|
75
|
+
schema(EmptySchema)
|
|
76
|
+
.field("foo")
|
|
77
|
+
.existed_as(
|
|
78
|
+
type=int,
|
|
79
|
+
info=Field(deflolbtt="hewwo"), # pyright: ignore[reportCallIssue]
|
|
80
|
+
),
|
|
81
|
+
)
|
|
79
82
|
)
|
|
80
|
-
)
|
|
81
83
|
|
|
82
84
|
class ExpectedSchema(BaseModel):
|
|
83
85
|
foo: int = Field(json_schema_extra={"deflolbtt": "hewwo"})
|
|
@@ -180,7 +182,7 @@ def assert_field_had_changes_apply(
|
|
|
180
182
|
attr: str,
|
|
181
183
|
attr_value: Any,
|
|
182
184
|
create_runtime_schemas: CreateRuntimeSchemas,
|
|
183
|
-
):
|
|
185
|
+
) -> dict[str, SchemaGenerator]:
|
|
184
186
|
schemas = create_runtime_schemas(version_change(schema(model).field("foo").had(**{attr: attr_value})))
|
|
185
187
|
|
|
186
188
|
field_info = schemas["2000-01-01"][model].model_fields["foo"]
|
|
@@ -190,6 +192,8 @@ def assert_field_had_changes_apply(
|
|
|
190
192
|
else:
|
|
191
193
|
assert getattr(field_info, attr) == attr_value
|
|
192
194
|
|
|
195
|
+
return schemas
|
|
196
|
+
|
|
193
197
|
|
|
194
198
|
@pytest.mark.parametrize(
|
|
195
199
|
("attr", "attr_value"),
|
|
@@ -266,7 +270,7 @@ def test__schema_field_had__decimal_field(attr: str, attr_value: Any, create_run
|
|
|
266
270
|
|
|
267
271
|
@pytest.mark.parametrize(
|
|
268
272
|
("attr", "attr_value"),
|
|
269
|
-
[("exclude",
|
|
273
|
+
[("exclude", True)],
|
|
270
274
|
)
|
|
271
275
|
def test__schema_field_had__list_of_int_field(attr: str, attr_value: Any, create_runtime_schemas: CreateRuntimeSchemas):
|
|
272
276
|
class SchemaWithOneListOfIntField(BaseModel):
|
|
@@ -294,6 +298,46 @@ def test__schema_field_had__float_field(
|
|
|
294
298
|
)
|
|
295
299
|
|
|
296
300
|
|
|
301
|
+
def test_schema_field_had__json_schema_extra_as_dict(create_runtime_schemas: CreateRuntimeSchemas):
|
|
302
|
+
class SchemaWithFooHadDictJsonSchemaExtra(BaseModel):
|
|
303
|
+
foo: str
|
|
304
|
+
|
|
305
|
+
schemas = assert_field_had_changes_apply(
|
|
306
|
+
SchemaWithFooHadDictJsonSchemaExtra,
|
|
307
|
+
"json_schema_extra",
|
|
308
|
+
{"example": "bar"},
|
|
309
|
+
create_runtime_schemas,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Validate the JSON Schema produced by Pydantic contains the extra
|
|
313
|
+
model_cls = schemas["2000-01-01"][SchemaWithFooHadDictJsonSchemaExtra] # pyright: ignore
|
|
314
|
+
json_schema = model_cls.model_json_schema()
|
|
315
|
+
assert json_schema["properties"]["foo"]["example"] == "bar"
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test__schema_field_had__json_schema_extra_as_callable(create_runtime_schemas: CreateRuntimeSchemas):
|
|
319
|
+
def modify_schema(schema_dict: dict[str, Any]):
|
|
320
|
+
schema_dict["example"] = "bar"
|
|
321
|
+
schema_dict["custom"] = 42
|
|
322
|
+
|
|
323
|
+
class SchemaWithFooHadCallableJsonSchemaExtra(BaseModel):
|
|
324
|
+
foo: str
|
|
325
|
+
|
|
326
|
+
schemas = assert_field_had_changes_apply(
|
|
327
|
+
SchemaWithFooHadCallableJsonSchemaExtra,
|
|
328
|
+
"json_schema_extra",
|
|
329
|
+
modify_schema,
|
|
330
|
+
create_runtime_schemas,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
# Validate the JSON Schema produced by Pydantic contains changes from the callable
|
|
334
|
+
model_cls = schemas["2000-01-01"][SchemaWithFooHadCallableJsonSchemaExtra] # pyright: ignore
|
|
335
|
+
json_schema = model_cls.model_json_schema()
|
|
336
|
+
props = json_schema["properties"]["foo"]
|
|
337
|
+
assert props["example"] == "bar"
|
|
338
|
+
assert props["custom"] == 42
|
|
339
|
+
|
|
340
|
+
|
|
297
341
|
def test__schema_field_didnt_have__removing_default(create_runtime_schemas: CreateRuntimeSchemas):
|
|
298
342
|
class SchemaWithDefaults(BaseModel):
|
|
299
343
|
foo: str = "hewwo"
|
|
@@ -99,7 +99,7 @@ wheels = [
|
|
|
99
99
|
|
|
100
100
|
[[package]]
|
|
101
101
|
name = "cadwyn"
|
|
102
|
-
version = "5.
|
|
102
|
+
version = "5.6.0"
|
|
103
103
|
source = { editable = "." }
|
|
104
104
|
dependencies = [
|
|
105
105
|
{ name = "backports-strenum", marker = "python_full_version < '3.11'" },
|
|
@@ -144,8 +144,8 @@ dev = [
|
|
|
144
144
|
[package.metadata]
|
|
145
145
|
requires-dist = [
|
|
146
146
|
{ name = "backports-strenum", marker = "python_full_version < '3.11'", specifier = ">=1.3.1,<2" },
|
|
147
|
-
{ name = "fastapi", specifier = ">=0.
|
|
148
|
-
{ name = "fastapi", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.
|
|
147
|
+
{ name = "fastapi", specifier = ">=0.119.0" },
|
|
148
|
+
{ name = "fastapi", extras = ["standard"], marker = "extra == 'standard'", specifier = ">=0.119.0" },
|
|
149
149
|
{ name = "jinja2", specifier = ">=3.1.2" },
|
|
150
150
|
{ name = "pydantic", specifier = ">=2.11.0" },
|
|
151
151
|
{ name = "starlette", specifier = ">=0.30.0" },
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Contribution Guide
|
|
2
|
-
|
|
3
|
-
## Setting up the environment
|
|
4
|
-
|
|
5
|
-
* The lowest currently supported version is Python 3.10. You should use
|
|
6
|
-
[uv](https://docs.astral.sh/uv/) to manage multiple Python
|
|
7
|
-
versions on your system.
|
|
8
|
-
* We maintain a Makefile with several commands to help with common tasks.
|
|
9
|
-
|
|
10
|
-
1. Install [uv](https://docs.astral.sh/uv/)
|
|
11
|
-
2. Run `uv sync` to create a virtual environment and install the required development dependencies
|
|
12
|
-
3. Install [pre-commit](https://pre-commit.com/) using uv: `uv tool install pre-commit`
|
|
13
|
-
4. Run `pre-commit install --install-hooks` to install pre-commit hooks
|
|
14
|
-
|
|
15
|
-
## Code contributions
|
|
16
|
-
|
|
17
|
-
### Workflow
|
|
18
|
-
|
|
19
|
-
1. [Fork](https://github.com/zmievsa/cadwyn/fork) the [Cadwyn repository](https://github.com/zmievsa/cadwyn)
|
|
20
|
-
2. Clone your fork locally with git
|
|
21
|
-
3. [Set up the environment](#setting-up-the-environment)
|
|
22
|
-
4. Make your changes
|
|
23
|
-
5. Commit your changes to git.
|
|
24
|
-
6. Open a [pull request](https://docs.github.com/en/pull-requests). Give the pull request a descriptive title indicating what it changes.
|
|
25
|
-
|
|
26
|
-
## Guidelines for writing code
|
|
27
|
-
|
|
28
|
-
* Code should be [Pythonic and zen](https://peps.python.org/pep-0020/)
|
|
29
|
-
* All code should be fully [typed](https://peps.python.org/pep-0484/). This is enforced via [ruff](https://github.com/astral-sh/ruff) but the type hinting itself will be enforced by [pyright](https://github.com/microsoft/pyright/) in the future.
|
|
30
|
-
* When requiring complex types, use a [type alias](https://docs.python.org/3/library/typing.html#type-aliases).
|
|
31
|
-
* If something cannot be typed correctly due to a limitation of the type checkers, you may use [typing.cast](https://docs.python.org/3/library/typing.html#typing.cast) to rectify the situation. However, you should only use as a last resort if you've exhausted all other options of [type narrowing](https://mypy.readthedocs.io/en/stable/type_narrowing.html), such as [isinstance()](https://docs.python.org/3/library/functions.html#isinstance) checks and [type guards](https://docs.python.org/3/library/typing.html#typing.TypeGuard)
|
|
32
|
-
* You may use `pyright: ignore` if you ensured that a line is correct, but pyright has issues with it.
|
|
33
|
-
* If you are adding or modifying existing code, ensure that it's fully tested. 100% test coverage is mandatory, and will be checked on the PR using [Github Actions](https://github.com/features/actions)
|
|
34
|
-
* When adding a new public interface, it has to be included in the concept documentation located in `docs/concepts.md`. If applicable, add or modify examples in the docs related to the new functionality implemented.
|
|
35
|
-
|
|
36
|
-
### Writing and running tests
|
|
37
|
-
|
|
38
|
-
Tests are contained within the `tests` directory, and follow roughly the same
|
|
39
|
-
directory structure as the `cadwyn` module. If you are adding a test
|
|
40
|
-
case, it should be located within the correct submodule of `tests`. E.g.
|
|
41
|
-
tests for `cadwyn/codegen.py` reside in `tests/codegen`.
|
|
42
|
-
|
|
43
|
-
`make test` to run tests located in `tests`
|
|
44
|
-
|
|
45
|
-
### Running type checkers
|
|
46
|
-
|
|
47
|
-
We use [pyright](https://github.com/microsoft/pyright/) to enforce type safety.
|
|
48
|
-
You can run it with:
|
|
49
|
-
|
|
50
|
-
`uv run pyright .`
|
|
51
|
-
|
|
52
|
-
## Project documentation
|
|
53
|
-
|
|
54
|
-
The documentation is located in the `/docs` directory and is all in
|
|
55
|
-
[Markdown](https://www.markdownguide.org/).
|
|
56
|
-
|
|
57
|
-
### Docs theme and appearance
|
|
58
|
-
|
|
59
|
-
We welcome contributions that enhance / improve the appearance and usability of the docs. We use [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) If you wish to contribute to the docs style / setup, or static site generation, you should consult the theme docs as a first step.
|
|
60
|
-
|
|
61
|
-
### Running the docs locally
|
|
62
|
-
|
|
63
|
-
Then you can serve the documentation with `mkdocs serve`
|
|
64
|
-
|
|
65
|
-
### Writing and editing docs
|
|
66
|
-
|
|
67
|
-
We welcome contributions that enhance / improve the content of the docs. Feel free to add examples, clarify text, restructure the docs, etc., but make sure to follow these guidelines:
|
|
68
|
-
|
|
69
|
-
* Write text in idiomatic English, using simple language
|
|
70
|
-
* Opt for [Oxford commas](https://en.wikipedia.org/wiki/Serial_comma) when listing a series of terms
|
|
71
|
-
* Keep examples simple and self contained
|
|
72
|
-
* Provide links where applicable
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# Change the business logic in a new version
|
|
3
|
-
|
|
4
|
-
First, ask yourself: are you sure there really needs to be a behavioral change? Are you sure it is not possible to keep the same logic for both versions? Or at least make the behavior depend on the received data? Behavioral changes (or **side effects**) are the least maintainable part of almost any versioning approach. They produce the largest footprint on your code so if you are not careful -- your logic will be littered with version checks.
|
|
5
|
-
|
|
6
|
-
But if you are certain that you need to make a breaking behavioral change, Cadwyn has all the tools to minimize its impact as much as possible.
|
|
7
|
-
|
|
8
|
-
## Calling endpoint causes unexpected data modifications
|
|
9
|
-
|
|
10
|
-
You'd use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
11
|
-
|
|
12
|
-
## Calling endpoint doesn't cause expected data modifications
|
|
13
|
-
|
|
14
|
-
You'd use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
15
|
-
|
|
16
|
-
## Calling endpoint doesn't cause expected additional actions (e.g. Webhooks)
|
|
17
|
-
|
|
18
|
-
You'd use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
19
|
-
|
|
20
|
-
## Errors
|
|
21
|
-
|
|
22
|
-
### Change the status code or a message in an HTTP error
|
|
23
|
-
|
|
24
|
-
You can [migrate anything about the error](../../concepts/version_changes.md#migration-of-http-errors) in a version change.
|
|
25
|
-
|
|
26
|
-
### Introduce a new error or remove an old error
|
|
27
|
-
|
|
28
|
-
You'd use an `if statement` with a [side effect](../../concepts/version_changes.md#version-changes-with-side-effects).
|
|
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
|