cadwyn 4.3.1__tar.gz → 4.4.1__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-4.3.1 → cadwyn-4.4.1}/.github/actions/setup-python-uv/action.yaml +4 -4
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/workflows/ci.yaml +37 -10
- cadwyn-4.4.1/.github/workflows/daily_tests.yaml +42 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/workflows/validate_links.yaml +9 -4
- {cadwyn-4.3.1 → cadwyn-4.4.1}/CHANGELOG.md +7 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/Makefile +1 -1
- {cadwyn-4.3.1 → cadwyn-4.4.1}/PKG-INFO +2 -2
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/applications.py +30 -6
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/route_generation.py +36 -12
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/schema_generation.py +1 -1
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/versions.py +2 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/quickstart/setup.md +1 -1
- cadwyn-4.4.1/docs_src/quickstart/setup/block001.sh +1 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/pyproject.toml +2 -1
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/versioned_app/app.py +3 -5
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/versioned_app/webhooks.py +1 -1
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/conftest.py +2 -1
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_applications.py +67 -7
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_render.py +10 -5
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_router_generation.py +17 -17
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_schema_generation/test_schema_validator.py +11 -5
- {cadwyn-4.3.1 → cadwyn-4.4.1}/uv.lock +259 -211
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/CODE_OF_CONDUCT.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/workflows/publish_docs.yaml +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.github/workflows/release.yaml +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.gitignore +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/.pre-commit-config.yaml +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/LICENSE +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/README.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/__main__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/_asts.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/_importer.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/_render.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/_utils.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/changelogs.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/exceptions.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/middleware.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/py.typed +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/routing.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/static/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/static/docs.html +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/common.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/data.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/endpoints.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/enums.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/cadwyn/structure/schemas.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/CNAME +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/api_version_header_and_context_variables.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/beware_of_data_versioning.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/changelogs.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/cli.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/endpoint_migrations.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/enum_migrations.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/index.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/main_app.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/methodology.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/schema_generation.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/schema_migrations.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/testing.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/concepts/version_changes.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/home/CONTRIBUTING.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_business_logic/index.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_endpoints/index.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_openapi_schemas/add_field.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_openapi_schemas/change_field_type.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_openapi_schemas/changing_constraints.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_openapi_schemas/remove_field.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/change_openapi_schemas/rename_a_field_in_schema.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/how_to/index.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/img/dashboard_with_one_version.png +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/img/dashboard_with_two_versions.png +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/img/get_users_endpoint_from_prior_version.png +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/img/simplified_migration_model.png +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/img/sponsor_logos/monite.png +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/img/unversioned_dashboard.png +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/index.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/plugin.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/quickstart/tutorial.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/theory/how_to_build_versioning_framework.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/theory/how_we_got_here.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs/theory/literature.md +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/setup/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/setup/block002.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/setup/tests/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/setup/tests/test_block002.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/block001.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/block002.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/block003.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/tests/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/tests/test_block001.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/tests/test_block002.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/docs_src/quickstart/tutorial/tests/test_block003.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/mkdocs.yml +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/ruff.toml +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/scripts/fix_links.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/scripts/split_md.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_data/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_data/unversioned_schema_dir/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_data/unversioned_schema_dir/unversioned_schemas.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_data/unversioned_schemas.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/app_for_testing_routing.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/render/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/render/classes.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/render/complex/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/render/complex/classes.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/render/complex/versions.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/render/versions.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/utils.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/versioned_app/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/versioned_app/v2021_01_01.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/_resources/versioned_app/v2022_01_02.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_changelog.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_cli.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_data_migrations.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_routing.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_schema_generation/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_schema_generation/test_enum.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_schema_generation/test_schema.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_schema_generation/test_schema_field.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/test_structure.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/tutorial/__init__.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/tutorial/main.py +0 -0
- {cadwyn-4.3.1 → cadwyn-4.4.1}/tests/tutorial/test_example.py +0 -0
|
@@ -13,15 +13,15 @@ inputs:
|
|
|
13
13
|
runs:
|
|
14
14
|
using: "composite"
|
|
15
15
|
steps:
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ inputs.python-version }}
|
|
16
19
|
- uses: astral-sh/setup-uv@v3
|
|
17
20
|
with:
|
|
18
21
|
version: ${{ inputs.uv-version }}
|
|
19
22
|
enable-cache: true
|
|
20
23
|
cache-dependency-glob: "uv.lock"
|
|
21
|
-
- uses: actions/setup-python@v5
|
|
22
|
-
with:
|
|
23
|
-
python-version: ${{ inputs.python-version }}
|
|
24
24
|
- run: |
|
|
25
|
-
uv sync --
|
|
25
|
+
uv sync --frozen --all-extras --dev
|
|
26
26
|
echo "$(pwd)/.venv/bin" >> $GITHUB_PATH
|
|
27
27
|
shell: bash
|
|
@@ -27,6 +27,8 @@ jobs:
|
|
|
27
27
|
python-version: "3.11"
|
|
28
28
|
- os: ubuntu-latest
|
|
29
29
|
python-version: "3.12"
|
|
30
|
+
- os: ubuntu-latest
|
|
31
|
+
python-version: "3.13"
|
|
30
32
|
- os: windows-latest
|
|
31
33
|
python-version: "3.10"
|
|
32
34
|
- os: macos-latest
|
|
@@ -38,32 +40,57 @@ jobs:
|
|
|
38
40
|
- uses: ./.github/actions/setup-python-uv
|
|
39
41
|
with:
|
|
40
42
|
python-version: ${{ matrix.python-version }}
|
|
41
|
-
- run: uv run coverage run --source=. -m pytest
|
|
43
|
+
- run: uv run coverage run --source=. --parallel-mode -m pytest tests
|
|
42
44
|
- name: Upload coverage results
|
|
43
45
|
uses: actions/upload-artifact@v3
|
|
44
46
|
if: matrix.os == 'ubuntu-latest' # Cross-platform coverage combination doesn't work
|
|
45
47
|
with:
|
|
46
|
-
name: coverage-results
|
|
48
|
+
name: main-tests-coverage-results
|
|
49
|
+
path: coverage/
|
|
50
|
+
Tutorial-tests:
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
name: Tutorial tests
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
- uses: actions/setup-python@v5
|
|
56
|
+
with:
|
|
57
|
+
python-version: "3.10"
|
|
58
|
+
- name: Install cadwyn with instructions from docs
|
|
59
|
+
run: sh docs_src/quickstart/setup/block001.sh
|
|
60
|
+
- run: pip install pytest coverage dirty-equals
|
|
61
|
+
- run: coverage run --source=. --parallel-mode -m pytest docs_src
|
|
62
|
+
- name: Upload coverage results
|
|
63
|
+
uses: actions/upload-artifact@v3
|
|
64
|
+
with:
|
|
65
|
+
name: docs-tests-coverage-results
|
|
47
66
|
path: coverage/
|
|
48
67
|
Coverage:
|
|
49
|
-
needs: Tests
|
|
68
|
+
needs: [Tests, Tutorial-tests]
|
|
50
69
|
runs-on: ubuntu-latest
|
|
51
70
|
steps:
|
|
52
71
|
- uses: actions/checkout@v4
|
|
53
|
-
-
|
|
72
|
+
- name: Download main tests coverage info
|
|
73
|
+
uses: actions/download-artifact@v3
|
|
54
74
|
with:
|
|
55
|
-
name: coverage-results
|
|
75
|
+
name: main-tests-coverage-results
|
|
56
76
|
path: coverage/
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
- name: Download docs tests coverage info
|
|
78
|
+
uses: actions/download-artifact@v3
|
|
79
|
+
with:
|
|
80
|
+
name: docs-tests-coverage-results
|
|
81
|
+
path: coverage/
|
|
82
|
+
- uses: actions/setup-python@v5
|
|
83
|
+
with:
|
|
84
|
+
python-version: "3.10"
|
|
85
|
+
- run: pip install 'coverage[toml]'
|
|
86
|
+
- run: coverage combine
|
|
87
|
+
- run: coverage xml
|
|
61
88
|
- name: Upload to Codecov
|
|
62
89
|
uses: codecov/codecov-action@v4
|
|
63
90
|
env:
|
|
64
91
|
fail_ci_if_error: true
|
|
65
92
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
66
|
-
- run:
|
|
93
|
+
- run: coverage report --fail-under=100
|
|
67
94
|
|
|
68
95
|
Lint:
|
|
69
96
|
runs-on: ubuntu-latest
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Twice Daily Package Update Testing
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "0 0 * * *"
|
|
6
|
+
- cron: "0 12 * * *"
|
|
7
|
+
workflow_dispatch: # Allows manual triggering of the workflow
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
update-dependencies-and-test:
|
|
11
|
+
name: Update dependencies and run tests
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: true
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: ./.github/actions/setup-python-uv
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- run: uv sync --refresh --all-extras --dev --upgrade
|
|
24
|
+
- run: pytest .
|
|
25
|
+
- uses: jakebailey/pyright-action@v1
|
|
26
|
+
with:
|
|
27
|
+
pylance-version: latest-release
|
|
28
|
+
|
|
29
|
+
notify-on-failure:
|
|
30
|
+
name: Notify on failure
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
needs: update-dependencies-and-test
|
|
33
|
+
if: failure()
|
|
34
|
+
steps:
|
|
35
|
+
- name: Send Telegram Notification on Failure
|
|
36
|
+
uses: appleboy/telegram-action@master
|
|
37
|
+
with:
|
|
38
|
+
to: 438153389
|
|
39
|
+
token: ${{ secrets.TELEGRAM_TOKEN }}
|
|
40
|
+
message: |
|
|
41
|
+
New version of something broke Cadwyn!
|
|
42
|
+
${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
@@ -21,10 +21,15 @@ jobs:
|
|
|
21
21
|
runs-on: ubuntu-latest
|
|
22
22
|
steps:
|
|
23
23
|
- uses: actions/checkout@v4
|
|
24
|
-
- uses:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
- uses: ./.github/actions/setup-python-uv
|
|
25
|
+
- run: |
|
|
26
|
+
OUTPUT=$(uv run mkdocs build 2>&1)
|
|
27
|
+
echo "$OUTPUT"
|
|
28
|
+
|
|
29
|
+
if echo "$OUTPUT" | grep -q "ERROR"; then
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
28
33
|
markdown-link-check:
|
|
29
34
|
runs-on: ubuntu-latest
|
|
30
35
|
steps:
|
|
@@ -5,6 +5,13 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [4.4.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* Support for [webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/) in swagger
|
|
13
|
+
* Automatic generation of versioned routes and webhooks upon the first request to Cadwyn. Notice that if you were using some of cadwyn's internal interfaces, this might break your code. If it did, make an issue and let's discuss your use case
|
|
14
|
+
|
|
8
15
|
## [4.3.1]
|
|
9
16
|
|
|
10
17
|
### Fixed
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cadwyn
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.4.1
|
|
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
|
|
7
7
|
Author-email: Stanislav Zmiev <zmievsa@gmail.com>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Keywords: api,api-versioning,code-generation,fastapi,hints,json-schema,pydantic,python,python310,python311,python312,stripe,versioning
|
|
10
|
+
Keywords: api,api-versioning,code-generation,fastapi,hints,json-schema,pydantic,python,python310,python311,python312,python313,stripe,versioning
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: Web Environment
|
|
13
13
|
Classifier: Framework :: AsyncIO
|
|
@@ -25,6 +25,7 @@ from starlette.routing import BaseRoute, Route
|
|
|
25
25
|
from starlette.types import Lifespan
|
|
26
26
|
from typing_extensions import Self
|
|
27
27
|
|
|
28
|
+
from cadwyn._utils import same_definition_as_in
|
|
28
29
|
from cadwyn.changelogs import CadwynChangelogResource, _generate_changelog
|
|
29
30
|
from cadwyn.middleware import HeaderVersioningMiddleware, _get_api_version_dependency
|
|
30
31
|
from cadwyn.route_generation import generate_versioned_routers
|
|
@@ -96,8 +97,8 @@ class Cadwyn(FastAPI):
|
|
|
96
97
|
**extra: Any,
|
|
97
98
|
) -> None:
|
|
98
99
|
self.versions = versions
|
|
99
|
-
# TODO: Remove argument entirely in any major version.
|
|
100
100
|
self._dependency_overrides_provider = FakeDependencyOverridesProvider({})
|
|
101
|
+
self._cadwyn_initialized = False
|
|
101
102
|
|
|
102
103
|
super().__init__(
|
|
103
104
|
debug=debug,
|
|
@@ -156,6 +157,8 @@ class Cadwyn(FastAPI):
|
|
|
156
157
|
api_version_header_name=api_version_header_name,
|
|
157
158
|
api_version_var=self.versions.api_version_var,
|
|
158
159
|
)
|
|
160
|
+
self._versioned_webhook_routers: dict[date, APIRouter] = {}
|
|
161
|
+
self._latest_version_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
|
|
159
162
|
|
|
160
163
|
self.changelog_url = changelog_url
|
|
161
164
|
self.include_changelog_url_in_schema = include_changelog_url_in_schema
|
|
@@ -176,6 +179,26 @@ class Cadwyn(FastAPI):
|
|
|
176
179
|
default_response_class=default_response_class,
|
|
177
180
|
)
|
|
178
181
|
|
|
182
|
+
@same_definition_as_in(FastAPI.__call__)
|
|
183
|
+
async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
|
|
184
|
+
if not self._cadwyn_initialized:
|
|
185
|
+
self._cadwyn_initialize()
|
|
186
|
+
self.__call__ = super().__call__
|
|
187
|
+
await self.__call__(scope, receive, send)
|
|
188
|
+
|
|
189
|
+
def _cadwyn_initialize(self) -> None:
|
|
190
|
+
generated_routers = generate_versioned_routers(
|
|
191
|
+
self._latest_version_router,
|
|
192
|
+
webhooks=self.webhooks,
|
|
193
|
+
versions=self.versions,
|
|
194
|
+
)
|
|
195
|
+
for version, router in generated_routers.endpoints.items():
|
|
196
|
+
self.add_header_versioned_routers(router, header_value=version.isoformat())
|
|
197
|
+
|
|
198
|
+
for version, router in generated_routers.webhooks.items():
|
|
199
|
+
self._versioned_webhook_routers[version] = router
|
|
200
|
+
self._cadwyn_initialized = True
|
|
201
|
+
|
|
179
202
|
def _add_default_versioned_routers(self) -> None:
|
|
180
203
|
for version in self.versions:
|
|
181
204
|
self.router.versioned_routers[version.value] = APIRouter(**self._kwargs_to_router)
|
|
@@ -240,12 +263,8 @@ class Cadwyn(FastAPI):
|
|
|
240
263
|
)
|
|
241
264
|
|
|
242
265
|
def generate_and_include_versioned_routers(self, *routers: APIRouter) -> None:
|
|
243
|
-
root_router = APIRouter(dependency_overrides_provider=self._dependency_overrides_provider)
|
|
244
266
|
for router in routers:
|
|
245
|
-
|
|
246
|
-
router_versions = generate_versioned_routers(root_router, versions=self.versions)
|
|
247
|
-
for version, router in router_versions.items():
|
|
248
|
-
self.add_header_versioned_routers(router, header_value=version.isoformat())
|
|
267
|
+
self._latest_version_router.include_router(router)
|
|
249
268
|
|
|
250
269
|
async def openapi_jsons(self, req: Request) -> JSONResponse:
|
|
251
270
|
raw_version = req.query_params.get("version") or req.headers.get(self.router.api_version_header_name)
|
|
@@ -276,6 +295,10 @@ class Cadwyn(FastAPI):
|
|
|
276
295
|
if root_path and root_path not in server_urls and self.root_path_in_servers:
|
|
277
296
|
self.servers.insert(0, {"url": root_path})
|
|
278
297
|
|
|
298
|
+
webhook_routes = None
|
|
299
|
+
if version in self._versioned_webhook_routers:
|
|
300
|
+
webhook_routes = self._versioned_webhook_routers[version].routes
|
|
301
|
+
|
|
279
302
|
return JSONResponse(
|
|
280
303
|
get_openapi(
|
|
281
304
|
title=self.title,
|
|
@@ -287,6 +310,7 @@ class Cadwyn(FastAPI):
|
|
|
287
310
|
contact=self.contact,
|
|
288
311
|
license_info=self.license_info,
|
|
289
312
|
routes=routes,
|
|
313
|
+
webhooks=webhook_routes,
|
|
290
314
|
tags=self.openapi_tags,
|
|
291
315
|
servers=self.servers,
|
|
292
316
|
)
|
|
@@ -7,7 +7,6 @@ from typing import (
|
|
|
7
7
|
TYPE_CHECKING,
|
|
8
8
|
Any,
|
|
9
9
|
Generic,
|
|
10
|
-
TypeVar,
|
|
11
10
|
cast,
|
|
12
11
|
)
|
|
13
12
|
|
|
@@ -20,7 +19,7 @@ from fastapi.routing import APIRoute
|
|
|
20
19
|
from issubclass import issubclass as lenient_issubclass
|
|
21
20
|
from pydantic import BaseModel
|
|
22
21
|
from starlette.routing import BaseRoute
|
|
23
|
-
from typing_extensions import assert_never
|
|
22
|
+
from typing_extensions import TypeVar, assert_never
|
|
24
23
|
|
|
25
24
|
from cadwyn._utils import Sentinel
|
|
26
25
|
from cadwyn.exceptions import (
|
|
@@ -48,7 +47,8 @@ if TYPE_CHECKING:
|
|
|
48
47
|
from fastapi.dependencies.models import Dependant
|
|
49
48
|
|
|
50
49
|
_Call = TypeVar("_Call", bound=Callable[..., Any])
|
|
51
|
-
_R = TypeVar("_R", bound=
|
|
50
|
+
_R = TypeVar("_R", bound=APIRouter)
|
|
51
|
+
_WR = TypeVar("_WR", bound=APIRouter, default=APIRouter)
|
|
52
52
|
# This is a hack we do because we can't guarantee how the user will use the router.
|
|
53
53
|
_DELETED_ROUTE_TAG = "_CADWYN_DELETED_ROUTE"
|
|
54
54
|
|
|
@@ -59,8 +59,21 @@ class _EndpointInfo:
|
|
|
59
59
|
endpoint_methods: frozenset[str]
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
@dataclass(slots=True, frozen=True)
|
|
63
|
+
class GeneratedRouters(Generic[_R, _WR]):
|
|
64
|
+
endpoints: dict[VersionDate, _R]
|
|
65
|
+
webhooks: dict[VersionDate, _WR]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def generate_versioned_routers(
|
|
69
|
+
router: _R,
|
|
70
|
+
versions: VersionBundle,
|
|
71
|
+
*,
|
|
72
|
+
webhooks: _WR | None = None,
|
|
73
|
+
) -> GeneratedRouters[_R, _WR]:
|
|
74
|
+
if webhooks is None:
|
|
75
|
+
webhooks = cast(_WR, APIRouter())
|
|
76
|
+
return _EndpointTransformer(router, versions, webhooks).transform()
|
|
64
77
|
|
|
65
78
|
|
|
66
79
|
class VersionedAPIRouter(fastapi.routing.APIRouter):
|
|
@@ -77,30 +90,36 @@ class VersionedAPIRouter(fastapi.routing.APIRouter):
|
|
|
77
90
|
return endpoint
|
|
78
91
|
|
|
79
92
|
|
|
80
|
-
class _EndpointTransformer(Generic[_R]):
|
|
81
|
-
def __init__(self, parent_router: _R, versions: VersionBundle) -> None:
|
|
93
|
+
class _EndpointTransformer(Generic[_R, _WR]):
|
|
94
|
+
def __init__(self, parent_router: _R, versions: VersionBundle, webhooks: _WR) -> None:
|
|
82
95
|
super().__init__()
|
|
83
96
|
self.parent_router = parent_router
|
|
84
97
|
self.versions = versions
|
|
98
|
+
self.parent_webhooks_router = webhooks
|
|
85
99
|
self.schema_generators = generate_versioned_models(versions)
|
|
86
100
|
|
|
87
101
|
self.routes_that_never_existed = [
|
|
88
102
|
route for route in parent_router.routes if isinstance(route, APIRoute) and _DELETED_ROUTE_TAG in route.tags
|
|
89
103
|
]
|
|
90
104
|
|
|
91
|
-
def transform(self) ->
|
|
105
|
+
def transform(self) -> GeneratedRouters[_R, _WR]:
|
|
92
106
|
router = deepcopy(self.parent_router)
|
|
107
|
+
webhook_router = deepcopy(self.parent_webhooks_router)
|
|
93
108
|
routers: dict[VersionDate, _R] = {}
|
|
109
|
+
webhook_routers: dict[VersionDate, _WR] = {}
|
|
94
110
|
|
|
95
111
|
for version in self.versions:
|
|
96
112
|
self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(router)
|
|
113
|
+
self.schema_generators[str(version.value)].annotation_transformer.migrate_router_to_version(webhook_router)
|
|
97
114
|
|
|
98
115
|
self._validate_all_data_converters_are_applied(router, version)
|
|
99
116
|
|
|
100
117
|
routers[version.value] = router
|
|
118
|
+
webhook_routers[version.value] = webhook_router
|
|
101
119
|
# Applying changes for the next version
|
|
102
120
|
router = deepcopy(router)
|
|
103
|
-
|
|
121
|
+
webhook_router = deepcopy(webhook_router)
|
|
122
|
+
self._apply_endpoint_changes_to_router(router.routes + webhook_router.routes, version)
|
|
104
123
|
|
|
105
124
|
if self.routes_that_never_existed:
|
|
106
125
|
raise RouterGenerationError(
|
|
@@ -146,7 +165,13 @@ class _EndpointTransformer(Generic[_R]):
|
|
|
146
165
|
for route in router.routes
|
|
147
166
|
if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
|
|
148
167
|
]
|
|
149
|
-
|
|
168
|
+
for _, webhook_router in webhook_routers.items():
|
|
169
|
+
webhook_router.routes = [
|
|
170
|
+
route
|
|
171
|
+
for route in webhook_router.routes
|
|
172
|
+
if not (isinstance(route, fastapi.routing.APIRoute) and _DELETED_ROUTE_TAG in route.tags)
|
|
173
|
+
]
|
|
174
|
+
return GeneratedRouters(routers, webhook_routers)
|
|
150
175
|
|
|
151
176
|
def _validate_all_data_converters_are_applied(self, router: APIRouter, version: Version):
|
|
152
177
|
path_to_route_methods_mapping, head_response_models, head_request_bodies = self._extract_all_routes_identifiers(
|
|
@@ -223,10 +248,9 @@ class _EndpointTransformer(Generic[_R]):
|
|
|
223
248
|
# TODO (https://github.com/zmievsa/cadwyn/issues/28): Simplify
|
|
224
249
|
def _apply_endpoint_changes_to_router( # noqa: C901
|
|
225
250
|
self,
|
|
226
|
-
|
|
251
|
+
routes: list[BaseRoute] | list[APIRoute],
|
|
227
252
|
version: Version,
|
|
228
253
|
):
|
|
229
|
-
routes = router.routes
|
|
230
254
|
for version_change in version.changes:
|
|
231
255
|
for instruction in version_change.alter_endpoint_instructions:
|
|
232
256
|
original_routes = _get_routes(
|
|
@@ -607,7 +607,7 @@ class SchemaGenerator:
|
|
|
607
607
|
|
|
608
608
|
def __getitem__(self, model: type[_T_ANY_MODEL], /) -> type[_T_ANY_MODEL]:
|
|
609
609
|
if not isinstance(model, type) or not issubclass(model, BaseModel | Enum) or model in (BaseModel, RootModel):
|
|
610
|
-
return model
|
|
610
|
+
return model
|
|
611
611
|
model = _unwrap_model(model)
|
|
612
612
|
|
|
613
613
|
if model in self.concrete_models:
|
|
@@ -156,6 +156,8 @@ class VersionChange:
|
|
|
156
156
|
"instructions_to_migrate_to_previous_version",
|
|
157
157
|
"__module__",
|
|
158
158
|
"__doc__",
|
|
159
|
+
"__firstlineno__",
|
|
160
|
+
"__static_attributes__",
|
|
159
161
|
}:
|
|
160
162
|
raise CadwynStructureError(
|
|
161
163
|
f"Found: '{attr_name}' attribute of type '{type(attr_value)}' in '{cls.__name__}'."
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pip install 'cadwyn[standard]'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cadwyn"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.4.1"
|
|
4
4
|
description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
|
|
5
5
|
authors = [{ name = "Stanislav Zmiev", email = "zmievsa@gmail.com" }]
|
|
6
6
|
license = "MIT"
|
|
@@ -20,6 +20,7 @@ keywords = [
|
|
|
20
20
|
"python310",
|
|
21
21
|
"python311",
|
|
22
22
|
"python312",
|
|
23
|
+
"python313",
|
|
23
24
|
]
|
|
24
25
|
classifiers = [
|
|
25
26
|
"Intended Audience :: Information Technology",
|
|
@@ -8,10 +8,9 @@ from fastapi.testclient import TestClient
|
|
|
8
8
|
|
|
9
9
|
from cadwyn import Cadwyn
|
|
10
10
|
from cadwyn.structure.versions import Version, VersionBundle
|
|
11
|
-
from tests._resources.utils import BASIC_HEADERS
|
|
12
11
|
from tests._resources.versioned_app.v2021_01_01 import router as v2021_01_01_router
|
|
13
12
|
from tests._resources.versioned_app.v2022_01_02 import router as v2022_01_02_router
|
|
14
|
-
from tests._resources.versioned_app.webhooks import router as
|
|
13
|
+
from tests._resources.versioned_app.webhooks import router as unversioned_router
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
# TODO: Add better tests for covering lifespan
|
|
@@ -23,17 +22,16 @@ async def lifespan(app: FastAPI):
|
|
|
23
22
|
versioned_app = Cadwyn(versions=VersionBundle(Version(date(2021, 1, 1))), lifespan=lifespan)
|
|
24
23
|
versioned_app.add_header_versioned_routers(v2021_01_01_router, header_value="2021-01-01")
|
|
25
24
|
versioned_app.add_header_versioned_routers(v2022_01_02_router, header_value="2022-02-02")
|
|
26
|
-
versioned_app.include_router(
|
|
25
|
+
versioned_app.include_router(unversioned_router)
|
|
27
26
|
|
|
28
27
|
versioned_app_with_custom_api_version_var = Cadwyn(
|
|
29
28
|
versions=VersionBundle(Version(date(2021, 1, 1))), lifespan=lifespan, api_version_var=ContextVar("My api version")
|
|
30
29
|
)
|
|
31
30
|
versioned_app_with_custom_api_version_var.add_header_versioned_routers(v2021_01_01_router, header_value="2021-01-01")
|
|
32
31
|
versioned_app_with_custom_api_version_var.add_header_versioned_routers(v2022_01_02_router, header_value="2022-02-02")
|
|
33
|
-
versioned_app_with_custom_api_version_var.include_router(
|
|
32
|
+
versioned_app_with_custom_api_version_var.include_router(unversioned_router)
|
|
34
33
|
|
|
35
34
|
# TODO: We should not have any clients that are run like this. Instead, all of them must run using "with"
|
|
36
|
-
client = TestClient(versioned_app, raise_server_exceptions=False, headers=BASIC_HEADERS)
|
|
37
35
|
client_without_headers = TestClient(versioned_app)
|
|
38
36
|
client_without_headers_and_with_custom_api_version_var = TestClient(versioned_app_with_custom_api_version_var)
|
|
39
37
|
|
|
@@ -105,6 +105,7 @@ class CreateVersionedApp:
|
|
|
105
105
|
)
|
|
106
106
|
)
|
|
107
107
|
app.generate_and_include_versioned_routers(router)
|
|
108
|
+
app._cadwyn_initialize()
|
|
108
109
|
return app
|
|
109
110
|
|
|
110
111
|
|
|
@@ -141,7 +142,7 @@ def version_change(
|
|
|
141
142
|
**body_items: Any,
|
|
142
143
|
):
|
|
143
144
|
return type(VersionChange)(
|
|
144
|
-
"MyVersionChange",
|
|
145
|
+
"MyVersionChange", # pyright: ignore[reportCallIssue]
|
|
145
146
|
(VersionChange,),
|
|
146
147
|
{
|
|
147
148
|
"description": "",
|
|
@@ -6,10 +6,13 @@ import pytest
|
|
|
6
6
|
from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI
|
|
7
7
|
from fastapi.routing import APIRoute
|
|
8
8
|
from fastapi.testclient import TestClient
|
|
9
|
+
from pydantic import BaseModel
|
|
9
10
|
|
|
10
11
|
from cadwyn import Cadwyn
|
|
11
12
|
from cadwyn.route_generation import VersionedAPIRouter
|
|
12
|
-
from cadwyn.structure.
|
|
13
|
+
from cadwyn.structure.endpoints import endpoint
|
|
14
|
+
from cadwyn.structure.schemas import schema
|
|
15
|
+
from cadwyn.structure.versions import HeadVersion, Version, VersionBundle, VersionChange
|
|
13
16
|
from tests._resources.utils import BASIC_HEADERS, DEFAULT_API_VERSION
|
|
14
17
|
from tests._resources.versioned_app.app import (
|
|
15
18
|
client_without_headers,
|
|
@@ -162,8 +165,8 @@ def test__header_based_versioning__invalid_version_header_format__should_raise_4
|
|
|
162
165
|
assert resp.json()[0]["loc"] == ["header", "x-api-version"]
|
|
163
166
|
|
|
164
167
|
|
|
165
|
-
def
|
|
166
|
-
resp = client_without_headers.post("/v1/
|
|
168
|
+
def test__get_unversioned_router():
|
|
169
|
+
resp = client_without_headers.post("/v1/unversioned")
|
|
167
170
|
assert resp.status_code == 200
|
|
168
171
|
assert resp.json() == {"saved": True}
|
|
169
172
|
|
|
@@ -254,14 +257,14 @@ def test__get_docs__specific_version():
|
|
|
254
257
|
assert resp.status_code == 200
|
|
255
258
|
|
|
256
259
|
|
|
257
|
-
def
|
|
258
|
-
resp = client_without_headers.post("/v1/
|
|
260
|
+
def test__get_unversioned_with_redirect():
|
|
261
|
+
resp = client_without_headers.post("/v1/unversioned/")
|
|
259
262
|
assert resp.status_code == 200
|
|
260
263
|
assert resp.json() == {"saved": True}
|
|
261
264
|
|
|
262
265
|
|
|
263
|
-
def
|
|
264
|
-
resp = client_without_headers.patch("/v1/
|
|
266
|
+
def test__get_unversioned_as_partial_because_of_method():
|
|
267
|
+
resp = client_without_headers.patch("/v1/unversioned")
|
|
265
268
|
assert resp.status_code == 405
|
|
266
269
|
|
|
267
270
|
|
|
@@ -291,3 +294,60 @@ def test__background_tasks():
|
|
|
291
294
|
resp = client.post("/send-notification/test@example.com", headers=BASIC_HEADERS)
|
|
292
295
|
assert resp.status_code == 200, resp.json()
|
|
293
296
|
assert background_task_data == ("test@example.com", "some notification")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def test__webhooks():
|
|
300
|
+
webhooks = VersionedAPIRouter()
|
|
301
|
+
|
|
302
|
+
class Subscription(BaseModel):
|
|
303
|
+
username: str
|
|
304
|
+
monthly_fee: float
|
|
305
|
+
start_date: str
|
|
306
|
+
|
|
307
|
+
@webhooks.post("new-subscription")
|
|
308
|
+
def new_subscription(body: Subscription): # pragma: no cover
|
|
309
|
+
"""
|
|
310
|
+
When a new user subscribes to your service we'll send you a POST request with this
|
|
311
|
+
data to the URL that you register for the event `new-subscription` in the dashboard.
|
|
312
|
+
"""
|
|
313
|
+
|
|
314
|
+
class MyVersionChange(VersionChange):
|
|
315
|
+
description = "Mess with webhooks"
|
|
316
|
+
instructions_to_migrate_to_previous_version = [
|
|
317
|
+
endpoint("new-subscription", ["POST"]).didnt_exist,
|
|
318
|
+
schema(Subscription).field("monthly_fee").didnt_exist,
|
|
319
|
+
]
|
|
320
|
+
|
|
321
|
+
app = Cadwyn(
|
|
322
|
+
versions=VersionBundle(HeadVersion(), Version("2023-04-12", MyVersionChange), Version("2022-11-16")),
|
|
323
|
+
webhooks=webhooks,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
@app.webhooks.post("post-subscription") # pragma: no cover
|
|
327
|
+
def post_subscription(body: Subscription): # pragma: no cover
|
|
328
|
+
"""This should also be there"""
|
|
329
|
+
|
|
330
|
+
with TestClient(app) as client:
|
|
331
|
+
resp = client.get("/openapi.json?version=2023-04-12")
|
|
332
|
+
openapi_dict = resp.json()
|
|
333
|
+
|
|
334
|
+
assert "webhooks" in openapi_dict, "'webhooks' section is missing"
|
|
335
|
+
assert "new-subscription" in openapi_dict["webhooks"], "'new-subscription' webhook is missing"
|
|
336
|
+
assert "post-subscription" in openapi_dict["webhooks"], "'post-subscription' webhook is missing"
|
|
337
|
+
assert "post" in openapi_dict["webhooks"]["post-subscription"], "POST method for 'post-subscription' is missing"
|
|
338
|
+
assert "Subscription" in openapi_dict["components"]["schemas"], "'Subscription' component is missing"
|
|
339
|
+
assert (
|
|
340
|
+
"monthly_fee" in openapi_dict["components"]["schemas"]["Subscription"]["properties"]
|
|
341
|
+
), "monthly_fee field is missing"
|
|
342
|
+
|
|
343
|
+
resp = client.get("/openapi.json?version=2022-11-16")
|
|
344
|
+
openapi_dict = resp.json()
|
|
345
|
+
|
|
346
|
+
assert "webhooks" in openapi_dict, "'webhooks' section is missing"
|
|
347
|
+
assert "new-subscription" not in openapi_dict["webhooks"], "'new-subscription' webhook is missing"
|
|
348
|
+
assert "post-subscription" in openapi_dict["webhooks"], "'post-subscription' webhook is present"
|
|
349
|
+
assert "post" in openapi_dict["webhooks"]["post-subscription"], "POST method for 'post-subscription' is missing"
|
|
350
|
+
assert "Subscription" in openapi_dict["components"]["schemas"], "'Subscription' component is missing"
|
|
351
|
+
assert (
|
|
352
|
+
"monthly_fee" not in openapi_dict["components"]["schemas"]["Subscription"]["properties"]
|
|
353
|
+
), "monthly_fee field is present yet it must be deleted"
|
|
@@ -19,11 +19,16 @@ def test__render_model__with_weird_types():
|
|
|
19
19
|
)
|
|
20
20
|
# TODO: sobolevn has created a tool for doing such nocovers in a better manner.
|
|
21
21
|
# hopefully someday we will switch to it.
|
|
22
|
-
if sys.version_info >= (3, 11):
|
|
22
|
+
if sys.version_info >= (3, 11):
|
|
23
23
|
rendered_lambda = "lambda: 83"
|
|
24
|
-
else:
|
|
24
|
+
else:
|
|
25
25
|
rendered_lambda = "lambda : 83"
|
|
26
26
|
|
|
27
|
+
if sys.version_info >= (3, 13):
|
|
28
|
+
rend_ann = "typing.Annotated"
|
|
29
|
+
else:
|
|
30
|
+
rend_ann = "Annotated"
|
|
31
|
+
|
|
27
32
|
# TODO: As you see, we do not rename bases correctly in render. We gotta fix it some day...
|
|
28
33
|
assert code(result) == code(
|
|
29
34
|
f'''
|
|
@@ -32,11 +37,11 @@ class ModelWithWeirdFields(A):
|
|
|
32
37
|
foo: dict = Field(default={{'a': 'b'}})
|
|
33
38
|
bar: list[int] = Field(default_factory=my_default_factory)
|
|
34
39
|
baz: typing.Literal[MyEnum.foo] = Field()
|
|
35
|
-
saz:
|
|
36
|
-
laz:
|
|
40
|
+
saz: {rend_ann}[str, StringConstraints(to_upper=True)] = Field()
|
|
41
|
+
laz: {rend_ann}[int, None, Interval(gt=12, ge=None, lt=None, le=None), None] = Field()
|
|
37
42
|
taz: typing.Union[int, str, None] = Field(default_factory={rendered_lambda})
|
|
38
43
|
naz: list[int] = Field(default=[1, 2, 3])
|
|
39
|
-
gaz:
|
|
44
|
+
gaz: {rend_ann}[bytes, Strict(strict=True), Len(min_length=0, max_length=None)] = Field(min_length=3, title='Hewwo')
|
|
40
45
|
'''
|
|
41
46
|
)
|
|
42
47
|
|