cadwyn 5.3.3__tar.gz → 5.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cadwyn might be problematic. Click here for more details.
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.pre-commit-config.yaml +17 -9
- {cadwyn-5.3.3 → cadwyn-5.4.2}/CHANGELOG.md +20 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/PKG-INFO +7 -8
- {cadwyn-5.3.3 → cadwyn-5.4.2}/README.md +4 -6
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/__main__.py +1 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/_asts.py +1 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/applications.py +2 -2
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/changelogs.py +5 -5
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/route_generation.py +2 -2
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/schema_generation.py +56 -37
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/data.py +2 -2
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/schemas.py +2 -2
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/versions.py +1 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/version_changes.md +25 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/home/CONTRIBUTING.md +1 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_openapi_schemas/remove_field.md +2 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/index.md +4 -10
- cadwyn-5.4.2/docs/theory/how_to_build_versioning_framework.md +24 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/theory/literature.md +1 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/pyproject.toml +4 -3
- {cadwyn-5.3.3 → cadwyn-5.4.2}/ruff.toml +21 -13
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/conftest.py +1 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_applications.py +14 -12
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_cli.py +0 -15
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_render.py +4 -2
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_router_generation.py +11 -11
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_schema_generation/test_schema_field.py +46 -1
- {cadwyn-5.3.3 → cadwyn-5.4.2}/uv.lock +867 -838
- cadwyn-5.3.3/docs/img/sponsor_logos/monite.png +0 -0
- cadwyn-5.3.3/docs/theory/how_to_build_versioning_framework.md +0 -23
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/.gitignore +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/LICENSE +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/Makefile +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/_importer.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/_internal/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/_internal/context_vars.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/_render.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/_utils.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/dependencies.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/exceptions.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/middleware.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/py.typed +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/routing.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/CNAME +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/api_version_parameter.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/changelogs.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/cli.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/index.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/main_app.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/methodology.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/concepts/testing.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/index.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/logos/cadwyn_icon_transparent.svg +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/logos/cadwyn_transparent.svg +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/logos/cadwyn_with_background.svg +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/plugin.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/stylesheets/extra.css +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/docs_src/ruff.toml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/mkdocs.yml +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/scripts/fix_links.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/scripts/split_md.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_data/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/utils.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_changelog.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_routing.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/test_structure.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/tutorial/main.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.3.3 → cadwyn-5.4.2}/tox.ini +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
default_language_version:
|
|
2
|
-
python: python3
|
|
2
|
+
python: python3
|
|
3
3
|
repos:
|
|
4
4
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
5
5
|
rev: v5.0.0
|
|
6
6
|
hooks:
|
|
7
7
|
- id: check-added-large-files
|
|
8
8
|
- id: check-yaml
|
|
9
|
-
args:
|
|
9
|
+
args:
|
|
10
|
+
- '--unsafe'
|
|
10
11
|
- id: check-toml
|
|
11
12
|
- id: end-of-file-fixer
|
|
12
13
|
- id: trailing-whitespace
|
|
@@ -18,27 +19,34 @@ repos:
|
|
|
18
19
|
- id: python-check-blanket-noqa
|
|
19
20
|
|
|
20
21
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
21
|
-
rev: v0.
|
|
22
|
+
rev: v0.11.10
|
|
22
23
|
hooks:
|
|
23
24
|
- id: ruff
|
|
24
|
-
args:
|
|
25
|
+
args:
|
|
26
|
+
- '--fix'
|
|
27
|
+
- '--exit-non-zero-on-fix'
|
|
25
28
|
- id: ruff-format
|
|
26
29
|
|
|
27
30
|
- repo: https://github.com/adamchainz/blacken-docs
|
|
28
|
-
rev:
|
|
31
|
+
rev: 1.19.1 # replace with latest tag on GitHub
|
|
29
32
|
hooks:
|
|
30
33
|
- id: blacken-docs
|
|
31
34
|
additional_dependencies:
|
|
32
35
|
- black==22.12.0
|
|
33
|
-
args:
|
|
36
|
+
args:
|
|
37
|
+
- '--line-length=80'
|
|
38
|
+
- '--target-version=py310'
|
|
39
|
+
- '--skip-errors'
|
|
34
40
|
|
|
35
41
|
- repo: https://github.com/igorshubovych/markdownlint-cli
|
|
36
|
-
rev: v0.
|
|
42
|
+
rev: v0.44.0
|
|
37
43
|
hooks:
|
|
38
44
|
- id: markdownlint
|
|
39
|
-
args:
|
|
45
|
+
args:
|
|
46
|
+
- '--disable'
|
|
47
|
+
- MD013
|
|
40
48
|
|
|
41
49
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
42
|
-
rev: 0.
|
|
50
|
+
rev: 0.7.5
|
|
43
51
|
hooks:
|
|
44
52
|
- id: uv-lock
|
|
@@ -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.4.2]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* Added support for computed fields
|
|
13
|
+
|
|
14
|
+
## [5.4.1]
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
* Fixed import error in python 3.9 when using typing_extensions==3.14.0
|
|
19
|
+
|
|
20
|
+
## [5.4.0]
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `typing_inspection` dependency from pydantic team for complex isinstance checks that must be the same between `typing` and `typing_extensions`
|
|
25
|
+
- Support for `pydantic>=2.12.0` FieldInfo refactoring from https://github.com/pydantic/pydantic/pull/11898
|
|
26
|
+
|
|
27
|
+
|
|
8
28
|
## [5.3.3]
|
|
9
29
|
|
|
10
30
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.4.2
|
|
4
4
|
Summary: Production-ready community-driven modern Stripe-like API versioning in FastAPI
|
|
5
5
|
Project-URL: Source code, https://github.com/zmievsa/cadwyn
|
|
6
6
|
Project-URL: Documentation, https://docs.cadwyn.dev
|
|
@@ -37,9 +37,10 @@ Requires-Python: >=3.9
|
|
|
37
37
|
Requires-Dist: backports-strenum<2,>=1.3.1; python_version < '3.11'
|
|
38
38
|
Requires-Dist: fastapi>=0.112.4
|
|
39
39
|
Requires-Dist: jinja2>=3.1.2
|
|
40
|
-
Requires-Dist: pydantic>=2.
|
|
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
|
+
Requires-Dist: typing-inspection>=0.4.0
|
|
43
44
|
Provides-Extra: standard
|
|
44
45
|
Requires-Dist: fastapi[standard]>=0.112.3; extra == 'standard'
|
|
45
46
|
Requires-Dist: typer>=0.7.0; extra == 'standard'
|
|
@@ -71,12 +72,12 @@ Production-ready community-driven modern [Stripe-like](https://stripe.com/blog/a
|
|
|
71
72
|
|
|
72
73
|
## Who is this for?
|
|
73
74
|
|
|
74
|
-
Cadwyn allows you to maintain the implementation just for your newest API version and get all the older versions generated automatically. You keep API
|
|
75
|
+
Cadwyn allows you to maintain the implementation just for your newest API version and get all the older versions generated automatically. You keep API backward compatibility encapsulated in small and independent "version change" modules while your business logic stays simple and knows nothing about versioning.
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
This [approach](https://docs.cadwyn.dev/theory/how_we_got_here/#ii-migration-based-response-building) may be useful if you want to:
|
|
77
78
|
|
|
78
79
|
1. Support many API versions for a long time
|
|
79
|
-
2.
|
|
80
|
+
2. Have features and bugfixes automatically backported to older API versions
|
|
80
81
|
|
|
81
82
|
Whether you are a newbie in API versioning, a pro looking for a sophisticated tool, an experimenter looking to build a similar framework, or even someone who just wants to learn about all approaches to API versioning -- Cadwyn has the functionality, theory, and documentation to cover all the mentioned use cases.
|
|
82
83
|
|
|
@@ -86,6 +87,4 @@ The [documentation](https://docs.cadwyn.dev) has everything you need to succeed.
|
|
|
86
87
|
|
|
87
88
|
## Sponsors
|
|
88
89
|
|
|
89
|
-
These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one too!
|
|
90
|
-
|
|
91
|
-
[](https://docs.monite.com/)
|
|
90
|
+
These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one, too!
|
|
@@ -24,12 +24,12 @@ Production-ready community-driven modern [Stripe-like](https://stripe.com/blog/a
|
|
|
24
24
|
|
|
25
25
|
## Who is this for?
|
|
26
26
|
|
|
27
|
-
Cadwyn allows you to maintain the implementation just for your newest API version and get all the older versions generated automatically. You keep API
|
|
27
|
+
Cadwyn allows you to maintain the implementation just for your newest API version and get all the older versions generated automatically. You keep API backward compatibility encapsulated in small and independent "version change" modules while your business logic stays simple and knows nothing about versioning.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
This [approach](https://docs.cadwyn.dev/theory/how_we_got_here/#ii-migration-based-response-building) may be useful if you want to:
|
|
30
30
|
|
|
31
31
|
1. Support many API versions for a long time
|
|
32
|
-
2.
|
|
32
|
+
2. Have features and bugfixes automatically backported to older API versions
|
|
33
33
|
|
|
34
34
|
Whether you are a newbie in API versioning, a pro looking for a sophisticated tool, an experimenter looking to build a similar framework, or even someone who just wants to learn about all approaches to API versioning -- Cadwyn has the functionality, theory, and documentation to cover all the mentioned use cases.
|
|
35
35
|
|
|
@@ -39,6 +39,4 @@ The [documentation](https://docs.cadwyn.dev) has everything you need to succeed.
|
|
|
39
39
|
|
|
40
40
|
## Sponsors
|
|
41
41
|
|
|
42
|
-
These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one too!
|
|
43
|
-
|
|
44
|
-
[](https://docs.monite.com/)
|
|
42
|
+
These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one, too!
|
|
@@ -22,7 +22,7 @@ NoneType = type(None)
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
# A parent type of typing._GenericAlias
|
|
25
|
-
_BaseGenericAlias = cast(type, type(List[int])).mro()[1] # noqa: UP006
|
|
25
|
+
_BaseGenericAlias = cast("type", type(List[int])).mro()[1] # noqa: UP006
|
|
26
26
|
|
|
27
27
|
# type(list[int]) and type(List[int]) are different which is why we have to do this.
|
|
28
28
|
# Please note that this problem is much wider than just lists which is why we use typing._BaseGenericAlias
|
|
@@ -400,7 +400,7 @@ class Cadwyn(FastAPI):
|
|
|
400
400
|
init_oauth=self.swagger_ui_init_oauth,
|
|
401
401
|
swagger_ui_parameters=self.swagger_ui_parameters,
|
|
402
402
|
)
|
|
403
|
-
return self._render_docs_dashboard(req, cast(str, self.docs_url))
|
|
403
|
+
return self._render_docs_dashboard(req, cast("str", self.docs_url))
|
|
404
404
|
|
|
405
405
|
async def redoc_dashboard(self, req: Request) -> Response:
|
|
406
406
|
version = req.query_params.get("version")
|
|
@@ -410,7 +410,7 @@ class Cadwyn(FastAPI):
|
|
|
410
410
|
openapi_url = root_path + f"{self.openapi_url}?version={version}"
|
|
411
411
|
return get_redoc_html(openapi_url=openapi_url, title=f"{self.title} - ReDoc")
|
|
412
412
|
|
|
413
|
-
return self._render_docs_dashboard(req, docs_url=cast(str, self.redoc_url))
|
|
413
|
+
return self._render_docs_dashboard(req, docs_url=cast("str", self.redoc_url))
|
|
414
414
|
|
|
415
415
|
def _extract_root_path(self, req: Request):
|
|
416
416
|
return req.scope.get("root_path", "").rstrip("/")
|
|
@@ -93,7 +93,7 @@ def _generate_changelog(versions: VersionBundle, router: _RootCadwynAPIRouter) -
|
|
|
93
93
|
generator_from_newer_version,
|
|
94
94
|
generator_from_older_version,
|
|
95
95
|
schemas_from_older_version,
|
|
96
|
-
cast(list[APIRoute], routes_from_newer_version),
|
|
96
|
+
cast("list[APIRoute]", routes_from_newer_version),
|
|
97
97
|
)
|
|
98
98
|
if changelog_entry is not None: # pragma: no branch # This should never happen
|
|
99
99
|
version_change_changelog.instructions.append(CadwynVersionChangeInstruction(changelog_entry))
|
|
@@ -321,18 +321,18 @@ def _convert_version_change_instruction_to_changelog_entry( # noqa: C901
|
|
|
321
321
|
if isinstance(instruction, EndpointDidntExistInstruction):
|
|
322
322
|
return CadwynEndpointWasAddedChangelogEntry(
|
|
323
323
|
path=instruction.endpoint_path,
|
|
324
|
-
methods=cast(Any, instruction.endpoint_methods),
|
|
324
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
325
325
|
)
|
|
326
326
|
elif isinstance(instruction, EndpointExistedInstruction):
|
|
327
327
|
return CadwynEndpointWasRemovedChangelogEntry(
|
|
328
328
|
path=instruction.endpoint_path,
|
|
329
|
-
methods=cast(Any, instruction.endpoint_methods),
|
|
329
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
330
330
|
)
|
|
331
331
|
elif isinstance(instruction, EndpointHadInstruction):
|
|
332
332
|
if instruction.attributes.include_in_schema is not Sentinel:
|
|
333
333
|
return CadwynEndpointWasRemovedChangelogEntry(
|
|
334
334
|
path=instruction.endpoint_path,
|
|
335
|
-
methods=cast(Any, instruction.endpoint_methods),
|
|
335
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
336
336
|
)
|
|
337
337
|
|
|
338
338
|
renaming_map = {"operation_id": "operationId"}
|
|
@@ -379,7 +379,7 @@ def _convert_version_change_instruction_to_changelog_entry( # noqa: C901
|
|
|
379
379
|
attribute_changes.append(CadwynEndpointAttributeChange(name="responses", new_value=changed_responses))
|
|
380
380
|
return CadwynEndpointHadChangelogEntry(
|
|
381
381
|
path=instruction.endpoint_path,
|
|
382
|
-
methods=cast(Any, instruction.endpoint_methods),
|
|
382
|
+
methods=cast("Any", instruction.endpoint_methods),
|
|
383
383
|
changes=attribute_changes,
|
|
384
384
|
)
|
|
385
385
|
|
|
@@ -78,7 +78,7 @@ def generate_versioned_routers(
|
|
|
78
78
|
webhooks: Union[_WR, None] = None,
|
|
79
79
|
) -> GeneratedRouters[_R, _WR]:
|
|
80
80
|
if webhooks is None:
|
|
81
|
-
webhooks = cast(_WR, APIRouter())
|
|
81
|
+
webhooks = cast("_WR", APIRouter())
|
|
82
82
|
return _EndpointTransformer(router, versions, webhooks).transform()
|
|
83
83
|
|
|
84
84
|
|
|
@@ -167,7 +167,7 @@ class _EndpointTransformer(Generic[_R, _WR]):
|
|
|
167
167
|
|
|
168
168
|
# We know they are APIRoutes because of the check at the very beginning of the top loop.
|
|
169
169
|
# I.e. Because head_route is an APIRoute, both routes are APIRoutes too
|
|
170
|
-
older_route = cast(APIRoute, older_route)
|
|
170
|
+
older_route = cast("APIRoute", older_route)
|
|
171
171
|
# Wait.. Why do we need this code again?
|
|
172
172
|
if older_route.body_field is not None and _route_has_a_simple_body_schema(older_route):
|
|
173
173
|
if hasattr(older_route.body_field.type_, "__cadwyn_original_model__"):
|
|
@@ -11,14 +11,20 @@ from collections.abc import Callable, Sequence
|
|
|
11
11
|
from datetime import date
|
|
12
12
|
from enum import Enum
|
|
13
13
|
from functools import cache
|
|
14
|
-
from typing import
|
|
14
|
+
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
16
|
+
Annotated,
|
|
17
|
+
Generic,
|
|
18
|
+
Union,
|
|
19
|
+
_BaseGenericAlias, # pyright: ignore[reportAttributeAccessIssue]
|
|
20
|
+
cast,
|
|
21
|
+
)
|
|
15
22
|
|
|
16
23
|
import fastapi.params
|
|
17
24
|
import fastapi.security.base
|
|
18
25
|
import fastapi.utils
|
|
19
26
|
import pydantic
|
|
20
27
|
import pydantic._internal._decorators
|
|
21
|
-
import typing_extensions
|
|
22
28
|
from fastapi import Response
|
|
23
29
|
from fastapi.dependencies.utils import is_async_gen_callable, is_coroutine_callable, is_gen_callable
|
|
24
30
|
from fastapi.routing import APIRoute
|
|
@@ -32,12 +38,12 @@ from pydantic._internal._decorators import (
|
|
|
32
38
|
RootValidatorDecoratorInfo,
|
|
33
39
|
ValidatorDecoratorInfo,
|
|
34
40
|
)
|
|
41
|
+
from pydantic._internal._known_annotated_metadata import collect_known_metadata
|
|
35
42
|
from pydantic._internal._typing_extra import try_eval_type as pydantic_try_eval_type
|
|
36
43
|
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
|
37
44
|
from typing_extensions import (
|
|
38
45
|
Any,
|
|
39
46
|
Doc,
|
|
40
|
-
NewType,
|
|
41
47
|
Self,
|
|
42
48
|
TypeAlias,
|
|
43
49
|
TypeAliasType,
|
|
@@ -80,11 +86,6 @@ if TYPE_CHECKING:
|
|
|
80
86
|
from cadwyn.structure.versions import HeadVersion, Version, VersionBundle
|
|
81
87
|
|
|
82
88
|
|
|
83
|
-
if sys.version_info >= (3, 10):
|
|
84
|
-
from typing import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
|
|
85
|
-
else:
|
|
86
|
-
from typing_extensions import _BaseGenericAlias # pyright: ignore[reportAttributeAccessIssue]
|
|
87
|
-
|
|
88
89
|
_Call = TypeVar("_Call", bound=Callable[..., Any])
|
|
89
90
|
|
|
90
91
|
_FieldName: TypeAlias = str
|
|
@@ -153,16 +154,12 @@ class PydanticFieldWrapper:
|
|
|
153
154
|
)
|
|
154
155
|
|
|
155
156
|
|
|
156
|
-
def _extract_passed_field_attributes(field_info: FieldInfo):
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
for
|
|
160
|
-
if
|
|
157
|
+
def _extract_passed_field_attributes(field_info: FieldInfo) -> dict[str, object]:
|
|
158
|
+
return {
|
|
159
|
+
k: v
|
|
160
|
+
for k, v in (field_info._attributes_set | collect_known_metadata(field_info.metadata)[0]).items()
|
|
161
|
+
if k in _all_field_arg_names and not (k == "frozen" and v is None)
|
|
161
162
|
}
|
|
162
|
-
# PydanticV2 always adds frozen to _attributes_set but we don't want it if it wasn't explicitly set
|
|
163
|
-
if attributes.get("frozen", ...) is None:
|
|
164
|
-
attributes.pop("frozen")
|
|
165
|
-
return attributes
|
|
166
163
|
|
|
167
164
|
|
|
168
165
|
@dataclasses.dataclass(**DATACLASS_SLOTS)
|
|
@@ -239,6 +236,11 @@ def _wrap_validator(func: Callable, is_pydantic_v1_style_validator: Any, decorat
|
|
|
239
236
|
func = func.__func__
|
|
240
237
|
kwargs = dataclasses.asdict(decorator_info)
|
|
241
238
|
decorator_fields = kwargs.pop("fields", None)
|
|
239
|
+
|
|
240
|
+
# wrapped_property is not accepted by computed_field()
|
|
241
|
+
if isinstance(decorator_info, ComputedFieldInfo):
|
|
242
|
+
kwargs.pop("wrapped_property", None)
|
|
243
|
+
|
|
242
244
|
actual_decorator = PYDANTIC_DECORATOR_TYPE_TO_DECORATOR_MAP[type(decorator_info)]
|
|
243
245
|
if is_pydantic_v1_style_validator:
|
|
244
246
|
# There's an inconsistency in their interfaces so we gotta resort to this
|
|
@@ -265,7 +267,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
|
|
|
265
267
|
# For example, when "from __future__ import annotations" is used in the file with the schema
|
|
266
268
|
if model is not BaseModel:
|
|
267
269
|
model.model_rebuild(raise_errors=False)
|
|
268
|
-
model = cast(type[_T_PYDANTIC_MODEL], model)
|
|
270
|
+
model = cast("type[_T_PYDANTIC_MODEL]", model)
|
|
269
271
|
|
|
270
272
|
decorators = _get_model_decorators(model)
|
|
271
273
|
validators = {}
|
|
@@ -345,7 +347,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
|
|
|
345
347
|
def _get_field_and_validator_names_from_model(cls: type) -> tuple[set[_FieldName], set[str]]:
|
|
346
348
|
fields = cls.model_fields
|
|
347
349
|
source = inspect.getsource(cls)
|
|
348
|
-
cls_ast = cast(ast.ClassDef, ast.parse(textwrap.dedent(source)).body[0])
|
|
350
|
+
cls_ast = cast("ast.ClassDef", ast.parse(textwrap.dedent(source)).body[0])
|
|
349
351
|
validator_names = (
|
|
350
352
|
_get_validator_info_or_none(node)
|
|
351
353
|
for node in cls_ast.body
|
|
@@ -468,7 +470,7 @@ class _PydanticModelWrapper(Generic[_T_PYDANTIC_MODEL]):
|
|
|
468
470
|
fields = {name: field.generate_field_copy(generator) for name, field in self.fields.items()}
|
|
469
471
|
model_copy = type(self.cls)(
|
|
470
472
|
self.name,
|
|
471
|
-
tuple(generator[cast(type[BaseModel], base)] for base in self.cls.__bases__),
|
|
473
|
+
tuple(generator[cast("type[BaseModel]", base)] for base in self.cls.__bases__),
|
|
472
474
|
self.other_attributes
|
|
473
475
|
| per_field_validators
|
|
474
476
|
| root_validators
|
|
@@ -587,9 +589,11 @@ class _AnnotationTransformer:
|
|
|
587
589
|
self._remake_endpoint_dependencies(route)
|
|
588
590
|
|
|
589
591
|
def _change_version_of_a_non_container_annotation(self, annotation: Any) -> Any:
|
|
590
|
-
|
|
592
|
+
from typing_inspection.typing_objects import is_any, is_newtype, is_typealiastype
|
|
593
|
+
|
|
594
|
+
if isinstance(annotation, (types.GenericAlias, _BaseGenericAlias)):
|
|
591
595
|
return get_origin(annotation)[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
|
|
592
|
-
elif
|
|
596
|
+
elif is_typealiastype(annotation):
|
|
593
597
|
if (
|
|
594
598
|
annotation.__module__ is not None and (annotation.__module__.startswith("pydantic."))
|
|
595
599
|
) or annotation.__name__ in _PYDANTIC_ALL_EXPORTED_NAMES:
|
|
@@ -616,7 +620,7 @@ class _AnnotationTransformer:
|
|
|
616
620
|
return getitem(
|
|
617
621
|
tuple(self.change_version_of_annotation(a) for a in get_args(annotation)),
|
|
618
622
|
)
|
|
619
|
-
elif annotation
|
|
623
|
+
elif is_any(annotation) or is_newtype(annotation):
|
|
620
624
|
return annotation
|
|
621
625
|
elif isinstance(annotation, type):
|
|
622
626
|
return self._change_version_of_type(annotation)
|
|
@@ -776,7 +780,7 @@ class SchemaGenerator:
|
|
|
776
780
|
wrapper = self._get_wrapper_for_model(model)
|
|
777
781
|
model_copy = wrapper.generate_model_copy(self)
|
|
778
782
|
self.concrete_models[model] = model_copy
|
|
779
|
-
return cast(type[_T_ANY_MODEL], model_copy)
|
|
783
|
+
return cast("type[_T_ANY_MODEL]", model_copy)
|
|
780
784
|
|
|
781
785
|
@overload
|
|
782
786
|
def _get_wrapper_for_model(self, model: type[BaseModel]) -> "_PydanticModelWrapper[BaseModel]": ...
|
|
@@ -865,7 +869,7 @@ def _apply_alter_schema_instructions(
|
|
|
865
869
|
elif isinstance(alter_schema_instruction, ValidatorExistedInstruction):
|
|
866
870
|
validator_name = get_name_of_function_wrapped_in_pydantic_validator(alter_schema_instruction.validator)
|
|
867
871
|
raw_validator = cast(
|
|
868
|
-
pydantic._internal._decorators.PydanticDescriptorProxy, alter_schema_instruction.validator
|
|
872
|
+
"pydantic._internal._decorators.PydanticDescriptorProxy", alter_schema_instruction.validator
|
|
869
873
|
)
|
|
870
874
|
schema_info.validators[validator_name] = _wrap_validator(
|
|
871
875
|
raw_validator.wrapped,
|
|
@@ -1041,15 +1045,19 @@ def _delete_field_attributes(
|
|
|
1041
1045
|
annotation: Any,
|
|
1042
1046
|
) -> None:
|
|
1043
1047
|
for attr_name in alter_schema_instruction.attributes:
|
|
1048
|
+
deleted = False
|
|
1049
|
+
|
|
1044
1050
|
if attr_name in field.passed_field_attributes:
|
|
1045
1051
|
field.delete_attribute(name=attr_name)
|
|
1046
|
-
|
|
1052
|
+
deleted = True
|
|
1053
|
+
if get_origin(annotation) == Annotated and any( # pragma: no branch
|
|
1047
1054
|
hasattr(sub_ann, attr_name) for sub_ann in get_args(annotation)
|
|
1048
1055
|
):
|
|
1049
1056
|
for sub_ann in get_args(annotation):
|
|
1050
1057
|
if hasattr(sub_ann, attr_name):
|
|
1051
1058
|
object.__setattr__(sub_ann, attr_name, None)
|
|
1052
|
-
|
|
1059
|
+
deleted = True
|
|
1060
|
+
if not deleted:
|
|
1053
1061
|
raise InvalidGenerationInstructionError(
|
|
1054
1062
|
f'You tried to delete the attribute "{attr_name}" of field "{alter_schema_instruction.name}" '
|
|
1055
1063
|
f'from "{model.name}" in "{version_change_name}" '
|
|
@@ -1058,19 +1066,30 @@ def _delete_field_attributes(
|
|
|
1058
1066
|
|
|
1059
1067
|
|
|
1060
1068
|
def _delete_field_from_model(model: _PydanticModelWrapper, field_name: str, version_change_name: str):
|
|
1061
|
-
if field_name
|
|
1069
|
+
if field_name in model.fields:
|
|
1070
|
+
model.fields.pop(field_name)
|
|
1071
|
+
model.annotations.pop(field_name)
|
|
1072
|
+
for validator_name, validator in model.validators.copy().items():
|
|
1073
|
+
if isinstance(validator, _PerFieldValidatorWrapper) and field_name in validator.fields:
|
|
1074
|
+
validator.fields.remove(field_name)
|
|
1075
|
+
# TODO: This behavior doesn't feel natural
|
|
1076
|
+
if not validator.fields:
|
|
1077
|
+
model.validators[validator_name].is_deleted = True
|
|
1078
|
+
|
|
1079
|
+
elif (
|
|
1080
|
+
field_name in model.validators
|
|
1081
|
+
and isinstance(model.validators[field_name], _ValidatorWrapper)
|
|
1082
|
+
and hasattr(model.validators[field_name], "decorator")
|
|
1083
|
+
and model.validators[field_name].decorator == pydantic.computed_field
|
|
1084
|
+
):
|
|
1085
|
+
validator = model.validators[field_name]
|
|
1086
|
+
model.validators[field_name].is_deleted = True
|
|
1087
|
+
model.annotations.pop(field_name, None)
|
|
1088
|
+
else:
|
|
1062
1089
|
raise InvalidGenerationInstructionError(
|
|
1063
1090
|
f'You tried to delete a field "{field_name}" from "{model.name}" '
|
|
1064
1091
|
f'in "{version_change_name}" but it doesn\'t have such a field.',
|
|
1065
1092
|
)
|
|
1066
|
-
model.fields.pop(field_name)
|
|
1067
|
-
model.annotations.pop(field_name)
|
|
1068
|
-
for validator_name, validator in model.validators.copy().items():
|
|
1069
|
-
if isinstance(validator, _PerFieldValidatorWrapper) and field_name in validator.fields:
|
|
1070
|
-
validator.fields.remove(field_name)
|
|
1071
|
-
# TODO: This behavior doesn't feel natural
|
|
1072
|
-
if not validator.fields:
|
|
1073
|
-
model.validators[validator_name].is_deleted = True
|
|
1074
1093
|
|
|
1075
1094
|
|
|
1076
1095
|
class _DummyEnum(Enum):
|
|
@@ -1100,7 +1119,7 @@ class _EnumWrapper(Generic[_T_ENUM]):
|
|
|
1100
1119
|
for attr_name, attr in initialization_namespace.items():
|
|
1101
1120
|
enum_dict[attr_name] = attr
|
|
1102
1121
|
enum_dict["__doc__"] = self.cls.__doc__
|
|
1103
|
-
model_copy = cast(type[_T_ENUM], type(self.name, self.cls.__bases__, enum_dict))
|
|
1122
|
+
model_copy = cast("type[_T_ENUM]", type(self.name, self.cls.__bases__, enum_dict))
|
|
1104
1123
|
model_copy.__cadwyn_original_model__ = self.cls # pyright: ignore[reportAttributeAccessIssue]
|
|
1105
1124
|
return model_copy
|
|
1106
1125
|
|
|
@@ -140,7 +140,7 @@ def convert_request_to_next_version_for(
|
|
|
140
140
|
if isinstance(schema_or_path, str):
|
|
141
141
|
return _AlterRequestByPathInstruction(
|
|
142
142
|
path=schema_or_path,
|
|
143
|
-
methods=set(cast(list, methods_or_second_schema)),
|
|
143
|
+
methods=set(cast("list", methods_or_second_schema)),
|
|
144
144
|
transformer=transformer,
|
|
145
145
|
)
|
|
146
146
|
else:
|
|
@@ -214,7 +214,7 @@ def convert_response_to_previous_version_for(
|
|
|
214
214
|
# The validation above checks that methods is not None
|
|
215
215
|
return _AlterResponseByPathInstruction(
|
|
216
216
|
path=schema_or_path,
|
|
217
|
-
methods=set(cast(list, methods_or_second_schema)),
|
|
217
|
+
methods=set(cast("list", methods_or_second_schema)),
|
|
218
218
|
transformer=transformer,
|
|
219
219
|
migrate_http_errors=migrate_http_errors,
|
|
220
220
|
)
|
|
@@ -246,7 +246,7 @@ class AlterFieldInstructionFactory:
|
|
|
246
246
|
info: Union[FieldInfo, Any, None] = None,
|
|
247
247
|
) -> FieldExistedAsInstruction:
|
|
248
248
|
if info is None:
|
|
249
|
-
info = cast(FieldInfo, Field())
|
|
249
|
+
info = cast("FieldInfo", Field())
|
|
250
250
|
info.annotation = type
|
|
251
251
|
return FieldExistedAsInstruction(is_hidden_from_changelog=False, schema=self.schema, name=self.name, field=info)
|
|
252
252
|
|
|
@@ -317,7 +317,7 @@ class AlterSchemaInstructionFactory:
|
|
|
317
317
|
def validator(
|
|
318
318
|
self, func: "Union[Callable[..., Any], classmethod[Any, Any, Any], PydanticDescriptorProxy]", /
|
|
319
319
|
) -> AlterValidatorInstructionFactory:
|
|
320
|
-
func = cast(Union[Callable[..., Any], PydanticDescriptorProxy], unwrap_wrapped_function(func))
|
|
320
|
+
func = cast("Union[Callable[..., Any], PydanticDescriptorProxy]", unwrap_wrapped_function(func))
|
|
321
321
|
|
|
322
322
|
if not isinstance(func, PydanticDescriptorProxy):
|
|
323
323
|
if hasattr(func, "__self__"):
|
|
@@ -620,7 +620,7 @@ class VersionBundle:
|
|
|
620
620
|
detail = response_info.body
|
|
621
621
|
if detail is None:
|
|
622
622
|
detail = http.HTTPStatus(response_info.status_code).phrase
|
|
623
|
-
raised_exception.detail = cast(str, detail)
|
|
623
|
+
raised_exception.detail = cast("str", detail)
|
|
624
624
|
raised_exception.headers = dict(response_info.headers)
|
|
625
625
|
raised_exception.status_code = response_info.status_code
|
|
626
626
|
|
|
@@ -408,6 +408,31 @@ print(BulkCreateUsersRequestBody is BulkCreateUsersResponseBody) # False
|
|
|
408
408
|
|
|
409
409
|
or to specify migrations using [endpoint path](#path-based-migration-specification) instead of a schema.
|
|
410
410
|
|
|
411
|
+
## Dependency re-execution warning
|
|
412
|
+
|
|
413
|
+
Notice that whenever a request comes to Cadwyn, we first validate it against the request's version of the schema, then we migrate it to the latest version, and then validate again to prevent migrations from creating invalid requests.
|
|
414
|
+
|
|
415
|
+
This means that if you have a dependency that is executed during the request validation, **it will be executed twice**. For example, if you have a dependency that checks whether a user exists in the database, it will be executed twice. This is not a problem if the dependency is idempotent but it is a problem if it is not.
|
|
416
|
+
|
|
417
|
+
To solve this problem, you can use the `cadwyn.current_dependency_solver` dependency which tell you whether your dependency is getting called before or after the request is migrated. 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"`.
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
from cadwyn import current_dependency_solver
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def my_dependency(
|
|
424
|
+
dependency_solver: Annotated[
|
|
425
|
+
Literal["fastapi", "cadwyn"], Depends(current_dependency_solver)
|
|
426
|
+
]
|
|
427
|
+
):
|
|
428
|
+
if dependency_solver == "fastapi": # Before migration
|
|
429
|
+
...
|
|
430
|
+
else: # After migration
|
|
431
|
+
...
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
but the majority of your dependencies will not need this as most dependencies should not have side effects or network calls within them.
|
|
435
|
+
|
|
411
436
|
## Version changes with side effects
|
|
412
437
|
|
|
413
438
|
Sometimes you will use API versioning to handle a breaking change in your **business logic**, not in the schemas themselves. In such cases, it is tempting to add a version check and just follow the new business logic such as:
|
|
@@ -66,7 +66,7 @@ Then you can serve the documentation with `mkdocs serve`
|
|
|
66
66
|
|
|
67
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
68
|
|
|
69
|
-
* Write text in idiomatic
|
|
69
|
+
* Write text in idiomatic English, using simple language
|
|
70
70
|
* Opt for [Oxford commas](https://en.wikipedia.org/wiki/Serial_comma) when listing a series of terms
|
|
71
71
|
* Keep examples simple and self contained
|
|
72
72
|
* Provide links where applicable
|
|
@@ -83,7 +83,8 @@ Let's say that we had a nullable `middle_name` field but we decided that it does
|
|
|
83
83
|
schema(BaseUser)
|
|
84
84
|
.field("middle_name")
|
|
85
85
|
.existed_as(
|
|
86
|
-
type=str | None,
|
|
86
|
+
type=str | None,
|
|
87
|
+
info=Field(description="User's Middle Name", default=None),
|
|
87
88
|
),
|
|
88
89
|
)
|
|
89
90
|
```
|
|
@@ -12,7 +12,7 @@ Production-ready community-driven modern [Stripe-like](https://stripe.com/blog/a
|
|
|
12
12
|
<img src="https://img.shields.io/codecov/c/github/zmievsa/cadwyn?color=%2334D058&logo=codecov" alt="Coverage">
|
|
13
13
|
</a>
|
|
14
14
|
<a href="https://pypi.org/project/cadwyn/" target="_blank">
|
|
15
|
-
<img alt="PyPI" src="https://img.shields.io/pypi/v/cadwyn?color=%2334D058&logo=pypi&label=PyPI
|
|
15
|
+
<img alt="PyPI" src="https://img.shields.io/pypi/v/cadwyn?color=%2334D058&logo=pypi&label=PyPI" alt="Package version">
|
|
16
16
|
</a>
|
|
17
17
|
<a href="https://pypi.org/project/cadwyn/" target="_blank">
|
|
18
18
|
<img src="https://img.shields.io/pypi/pyversions/cadwyn?color=%2334D058&logo=python" alt="Supported Python versions">
|
|
@@ -24,21 +24,15 @@ Production-ready community-driven modern [Stripe-like](https://stripe.com/blog/a
|
|
|
24
24
|
|
|
25
25
|
## Who is this for?
|
|
26
26
|
|
|
27
|
-
Cadwyn allows you to
|
|
27
|
+
Cadwyn allows you to maintain the implementation just for your newest API version and get all the older versions generated automatically. You keep API backward compatibility encapsulated in small and independent "version change" modules while your business logic stays simple and knows nothing about versioning.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
This [approach](https://docs.cadwyn.dev/theory/how_we_got_here/#ii-migration-based-response-building) may be useful if you want to:
|
|
30
30
|
|
|
31
31
|
1. Support many API versions for a long time
|
|
32
|
-
2.
|
|
32
|
+
2. Have features and bugfixes automatically backported to older API versions
|
|
33
33
|
|
|
34
34
|
Whether you are a newbie in API versioning, a pro looking for a sophisticated tool, an experimenter looking to build a similar framework, or even someone who just wants to learn about all approaches to API versioning -- Cadwyn has the functionality, theory, and documentation to cover all the mentioned use cases.
|
|
35
35
|
|
|
36
36
|
## Get started
|
|
37
37
|
|
|
38
38
|
It is recommended to read the [quickstart tutorial](https://docs.cadwyn.dev/quickstart/setup/) first to get your feet wet with Cadwyn's approach
|
|
39
|
-
|
|
40
|
-
## Sponsors
|
|
41
|
-
|
|
42
|
-
These are our gorgeous sponsors. They are using Cadwyn and are sponsoring it through various means. Contact [me](https://github.com/zmievsa) if you would like to become one too!
|
|
43
|
-
|
|
44
|
-
[](https://docs.monite.com/)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# How to build a versioning framework
|
|
2
|
+
|
|
3
|
+
## Questions to ask yourself when rating your framework
|
|
4
|
+
|
|
5
|
+
### How easy is it to create a version?
|
|
6
|
+
|
|
7
|
+
If it is too easy, it is probably a trap. The framework is probably hiding too much complexity from you and will shoot you in the back later. For example, early on we tried a simple "copy the entire business logic into a separate directory" approach which made it so simple to add new versions. We added too many of them in the end, thus it got hellishly hard to maintain or get rid of these versions.
|
|
8
|
+
|
|
9
|
+
### How easy is it to delete an old version?
|
|
10
|
+
|
|
11
|
+
Your framework must be able to let you clean up versions as simply as possible and cheaply whenever you need to. For example, if your framework tries to minimize the amount of code duplication in your repository by having new routes include old routes within them and new business logic inherit from classes from old business logic, then deleting an old version is going to be painful; oftentimes even dangerous as versions can quickly start interacting with each other in all sorts of ways, turning a single small application into a set of interconnected applications.
|
|
12
|
+
|
|
13
|
+
### How easy is it to see the differences between versions?
|
|
14
|
+
|
|
15
|
+
The easier it is, the better off our users are.
|
|
16
|
+
|
|
17
|
+
### What exactly do you need to duplicate to create a new version?
|
|
18
|
+
|
|
19
|
+
The less we duplicate and maintain by hand, the easier it is to support. However, the less we duplicate, the more chance there is to break the old versions with new releases.
|
|
20
|
+
|
|
21
|
+
### How easy is it to notice accidental data versioning?
|
|
22
|
+
|
|
23
|
+
Data versioning is an incredibly big issue when versioning your API.
|
|
24
|
+
So if your framework makes it hard to version data -- it's really good!
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Literature
|
|
2
2
|
|
|
3
|
-
During Cadwyn's development, I went through countless resources on API Versioning. The following are the most unique and effective ones I
|
|
3
|
+
During Cadwyn's development, I went through countless resources on API Versioning. The following are the most unique and effective ones I managed to find.
|
|
4
4
|
|
|
5
5
|
## Cadwyn-like API Versioning
|
|
6
6
|
|