cadwyn 5.4.4__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.4 → cadwyn-5.4.5}/CHANGELOG.md +8 -8
- {cadwyn-5.4.4 → cadwyn-5.4.5}/PKG-INFO +1 -24
- {cadwyn-5.4.4 → cadwyn-5.4.5}/README.md +0 -23
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/changelogs.py +13 -9
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/schema_generation.py +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/endpoints.py +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/versions.py +1 -1
- {cadwyn-5.4.4 → 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.4 → cadwyn-5.4.5}/docs/concepts/changelogs.md +3 -3
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/cli.md +2 -2
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/endpoint_migrations.md +9 -9
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/enum_migrations.md +2 -2
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/index.md +2 -2
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/main_app.md +4 -4
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/methodology.md +2 -2
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/schema_generation.md +5 -4
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/schema_migrations.md +12 -13
- cadwyn-5.4.5/docs/concepts/testing.md +35 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/concepts/version_changes.md +79 -73
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/home/CONTRIBUTING.md +0 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/add_field.md +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/change_schema_without_endpoint.md +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/changing_constraints.md +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/remove_field.md +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/quickstart/tutorial.md +5 -5
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/theory/how_to_build_versioning_framework.md +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/pyproject.toml +1 -1
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_router_generation.py +3 -3
- cadwyn-5.4.5/uv.lock +2151 -0
- cadwyn-5.4.4/.all-contributorsrc +0 -4
- cadwyn-5.4.4/docs/concepts/beware_of_data_versioning.md +0 -9
- cadwyn-5.4.4/docs/concepts/testing.md +0 -35
- cadwyn-5.4.4/uv.lock +0 -1792
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/actions/setup-python-uv/action.yaml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/workflows/ci.yaml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/workflows/daily_tests.yaml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.github/workflows/release.yaml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.gitignore +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/.pre-commit-config.yaml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/LICENSE +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/Makefile +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/__main__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/_asts.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/_importer.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/_internal/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/_internal/context_vars.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/_render.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/_utils.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/applications.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/dependencies.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/exceptions.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/middleware.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/py.typed +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/route_generation.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/routing.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/static/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/static/docs.html +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/common.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/data.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/enums.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/CNAME +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/index.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/how_to/version_with_paths_and_numbers_instead_of_headers_and_dates.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/logos/cadwyn_icon_transparent.svg +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/logos/cadwyn_transparent.svg +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/logos/cadwyn_with_background.svg +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/index.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/plugin.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/quickstart/setup.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/stylesheets/extra.css +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs/theory/literature.md +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block001.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/block002.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block001.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/how_to/change_openapi_schemas/change_schema_without_endpoint/tests/test_block002.py +0 -0
- {cadwyn-5.4.4 → 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.4 → 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.4 → 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.4 → 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.4 → cadwyn-5.4.5}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/setup/block001.sh +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/docs_src/ruff.toml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/mkdocs.yml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/ruff.toml +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/scripts/fix_links.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/scripts/split_md.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_data/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/render/classes.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/render/versions.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/utils.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/versioned_app/app.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/_resources/versioned_app/webhooks.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/conftest.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_applications.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_auth_dependencies.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_changelog.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_cli.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_data_migrations.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_render.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_router_generation_with_from_future_annotations.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_routing.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema_validator.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_schema_generation/test_schema_with_future_annotations.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/test_structure.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/tutorial/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/tutorial/main.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/tutorial/test_example.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/versioning_styles/__init__.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tests/versioning_styles/test_versioning_formats.py +0 -0
- {cadwyn-5.4.4 → cadwyn-5.4.5}/tox.ini +0 -0
|
@@ -92,17 +92,17 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
92
92
|
* `__doc__` attribute is now copied from the original enum to the generated enum
|
|
93
93
|
* Python 3.12 type aliases and their typing_extensions backports are now supported (including `pydantic.JsonValue` and other `typing_extensions.TypeAliasType` instances)
|
|
94
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
|
|
95
|
-
* Updated minimum
|
|
95
|
+
* Updated minimum FastAPI version to 0.112.4 because embed_body_fields was added in 0.112.4
|
|
96
96
|
|
|
97
97
|
## [5.1.2]
|
|
98
98
|
|
|
99
99
|
### Fixed
|
|
100
100
|
|
|
101
|
-
* Generators not being called when
|
|
101
|
+
* Generators not being called when FastAPI validates the initial request
|
|
102
102
|
|
|
103
103
|
### Added
|
|
104
104
|
|
|
105
|
-
* `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"`.
|
|
106
106
|
|
|
107
107
|
## [5.1.1]
|
|
108
108
|
|
|
@@ -309,7 +309,7 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
309
309
|
|
|
310
310
|
### Changed
|
|
311
311
|
|
|
312
|
-
* `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
|
|
313
313
|
|
|
314
314
|
### Fixed
|
|
315
315
|
|
|
@@ -330,7 +330,7 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
330
330
|
|
|
331
331
|
### Fixed
|
|
332
332
|
|
|
333
|
-
*
|
|
333
|
+
* OpenAPI not being generated when lifespan was used
|
|
334
334
|
|
|
335
335
|
## [3.15.1]
|
|
336
336
|
|
|
@@ -455,7 +455,7 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
455
455
|
|
|
456
456
|
### Fixed
|
|
457
457
|
|
|
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
|
|
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.
|
|
459
459
|
|
|
460
460
|
## [3.6.5]
|
|
461
461
|
|
|
@@ -713,13 +713,13 @@ Versions 3.x.x are still supported in terms of bug and security fixes but all th
|
|
|
713
713
|
|
|
714
714
|
### Fixed
|
|
715
715
|
|
|
716
|
-
* Custom body fields created by
|
|
716
|
+
* Custom body fields created by FastAPI caused an exception. Now they are ignored
|
|
717
717
|
|
|
718
718
|
## [2.0.2]
|
|
719
719
|
|
|
720
720
|
### Added
|
|
721
721
|
|
|
722
|
-
* A link to
|
|
722
|
+
* A link to OpenAPI discussion on enum expansion into docs/recipes
|
|
723
723
|
* A link to intercom's API versioning article into docs/theory
|
|
724
724
|
|
|
725
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):
|
|
@@ -541,7 +541,7 @@ class _AsyncGeneratorCallableWrapper(_CallableWrapper):
|
|
|
541
541
|
class _AnnotationTransformer:
|
|
542
542
|
def __init__(self, generator: "SchemaGenerator") -> None:
|
|
543
543
|
# This cache is not here for speeding things up. It's for preventing the creation of copies of the same object
|
|
544
|
-
# 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.
|
|
545
545
|
# It's defined here and not on the method because of this: https://youtu.be/sVjtp6tGo0g
|
|
546
546
|
self.generator = generator
|
|
547
547
|
# TODO: Rewrite this to memoize
|
|
@@ -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)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Schema 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
|
-
Please note that you only need to have a migration if it is a breaking change for your users. The scenarios below only describe "what you can do" but not "what you should do". For the "should", please refer to the [how-to docs](../how_to/change_openapi_schemas/add_field.md).
|
|
5
|
+
Please note that you only need to have a migration if it is a breaking change for your users. The scenarios below only describe "what you can do" but not "what you should do". For the "should" part, please refer to the [how-to docs](../how_to/change_openapi_schemas/add_field.md).
|
|
6
6
|
|
|
7
7
|
## Add a field to the older version
|
|
8
8
|
|
|
9
9
|
```python
|
|
10
|
-
from pydantic import Field
|
|
11
10
|
from cadwyn import VersionChange, schema
|
|
11
|
+
from pydantic import Field
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class MyChange(VersionChange):
|
|
@@ -35,7 +35,7 @@ class MyChange(VersionChange):
|
|
|
35
35
|
|
|
36
36
|
## Change a field in the older version
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
The following code allows to set an attribute of a field, such as a description:
|
|
39
39
|
|
|
40
40
|
```python
|
|
41
41
|
from cadwyn import VersionChange, schema
|
|
@@ -48,7 +48,7 @@ class MyChange(VersionChange):
|
|
|
48
48
|
)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
The following code allows to un-set an attribute of a field, as if it never existed:
|
|
52
52
|
|
|
53
53
|
```python
|
|
54
54
|
from cadwyn import VersionChange, schema
|
|
@@ -65,23 +65,23 @@ class MyChange(VersionChange):
|
|
|
65
65
|
|
|
66
66
|
If you add `default` or `default_factory` into the old version of a schema -- it will not manifest in code automatically. Instead, you should add both the `default` or `default_factory`, and then also add the default value using a request migration.
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
The reason for such behaviour is the way how Cadwyn works with pydantic and unfortunately this cannot be changed:
|
|
69
69
|
|
|
70
70
|
Cadwyn:
|
|
71
71
|
|
|
72
|
-
1. Receives
|
|
72
|
+
1. Receives a request for API version `V`
|
|
73
73
|
2. Validates the request using the schemas from `V`
|
|
74
74
|
3. Marshalls the unmarshalled request body into a raw data structure using `BaseModel.dict` (`BaseModel.model_dump` in Pydantic v2) using **exclude_unset=True**
|
|
75
75
|
4. Passes the request through all request migrations from `V` to `latest`
|
|
76
76
|
5. Validates the request using `latest` schemas
|
|
77
77
|
|
|
78
|
-
The part that causes the aforementioned problem is
|
|
78
|
+
The part that causes the aforementioned problem is cadwyn's usage of `exclude_unset=True`. Unfortunately, when we use it, all default fields do not get set, so `latest` does not receive them. And if `latest` does not have the same defaults (for example, if the field has no default and is required in `latest`), then an error will occur. If we used `exclude_unset=False`, then `exclude_unset` would lose all its purpose for the users of our library so we cannot give it up. Instead, you should set all extras on step 4 in your request migrations.
|
|
79
79
|
|
|
80
80
|
## Add a validator to the older version
|
|
81
81
|
|
|
82
82
|
```python
|
|
83
|
-
from pydantic import Field, field_validator
|
|
84
83
|
from cadwyn import VersionChange, schema
|
|
84
|
+
from pydantic import Field, field_validator
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
@field_validator("foo")
|
|
@@ -101,8 +101,8 @@ class MyChange(VersionChange):
|
|
|
101
101
|
## Remove a validator from the older version
|
|
102
102
|
|
|
103
103
|
```python
|
|
104
|
-
from pydantic import Field, validator
|
|
105
104
|
from cadwyn import VersionChange, schema
|
|
105
|
+
from pydantic import Field, validator
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
class MyChange(VersionChange):
|
|
@@ -114,7 +114,8 @@ class MyChange(VersionChange):
|
|
|
114
114
|
|
|
115
115
|
## Rename a schema in the older version
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
The following code allows to replace all schema name occurrences with the new one to make sure that the name is different in openapi.json:
|
|
118
|
+
|
|
118
119
|
|
|
119
120
|
```python
|
|
120
121
|
from cadwyn import VersionChange, schema
|
|
@@ -126,5 +127,3 @@ class MyChange(VersionChange):
|
|
|
126
127
|
schema(MySchema).had(name="OtherSchema"),
|
|
127
128
|
)
|
|
128
129
|
```
|
|
129
|
-
|
|
130
|
-
which will replace all references to this schema with the new name.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
As Cadwyn allows to keep the same business logic, database schemas, etc -- you should have a single set of **common** tests that test the current latest version. These tests are going to work like the regular tests that you would have if you did not have any API versioning.
|
|
4
|
+
|
|
5
|
+
Here's a possible structure for test files:
|
|
6
|
+
|
|
7
|
+
```tree
|
|
8
|
+
└── tests
|
|
9
|
+
├── __init__.py
|
|
10
|
+
├── conftest.py
|
|
11
|
+
├── head
|
|
12
|
+
│ ├── __init__.py
|
|
13
|
+
│ ├── conftest.py
|
|
14
|
+
│ ├── test_users.py
|
|
15
|
+
│ ├── test_admins.py
|
|
16
|
+
│ └── test_invoices.py
|
|
17
|
+
├── v2022_11_16
|
|
18
|
+
│ ├── __init__.py
|
|
19
|
+
│ ├── conftest.py
|
|
20
|
+
│ └── test_invoices.py
|
|
21
|
+
└── v2023_03_11
|
|
22
|
+
├── __init__.py
|
|
23
|
+
├── conftest.py
|
|
24
|
+
└── test_users.py
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The following process is recommended when creating a new version:
|
|
29
|
+
|
|
30
|
+
1. Before creating a new version, apply the changes you want to your logic and schemas in the latest version (i.e., make the desired breaking changes), and then run the tests in the head directory. The tests that fail indicate the broken contracts
|
|
31
|
+
1. Create a new sub-directory in `tests`, name it after the previous version (e.g., v2024_05_04), and copy only the failing tests into it. Ensure these tests invoke the old version of the API
|
|
32
|
+
1. Write a `VersionChange` that fixes the tests in this outdated version through converters. In the new version, these same tests should still fail
|
|
33
|
+
1. Modify the tests in the head directory so that they test the new API contract and pass successfully
|
|
34
|
+
|
|
35
|
+
This approach helps to keep the old versions covered by tests and to keep the test code duplication minimal.
|