cadwyn 5.4.3__tar.gz → 5.4.5__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.3 → cadwyn-5.4.5}/CHANGELOG.md +14 -8
- {cadwyn-5.4.3 → cadwyn-5.4.5}/PKG-INFO +1 -24
- {cadwyn-5.4.3 → cadwyn-5.4.5}/README.md +0 -23
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/changelogs.py +13 -9
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/schema_generation.py +29 -8
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/endpoints.py +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/versions.py +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/api_version_parameter.md +6 -6
- cadwyn-5.4.5/docs/concepts/beware_of_data_versioning.md +9 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/changelogs.md +3 -3
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/cli.md +2 -2
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/endpoint_migrations.md +9 -9
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/enum_migrations.md +2 -2
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/index.md +2 -2
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/main_app.md +4 -4
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/methodology.md +2 -2
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/schema_generation.md +5 -4
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/schema_migrations.md +12 -13
- cadwyn-5.4.5/docs/concepts/testing.md +35 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/concepts/version_changes.md +79 -73
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/home/CONTRIBUTING.md +0 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/add_field.md +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/changing_constraints.md +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/remove_field.md +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/quickstart/tutorial.md +5 -5
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/theory/how_to_build_versioning_framework.md +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/pyproject.toml +1 -1
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_router_generation.py +3 -3
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema_field.py +79 -1
- cadwyn-5.4.5/uv.lock +2151 -0
- cadwyn-5.4.3/.all-contributorsrc +0 -4
- cadwyn-5.4.3/docs/concepts/beware_of_data_versioning.md +0 -9
- cadwyn-5.4.3/docs/concepts/testing.md +0 -35
- cadwyn-5.4.3/uv.lock +0 -1792
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.gitignore +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/LICENSE +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/Makefile +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/__main__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/_asts.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/_importer.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/_internal/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/_internal/context_vars.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/_render.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/_utils.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/applications.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/dependencies.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/exceptions.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/middleware.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/py.typed +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/route_generation.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/routing.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/CNAME +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/index.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/logos/cadwyn_icon_transparent.svg +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/logos/cadwyn_transparent.svg +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/logos/cadwyn_with_background.svg +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/index.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/plugin.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/stylesheets/extra.css +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs/theory/literature.md +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/block001.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/how_to/version_with_path_and_numbers_instead_of_headers_and_dates/tests/test_block_001.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/docs_src/ruff.toml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/mkdocs.yml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/ruff.toml +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/scripts/fix_links.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/scripts/split_md.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_data/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/utils.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/conftest.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_applications.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_changelog.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_cli.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_render.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_routing.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/test_structure.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/tutorial/main.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.4.3 → cadwyn-5.4.5}/tox.ini +0 -0
|
@@ -5,6 +5,12 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [5.4.4]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
* Fixed KeyError when versioning Pydantic models containing ClassVar annotations
|
|
13
|
+
|
|
8
14
|
## [5.4.3]
|
|
9
15
|
|
|
10
16
|
### Fixed
|
|
@@ -86,17 +92,17 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
86
92
|
* `__doc__` attribute is now copied from the original enum to the generated enum
|
|
87
93
|
* Python 3.12 type aliases and their typing_extensions backports are now supported (including `pydantic.JsonValue` and other `typing_extensions.TypeAliasType` instances)
|
|
88
94
|
* The bug when solve_dependencies error on the migration of a request to the latest version responds with a non-json serializable error and cadwyn showed a failed to serialize error instead of the actual error
|
|
89
|
-
* Updated minimum
|
|
95
|
+
* Updated minimum FastAPI version to 0.112.4 because embed_body_fields was added in 0.112.4
|
|
90
96
|
|
|
91
97
|
## [5.1.2]
|
|
92
98
|
|
|
93
99
|
### Fixed
|
|
94
100
|
|
|
95
|
-
* Generators not being called when
|
|
101
|
+
* Generators not being called when FastAPI validates the initial request
|
|
96
102
|
|
|
97
103
|
### Added
|
|
98
104
|
|
|
99
|
-
* `cadwyn.current_dependency_solver` function to check whether cadwyn or
|
|
105
|
+
* `cadwyn.current_dependency_solver` function to check whether cadwyn or FastAPI is currently solving dependencies. It can be used as a dependency for versioned endpoints like so: `current_dependency_solver: Annotated[Literal["fastapi", "cadwyn"], Depends(current_dependency_solver)]`. If your dependency has side effects, you would likely want to only run it once per request. If you want to run it once we migrated the request to the latest version, you should only run it when `current_dependency_solver` is `"cadwyn"`. If you want your dependency to run at the very beginning of handling the request, you should only run it when `current_dependency_solver` is `"fastapi"`.
|
|
100
106
|
|
|
101
107
|
## [5.1.1]
|
|
102
108
|
|
|
@@ -303,7 +309,7 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
303
309
|
|
|
304
310
|
### Changed
|
|
305
311
|
|
|
306
|
-
* `Cadwyn.enrich_swagger` is now completely unnecessary:
|
|
312
|
+
* `Cadwyn.enrich_swagger` is now completely unnecessary: OpenAPI is now generated at runtime. It also now does not do anything, is deprecated, and will be removed in a future version
|
|
307
313
|
|
|
308
314
|
### Fixed
|
|
309
315
|
|
|
@@ -324,7 +330,7 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
324
330
|
|
|
325
331
|
### Fixed
|
|
326
332
|
|
|
327
|
-
*
|
|
333
|
+
* OpenAPI not being generated when lifespan was used
|
|
328
334
|
|
|
329
335
|
## [3.15.1]
|
|
330
336
|
|
|
@@ -449,7 +455,7 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
449
455
|
|
|
450
456
|
### Fixed
|
|
451
457
|
|
|
452
|
-
* When a class-based dependency from **fastapi** was used (anything security related), FastAPI had hardcoded `isinstance` checks for it which it used to enrich swagger with functionality. But when the dependencies were wrapped into our function wrappers, these checks stopped passing, thus breaking this functionality in swagger. Now we ignore all dependencies that FastAPI creates. This also introduces a hard-to-solve bug: if
|
|
458
|
+
* When a class-based dependency from **fastapi** was used (anything security related), FastAPI had hardcoded `isinstance` checks for it which it used to enrich swagger with functionality. But when the dependencies were wrapped into our function wrappers, these checks stopped passing, thus breaking this functionality in swagger. Now we ignore all dependencies that FastAPI creates. This also introduces a hard-to-solve bug: if FastAPI's class-based security dependency was subclassed and then `__call__` was overridden with new dependencies that are versioned -- we will not migrate them from version to version. I hope this is an extremely rare use case though. In fact, such use case breaks Liskov Substitution Principle and doesn't make much sense because security classes already include `request` parameter which means that no extra dependencies or parameters are necessary.
|
|
453
459
|
|
|
454
460
|
## [3.6.5]
|
|
455
461
|
|
|
@@ -707,13 +713,13 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
707
713
|
|
|
708
714
|
### Fixed
|
|
709
715
|
|
|
710
|
-
* Custom body fields created by
|
|
716
|
+
* Custom body fields created by FastAPI caused an exception. Now they are ignored
|
|
711
717
|
|
|
712
718
|
## [2.0.2]
|
|
713
719
|
|
|
714
720
|
### Added
|
|
715
721
|
|
|
716
|
-
* A link to
|
|
722
|
+
* A link to OpenAPI discussion on enum expansion into docs/recipes
|
|
717
723
|
* A link to intercom's API versioning article into docs/theory
|
|
718
724
|
|
|
719
725
|
## [2.0.1]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 5.4.
|
|
3
|
+
Version: 5.4.5
|
|
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
|
|
@@ -88,26 +88,3 @@ The [documentation](https://docs.cadwyn.dev) has everything you need to succeed.
|
|
|
88
88
|
## Sponsors
|
|
89
89
|
|
|
90
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!
|
|
91
|
-
|
|
92
|
-
## Contributors
|
|
93
|
-
|
|
94
|
-
<details>
|
|
95
|
-
|
|
96
|
-
<summary>Thanks goes to these wonderful people:</summary>
|
|
97
|
-
<a href="https://allcontributors.org/docs/en/emoji-key">Emoji Key </a>
|
|
98
|
-
|
|
99
|
-
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
100
|
-
<!-- prettier-ignore-start -->
|
|
101
|
-
<!-- markdownlint-disable -->
|
|
102
|
-
|
|
103
|
-
<!-- markdownlint-restore -->
|
|
104
|
-
<!-- prettier-ignore-end -->
|
|
105
|
-
|
|
106
|
-
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
107
|
-
|
|
108
|
-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
|
|
109
|
-
Contributions are welcome!
|
|
110
|
-
|
|
111
|
-
</details>
|
|
112
|
-
|
|
113
|
-
<!-- contributors-end -->
|
|
@@ -40,26 +40,3 @@ The [documentation](https://docs.cadwyn.dev) has everything you need to succeed.
|
|
|
40
40
|
## Sponsors
|
|
41
41
|
|
|
42
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
|
-
## Contributors
|
|
45
|
-
|
|
46
|
-
<details>
|
|
47
|
-
|
|
48
|
-
<summary>Thanks goes to these wonderful people:</summary>
|
|
49
|
-
<a href="https://allcontributors.org/docs/en/emoji-key">Emoji Key </a>
|
|
50
|
-
|
|
51
|
-
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
52
|
-
<!-- prettier-ignore-start -->
|
|
53
|
-
<!-- markdownlint-disable -->
|
|
54
|
-
|
|
55
|
-
<!-- markdownlint-restore -->
|
|
56
|
-
<!-- prettier-ignore-end -->
|
|
57
|
-
|
|
58
|
-
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
59
|
-
|
|
60
|
-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
|
|
61
|
-
Contributions are welcome!
|
|
62
|
-
|
|
63
|
-
</details>
|
|
64
|
-
|
|
65
|
-
<!-- contributors-end -->
|
|
@@ -2,14 +2,8 @@ import copy
|
|
|
2
2
|
import sys
|
|
3
3
|
from enum import auto
|
|
4
4
|
from logging import getLogger
|
|
5
|
-
from typing import Any, Literal, TypeVar, Union, cast, get_args
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union, cast, get_args
|
|
6
6
|
|
|
7
|
-
from fastapi._compat import (
|
|
8
|
-
GenerateJsonSchema,
|
|
9
|
-
ModelField,
|
|
10
|
-
get_compat_model_name_map,
|
|
11
|
-
get_definitions,
|
|
12
|
-
)
|
|
13
7
|
from fastapi.openapi.constants import REF_TEMPLATE
|
|
14
8
|
from fastapi.openapi.utils import (
|
|
15
9
|
get_fields_from_routes,
|
|
@@ -41,6 +35,9 @@ from .structure.schemas import (
|
|
|
41
35
|
ValidatorExistedInstruction,
|
|
42
36
|
)
|
|
43
37
|
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from fastapi._compat import ModelField
|
|
40
|
+
|
|
44
41
|
if sys.version_info >= (3, 11): # pragma: no cover
|
|
45
42
|
from enum import StrEnum
|
|
46
43
|
else: # pragma: no cover
|
|
@@ -120,7 +117,7 @@ def _get_affected_model_names(
|
|
|
120
117
|
FieldDidntHaveInstruction,
|
|
121
118
|
],
|
|
122
119
|
generator_from_newer_version: SchemaGenerator,
|
|
123
|
-
schemas_from_last_version: list[ModelField],
|
|
120
|
+
schemas_from_last_version: "list[ModelField]",
|
|
124
121
|
):
|
|
125
122
|
changed_model = generator_from_newer_version._get_wrapper_for_model(instruction.schema)
|
|
126
123
|
annotations = [model.field_info.annotation for model in schemas_from_last_version]
|
|
@@ -156,6 +153,13 @@ def _get_all_pydantic_models_from_generic(annotation: Any) -> list[type[BaseMode
|
|
|
156
153
|
|
|
157
154
|
|
|
158
155
|
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
|
+
|
|
159
163
|
class CadwynDummyModelForRepresentation(BaseModel):
|
|
160
164
|
my_field: model
|
|
161
165
|
|
|
@@ -315,7 +319,7 @@ def _convert_version_change_instruction_to_changelog_entry( # noqa: C901
|
|
|
315
319
|
version_change: type[VersionChange],
|
|
316
320
|
generator_from_newer_version: SchemaGenerator,
|
|
317
321
|
generator_from_older_version: SchemaGenerator,
|
|
318
|
-
schemas_from_older_version: list[ModelField],
|
|
322
|
+
schemas_from_older_version: "list[ModelField]",
|
|
319
323
|
routes_from_newer_version: list[APIRoute],
|
|
320
324
|
):
|
|
321
325
|
if isinstance(instruction, EndpointDidntExistInstruction):
|
|
@@ -14,6 +14,7 @@ from functools import cache
|
|
|
14
14
|
from typing import (
|
|
15
15
|
TYPE_CHECKING,
|
|
16
16
|
Annotated,
|
|
17
|
+
ClassVar,
|
|
17
18
|
Generic,
|
|
18
19
|
Union,
|
|
19
20
|
_BaseGenericAlias, # pyright: ignore[reportAttributeAccessIssue]
|
|
@@ -41,6 +42,7 @@ from pydantic._internal._decorators import (
|
|
|
41
42
|
from pydantic._internal._known_annotated_metadata import collect_known_metadata
|
|
42
43
|
from pydantic._internal._typing_extra import try_eval_type as pydantic_try_eval_type
|
|
43
44
|
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
|
45
|
+
from pydantic_core import PydanticUndefined
|
|
44
46
|
from typing_extensions import (
|
|
45
47
|
Any,
|
|
46
48
|
Doc,
|
|
@@ -316,7 +318,7 @@ def _wrap_pydantic_model(model: type[_T_PYDANTIC_MODEL]) -> "_PydanticModelWrapp
|
|
|
316
318
|
field_name,
|
|
317
319
|
)
|
|
318
320
|
for field_name in model.__annotations__
|
|
319
|
-
if field_name in defined_fields
|
|
321
|
+
if field_name in defined_fields and field_name in model.model_fields
|
|
320
322
|
}
|
|
321
323
|
|
|
322
324
|
main_attributes = fields | validators
|
|
@@ -539,7 +541,7 @@ class _AsyncGeneratorCallableWrapper(_CallableWrapper):
|
|
|
539
541
|
class _AnnotationTransformer:
|
|
540
542
|
def __init__(self, generator: "SchemaGenerator") -> None:
|
|
541
543
|
# This cache is not here for speeding things up. It's for preventing the creation of copies of the same object
|
|
542
|
-
# because such copies could produce weird behaviors at runtime, especially if you/
|
|
544
|
+
# because such copies could produce weird behaviors at runtime, especially if you/FastAPI do any comparisons.
|
|
543
545
|
# It's defined here and not on the method because of this: https://youtu.be/sVjtp6tGo0g
|
|
544
546
|
self.generator = generator
|
|
545
547
|
# TODO: Rewrite this to memoize
|
|
@@ -592,7 +594,12 @@ class _AnnotationTransformer:
|
|
|
592
594
|
from typing_inspection.typing_objects import is_any, is_newtype, is_typealiastype
|
|
593
595
|
|
|
594
596
|
if isinstance(annotation, (types.GenericAlias, _BaseGenericAlias)):
|
|
595
|
-
|
|
597
|
+
origin = get_origin(annotation)
|
|
598
|
+
args = get_args(annotation)
|
|
599
|
+
# Classvar does not support generic tuple arguments
|
|
600
|
+
if origin is ClassVar:
|
|
601
|
+
return ClassVar[self.change_version_of_annotation(args[0])]
|
|
602
|
+
return origin[tuple(self.change_version_of_annotation(arg) for arg in get_args(annotation))]
|
|
596
603
|
elif is_typealiastype(annotation):
|
|
597
604
|
if (
|
|
598
605
|
annotation.__module__ is not None and (annotation.__module__.startswith("pydantic."))
|
|
@@ -948,11 +955,20 @@ def _add_field_to_model(
|
|
|
948
955
|
f'in "{version_change_name}" but there is already a field with that name.',
|
|
949
956
|
)
|
|
950
957
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
958
|
+
# Special handling for ClassVar fields
|
|
959
|
+
if get_origin(alter_schema_instruction.field.annotation) is ClassVar:
|
|
960
|
+
# ClassVar fields should not be in model.fields, only in annotations and other_attributes
|
|
961
|
+
model.annotations[alter_schema_instruction.name] = alter_schema_instruction.field.annotation
|
|
962
|
+
# Set the actual ClassVar value in other_attributes
|
|
963
|
+
if alter_schema_instruction.field.default is not PydanticUndefined:
|
|
964
|
+
model.other_attributes[alter_schema_instruction.name] = alter_schema_instruction.field.default
|
|
965
|
+
else:
|
|
966
|
+
# Regular field handling
|
|
967
|
+
field = PydanticFieldWrapper(
|
|
968
|
+
alter_schema_instruction.field, alter_schema_instruction.field.annotation, alter_schema_instruction.name
|
|
969
|
+
)
|
|
970
|
+
model.fields[alter_schema_instruction.name] = field
|
|
971
|
+
model.annotations[alter_schema_instruction.name] = alter_schema_instruction.field.annotation
|
|
956
972
|
|
|
957
973
|
|
|
958
974
|
def _change_field_in_model(
|
|
@@ -1085,6 +1101,11 @@ def _delete_field_from_model(model: _PydanticModelWrapper, field_name: str, vers
|
|
|
1085
1101
|
validator = model.validators[field_name]
|
|
1086
1102
|
model.validators[field_name].is_deleted = True
|
|
1087
1103
|
model.annotations.pop(field_name, None)
|
|
1104
|
+
elif field_name in model.annotations and get_origin(model.annotations[field_name]) is ClassVar:
|
|
1105
|
+
# Handle ClassVar fields - they exist in annotations but not in model.fields
|
|
1106
|
+
model.annotations.pop(field_name)
|
|
1107
|
+
# Also remove the attribute from other_attributes if it exists there
|
|
1108
|
+
model.other_attributes.pop(field_name, None)
|
|
1088
1109
|
else:
|
|
1089
1110
|
raise InvalidGenerationInstructionError(
|
|
1090
1111
|
f'You tried to delete a field "{field_name}" from "{model.name}" '
|
|
@@ -18,7 +18,7 @@ HTTP_METHODS = {"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"}
|
|
|
18
18
|
|
|
19
19
|
@dataclass(**DATACLASS_SLOTS)
|
|
20
20
|
class EndpointAttributesPayload:
|
|
21
|
-
#
|
|
21
|
+
# FastAPI API routes also have "endpoint" and "dependency_overrides_provider" fields.
|
|
22
22
|
# We do not use them because:
|
|
23
23
|
# 1. "endpoint" must not change -- otherwise this versioning is doomed
|
|
24
24
|
# 2. "dependency_overrides_provider" is taken from router's attributes
|
|
@@ -694,7 +694,7 @@ class VersionBundle:
|
|
|
694
694
|
# We use this instead of `.body()` to automatically guess body type and load the correct body, even if it's a form
|
|
695
695
|
async def _get_body(
|
|
696
696
|
request: FastapiRequest, body_field: Union[ModelField, None], exit_stack: AsyncExitStack
|
|
697
|
-
): # pragma: no cover # This is from
|
|
697
|
+
): # pragma: no cover # This is from FastAPI
|
|
698
698
|
is_body_form = body_field and isinstance(body_field.field_info, params.Form)
|
|
699
699
|
try:
|
|
700
700
|
body: Any = None
|
|
@@ -63,7 +63,7 @@ app = Cadwyn(
|
|
|
63
63
|
)
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
In
|
|
66
|
+
In the example above only dates will be accepted as valid versions.
|
|
67
67
|
|
|
68
68
|
You can also use an arbitrary string:
|
|
69
69
|
|
|
@@ -80,21 +80,21 @@ app = Cadwyn(
|
|
|
80
80
|
)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
In
|
|
83
|
+
In the example above any string will be accepted as a valid version. Arbitrary strings can be used, Cadwyn will not sort them. Cadwyn will assume their actual order matches the order of the versions in the `VersionBundle`.
|
|
84
84
|
|
|
85
85
|
### API Version waterfalling
|
|
86
86
|
|
|
87
87
|
For historical reasons, date-based routing also supports waterfalling the requests to the closest earlier version of the API if the request date parameter doesn't match any of the versions exactly.
|
|
88
88
|
|
|
89
|
-
If the app has two versions: 2022-01-02 and 2022-01-05, and the request date parameter is 2022-01-03, then the request will be routed to 2022-01-02 version as it the closest version, but lower than the request date parameter.
|
|
89
|
+
If the app has two versions: 2022-01-02 and 2022-01-05, and the request date parameter is 2022-01-03, then the request will be routed to the 2022-01-02 version, as it is the closest version, but lower than the request date parameter.
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
An exact match is always preferred to a partial match and a request will never be matched to the higher-versioned route.
|
|
92
92
|
|
|
93
|
-
We implement routing like this because Cadwyn was born in a microservice architecture and it is extremely convenient to have waterfalling there. For example, imagine that you have two Cadwyn services: Payables and Receivables, each defining its own API versions. Payables service might contain 10 versions while
|
|
93
|
+
We implement routing like this because Cadwyn was born in a microservice architecture and it is extremely convenient to have waterfalling there. For example, imagine that you have two Cadwyn services: Payables and Receivables, each defining its own API versions. Payables service might contain 10 versions while Receivables service might contain only 2 versions because it didn't need as many breaking changes. If a client requests a version that does not exist in Receivables, we will just waterfall to some earlier version, making Receivables behavior consistent even if API keeps getting new versions.
|
|
94
94
|
|
|
95
95
|
## API Version Parameter Title and Description
|
|
96
96
|
|
|
97
|
-
You can pass a title and/or a description to `Cadwyn` constructor.
|
|
97
|
+
You can pass a title and/or a description to the `Cadwyn` constructor. It is equivalent to passing `title` and `description` to `fastapi.Path` or `fastapi.Header` constructors.
|
|
98
98
|
|
|
99
99
|
```python
|
|
100
100
|
app = Cadwyn(
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Beware of data versioning
|
|
2
|
+
|
|
3
|
+
Oftentimes you will want to introduce a breaking change where one of the following is true:
|
|
4
|
+
|
|
5
|
+
* Old data cannot be automatically converted to the structure of the new response
|
|
6
|
+
* New response cannot be automatically migrated to an older response
|
|
7
|
+
* Old request cannot be automatically converted to the HEAD request
|
|
8
|
+
|
|
9
|
+
This means that you are not versioning your API, you are versioning your **data**. This cannot be solved by an API versioning framework. It also makes it incredibly hard to version as you now cannot guarantee compatibility between versions. Avoid this at all costs -- all your API versions must be compatible between each other. Data versioning is not a result of a complicated use case, it is a result of **errors** when divising a new version. I am yet to meet a single case where data versioning is the right way to solve an API versioning problem.
|
|
@@ -27,11 +27,11 @@ class CompletelyHiddenVersionChange(VersionChange):
|
|
|
27
27
|
|
|
28
28
|
## Customizing changelog endpoint
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
The changelog endpoint name can be customized by specifying a new name via the `changelog_url` argument to the `Cadwyn()` constructor. Accessing this url via the `GET` request will return the changelog for all versions based on the content of your `VersionBundle`.
|
|
31
31
|
|
|
32
|
-
If you want to hide the changelog endpoint, pass `include_changelog_url_in_schema=False` to `Cadwyn`.
|
|
32
|
+
If you want to hide the changelog endpoint, pass `include_changelog_url_in_schema=False` to `Cadwyn()`.
|
|
33
33
|
|
|
34
|
-
If you want to delete the changelog endpoint, pass `changelog_url=None` to `Cadwyn`.
|
|
34
|
+
If you want to delete the changelog endpoint, pass `changelog_url=None` to `Cadwyn()`.
|
|
35
35
|
|
|
36
36
|
## Changelog structure and entry types
|
|
37
37
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# CLI
|
|
2
2
|
|
|
3
|
-
Cadwyn has an optional CLI
|
|
4
|
-
You can run `cadwyn --version` to check current version of Cadwyn.
|
|
3
|
+
Cadwyn has an optional CLI that can be installed with `pip install 'cadwyn[standard]'`.
|
|
4
|
+
You can run `cadwyn --version` to check the current version of Cadwyn.
|
|
5
5
|
|
|
6
6
|
Its main purpose is [rendering generated schemas](./schema_generation.md#rendering-schemas).
|
|
7
7
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Endpoint migrations
|
|
2
2
|
|
|
3
|
-
Note that the `endpoint(...)` constructor contains a second argument that describes the methods of the endpoints you would like to edit. If you have two routes for a single endpoint and you put both of their methods into the instruction -- both of them
|
|
3
|
+
Note that the `endpoint(...)` constructor contains a second argument that describes the methods of the endpoints you would like to edit. If you have two routes for a single endpoint and you put both of their methods into the instruction -- as expected, both of them will be changed.
|
|
4
4
|
|
|
5
|
-
## Defining endpoints that didn't exist
|
|
5
|
+
## Defining endpoints that didn't exist for new versions
|
|
6
6
|
|
|
7
7
|
If you had an endpoint in an old version **but want to delete it in a new version**, you must still define it as usual with all your other endpoints but mark it as deleted.
|
|
8
8
|
|
|
@@ -43,7 +43,7 @@ class MyChange(VersionChange):
|
|
|
43
43
|
|
|
44
44
|
## Changing endpoint attributes
|
|
45
45
|
|
|
46
|
-
If you want to change any attribute of your endpoint in a new version, you can return the attribute's value in all older versions like
|
|
46
|
+
If you want to change any attribute of your endpoint in a new version, you can return the attribute's value in all older versions like this:
|
|
47
47
|
|
|
48
48
|
```python
|
|
49
49
|
from cadwyn import VersionChange, endpoint
|
|
@@ -64,11 +64,11 @@ However, you only need to have a migration if it is a breaking change for your u
|
|
|
64
64
|
|
|
65
65
|
Note that changing endpoint `dependencies` is only going to affect the initial validation. So Cadwyn will take your altered dependencies and run them on each request to the endpoint but ultimately your endpoint code is always going to use the HEAD version of your dependencies. So be careful.
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
Also note that if some of your dependencies were added at app/router level, they **are** going to be overwritten by this instruction. Most of the time it is rather safe, however, as all the necessary dependencies will still run on HEAD version.
|
|
68
68
|
|
|
69
69
|
## Dealing with endpoint duplicates
|
|
70
70
|
|
|
71
|
-
Sometimes, when you're doing some advanced changes in between versions, you
|
|
71
|
+
Sometimes, when you're doing some advanced changes in between versions, you need to rewrite your endpoint function entirely. So essentially you'd have the following structure:
|
|
72
72
|
|
|
73
73
|
```python
|
|
74
74
|
from fastapi.params import Param
|
|
@@ -92,7 +92,7 @@ def get_users_by_name(user_name: Annotated[str, Param()]):
|
|
|
92
92
|
"""Do some logic with user_name"""
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
As you see, these two functions have the same
|
|
95
|
+
As you see, these two functions have the same parameters and path decorators. And when you have many versions, you can have even more functions like these two. So how do we ask cadwyn to restore only one of them and delete the other one?
|
|
96
96
|
|
|
97
97
|
```python
|
|
98
98
|
from cadwyn import VersionChange, endpoint
|
|
@@ -104,8 +104,8 @@ class UseParamsInsteadOfHeadersForUserNameFiltering(VersionChange):
|
|
|
104
104
|
"because using headers is a bad API practice in such scenarios."
|
|
105
105
|
)
|
|
106
106
|
instructions_to_migrate_to_previous_version = (
|
|
107
|
-
# We need to specify the name, otherwise, we will encounter an exception due to
|
|
108
|
-
# with the same
|
|
107
|
+
# We need to specify the name, otherwise, we will encounter an exception due to
|
|
108
|
+
# having two identical endpoints with the same parameters and path decorators
|
|
109
109
|
endpoint(
|
|
110
110
|
"/users",
|
|
111
111
|
["GET"],
|
|
@@ -117,4 +117,4 @@ class UseParamsInsteadOfHeadersForUserNameFiltering(VersionChange):
|
|
|
117
117
|
)
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
So by using a more concrete `func_name`, we are
|
|
120
|
+
So by using a more concrete `func_name`, we are able to distinguish between different functions that affect the same routes.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# Enum migrations
|
|
2
2
|
|
|
3
|
-
All of the following instructions affect only
|
|
3
|
+
All of the following instructions affect only OpenAPI schemas and their initial validation. All of your incoming requests will still be converted into your HEAD schemas.
|
|
4
4
|
|
|
5
5
|
## Adding enum members
|
|
6
6
|
|
|
7
|
-
Note that adding enum members **can** be a breaking change unlike adding optional fields to a schema. For example, if I return a list of entities, each of which has some type, and I add a new type
|
|
7
|
+
Note that adding enum members **can** be a breaking change unlike adding optional fields to a schema. For example, if I return a list of entities, each of which has some type, and I add a new type, then my client's code is likely to break.
|
|
8
8
|
|
|
9
9
|
So I suggest adding enum members in new versions as well.
|
|
10
10
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Concepts
|
|
2
2
|
|
|
3
|
-
This section covers the entirety of features and their rationale in Cadwyn. It can also be used as
|
|
3
|
+
This section covers the entirety of features and their rationale in Cadwyn. It can also be used as reference documentation until we have a proper one. First, let's talk about the reasons for using Cadwyn at all.
|
|
4
4
|
|
|
5
|
-
Cadwyn aims to be the most accurate and sophisticated API Versioning model out there. First of all, you maintain **zero** duplicated code yourself. Usually, in API versioning you [would need to](../theory/how_we_got_here.md) duplicate and maintain at least some layer of your application. It could be the database, business logic, schemas, and endpoints. Cadwyn
|
|
5
|
+
Cadwyn aims to be the most accurate and sophisticated API Versioning model out there. First of all, you maintain **zero** duplicated code yourself. Usually, in API versioning you [would need to](../theory/how_we_got_here.md) duplicate and maintain at least some layer of your application. It could be the database, business logic, schemas, and endpoints. With Cadwyn you avoid duplicating any of that. Internally, it duplicates endpoints and schemas at runtime but none of it becomes your tech debt, none of it becomes your code to support. If you test rigorously, then only [some small subset of your tests](./testing.md) will need to be duplicated when existing functionality is changed between versions.
|
|
6
6
|
|
|
7
7
|
You define your database, business logic, routes, and schemas only once. Then, whenever you release a new API version, you use Cadwyn's [version change DSL](./version_changes.md#version-changes) to describe how to convert your app to the previous version. So your business logic and database stay intact and always represent the latest version while the version changes make sure that your clients can continue using the previous versions without ever needing to update their code.
|
|
8
8
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Main App
|
|
2
2
|
|
|
3
|
-
Cadwyn's standard usage
|
|
3
|
+
Cadwyn's standard usage involves a single customized FastAPI app: `cadwyn.Cadwyn`. It accepts all the same arguments as `FastAPI` does and two more keyword-only arguments:
|
|
4
4
|
|
|
5
5
|
* Required `versions: VersionBundle` describes [all versions](./version_changes.md#versionbundle) within your application
|
|
6
6
|
* Optional `api_version_parameter_name: str = "x_api_version"` is the parameter that Cadwyn will use for [routing](#routing) to different API versions of your app
|
|
7
7
|
|
|
8
|
-
After you have defined
|
|
8
|
+
After you have defined the main app, you can add versioned API routers to it using `Cadwyn.generate_and_include_versioned_routers(*routers)`
|
|
9
9
|
|
|
10
10
|
```python
|
|
11
11
|
from cadwyn import VersionedAPIRouter, Cadwyn
|
|
@@ -33,9 +33,9 @@ That's it! `generate_and_include_versioned_routers` will generate all versions o
|
|
|
33
33
|
|
|
34
34
|
## Routing
|
|
35
35
|
|
|
36
|
-
Cadwyn is built on header-based routing. First, we route requests to the appropriate API version based on the version header (`x-api-version` by default). Then we route by the appropriate url path and method. Currently, Cadwyn only works with ISO date-based versions (such as `2022-11-16`). If the user sends an
|
|
36
|
+
Cadwyn is built on header-based routing. First, we route requests to the appropriate API version based on the version header (`x-api-version` by default). Then we route by the appropriate url path and method. Currently, Cadwyn only works with ISO date-based versions (such as `2022-11-16`). If the user sends a date that does not have an exact match, Cadwyn picks up the closest lower applicable version. For example, `2022-11-16` in request can be matched by `2022-11-15` and `2000-01-01` but cannot be matched by `2022-11-17`.
|
|
37
37
|
|
|
38
|
-
However, header-based routing is
|
|
38
|
+
However, header-based routing is the default way to use Cadwyn. If you want to use any other form of routing, you can use Cadwyn directly through `cadwyn.generate_versioned_routers` or subclass `cadwyn.Cadwyn` to use a different router and middleware. Just remember to update the `VersionBundle.api_version_var` variable each time you route some request to a version. This variable allows Cadwyn to do [side effects](./version_changes.md#version-changes-with-side-effects) and [data migrations](./version_changes.md#data-migrations).
|
|
39
39
|
|
|
40
40
|
### VersionedAPIRouter
|
|
41
41
|
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Cadwyn implements a methodology that is based on the following set of principles:
|
|
4
4
|
|
|
5
|
-
* Each version
|
|
5
|
+
* Each version consists of "version changes" or "compatibility gates" which describe **independent, atomic** differences from the previous version
|
|
6
6
|
* We make a new version if and only if we have breaking changes
|
|
7
7
|
* Versions must have little to no effect on the business logic
|
|
8
8
|
* Versions **must always** be compatible in terms of data
|
|
9
9
|
* Creating new versions is avoided at all costs
|
|
10
|
-
* Any
|
|
10
|
+
* Any backward-compatible features must be backported to all compatible versions
|
|
11
11
|
|
|
12
12
|
These rules give us an ability to have a large number of self-documenting versions while encapsulating their complexity in small version change classes, providing a consistent and stable experience to our users.
|
|
13
13
|
|
|
@@ -4,13 +4,13 @@ Cadwyn automatically generates versioned schemas and everything related to them
|
|
|
4
4
|
|
|
5
5
|
## Rendering schemas
|
|
6
6
|
|
|
7
|
-
When you have many versions and
|
|
7
|
+
When you have many versions and schemas, it is quite challenging to know what validators, fields, and other attributes are defined on each schema in any specific version. To address this issue, there is a way to **render** the generated pydantic models and enums as code using the command-line interface.
|
|
8
8
|
|
|
9
|
-
**NOTICE** that `cadwyn render` does not
|
|
9
|
+
**NOTICE** that `cadwyn render` does not guarantee rendering correct schemas. It is going to be a close enough approximation for manual validation. However, it is not yet ready to be used for production code generation. For example, it doesn't handle schema renamings in class `__bases__` yet.
|
|
10
10
|
|
|
11
11
|
### Rendering a module
|
|
12
12
|
|
|
13
|
-
Here's
|
|
13
|
+
Here's a way to render the entire module with the schemas:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
cadwyn render module data.schemas --app=main:app --version=2024-05-26
|
|
@@ -20,7 +20,7 @@ This command will print to stdout what the schemas would look like in version 20
|
|
|
20
20
|
|
|
21
21
|
### Rendering a single model
|
|
22
22
|
|
|
23
|
-
Here's
|
|
23
|
+
Here's a way to render a single pydantic model or enum with the schemas:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
cadwyn render model data.schemas:UserCreateRequest --app=main:app --version=2024-05-26
|
|
@@ -34,6 +34,7 @@ Cadwyn is capable of generating versioned schemas from its version changes even
|
|
|
34
34
|
|
|
35
35
|
```python
|
|
36
36
|
import cadwyn
|
|
37
|
+
from cadwyn.schema_generation import generate_versioned_models
|
|
37
38
|
from my_versions import version_bundle, MyVersionedSchema
|
|
38
39
|
|
|
39
40
|
schema_generators = generate_versioned_models(version_bundle)
|