fastapi-router-versioning 0.2.6__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/dependabot.yml +2 -19
  2. fastapi_router_versioning-0.3.0/.github/workflows/bump-pre-commit-hooks.yml +67 -0
  3. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/create-draft-release.yml +3 -3
  4. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/labeler.yml +1 -1
  5. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/latest-changes.yml +1 -1
  6. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/pre-commit.yml +3 -3
  7. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/prepare-release.yml +3 -3
  8. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/publish.yml +3 -3
  9. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/test-redistribute.yml +3 -3
  10. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/test.yml +6 -6
  11. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/zizmor.yml +2 -2
  12. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.pre-commit-config.yaml +1 -1
  13. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/PKG-INFO +53 -58
  14. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/README.md +52 -57
  15. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/RELEASE_NOTES.md +21 -0
  16. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/mounted_subapps_app.py +2 -4
  17. fastapi_router_versioning-0.3.0/examples/validation_override_integration_app.py +56 -0
  18. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/fastapi_router_versioning/__init__.py +1 -1
  19. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/fastapi_router_versioning/versioner.py +0 -99
  20. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/pyproject.toml +1 -0
  21. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/tests/test_versioner.py +7 -330
  22. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/uv.lock +250 -232
  23. fastapi_router_versioning-0.2.6/examples/validation_error_code_app.py +0 -57
  24. fastapi_router_versioning-0.2.6/examples/validation_error_code_custom_handler_app.py +0 -70
  25. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/labeler.yml +0 -0
  26. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/detect-conflicts.yml +0 -0
  27. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.github/workflows/guard-dependencies.yml +0 -0
  28. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.gitignore +0 -0
  29. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/.python-version +0 -0
  30. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/LICENSE +0 -0
  31. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/calver_app.py +0 -0
  32. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/download_static_assets.py +0 -0
  33. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/multi_router_app.py +0 -0
  34. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/openapi_hook_app.py +0 -0
  35. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/self_hosted_docs_app.py +0 -0
  36. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/semver_app.py +0 -0
  37. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/semver_major_only_app.py +0 -0
  38. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/examples/webhook_versioning_app.py +0 -0
  39. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/fastapi_router_versioning/py.typed +0 -0
  40. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/scripts/format.sh +0 -0
  41. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/scripts/lint.sh +0 -0
  42. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/scripts/prepare_release.py +0 -0
  43. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/scripts/test-cov-html.sh +0 -0
  44. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/scripts/test-cov.sh +0 -0
  45. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/scripts/test.sh +0 -0
  46. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/tests/__init__.py +0 -0
  47. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/tests/test_calver.py +0 -0
  48. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/tests/test_fastapi_integration.py +0 -0
  49. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/tests/test_prepare_release.py +0 -0
  50. {fastapi_router_versioning-0.2.6 → fastapi_router_versioning-0.3.0}/tests/test_semver.py +0 -0
@@ -4,7 +4,7 @@ updates:
4
4
  - package-ecosystem: "github-actions"
5
5
  directory: "/"
6
6
  schedule:
7
- interval: "weekly"
7
+ interval: "monthly"
8
8
  cooldown:
9
9
  default-days: 7
10
10
  commit-message:
@@ -21,7 +21,7 @@ updates:
21
21
  - package-ecosystem: "uv"
22
22
  directory: "/"
23
23
  schedule:
24
- interval: "weekly"
24
+ interval: "monthly"
25
25
  cooldown:
26
26
  default-days: 7
27
27
  commit-message:
@@ -31,20 +31,3 @@ updates:
31
31
  dependency-type: "development"
32
32
  patterns:
33
33
  - "*"
34
- # pre-commit
35
- - package-ecosystem: "pre-commit"
36
- directory: "/"
37
- schedule:
38
- interval: "weekly"
39
- cooldown:
40
- default-days: 7
41
- commit-message:
42
- prefix: ⬆
43
- labels:
44
- - "internal"
45
- - "dependencies"
46
- - "pre-commit"
47
- groups:
48
- pre-commit:
49
- patterns:
50
- - "*"
@@ -0,0 +1,67 @@
1
+ name: Bump pre-commit hooks
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "0 12 1 * *"
6
+ workflow_dispatch:
7
+
8
+ permissions: {}
9
+
10
+ jobs:
11
+ bump-pre-commit-hooks:
12
+ if: github.repository_owner == 'mat81black'
13
+ runs-on: ubuntu-latest
14
+ timeout-minutes: 10
15
+ steps:
16
+ - name: Dump GitHub context
17
+ env:
18
+ GITHUB_CONTEXT: ${{ toJson(github) }}
19
+ run: echo "$GITHUB_CONTEXT"
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ with:
22
+ token: ${{ secrets.FASTAPI_ROUTER_VERSIONING_LATEST_CHANGES }}
23
+ persist-credentials: true
24
+ - name: Set up Python
25
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
26
+ with:
27
+ python-version-file: ".python-version"
28
+ - name: Setup uv
29
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
30
+ with:
31
+ # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
32
+ # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
33
+ version: "0.11.18"
34
+ cache-dependency-glob: |
35
+ pyproject.toml
36
+ uv.lock
37
+ - name: Bump pre-commit hooks
38
+ run: uv run prek auto-update --freeze --cooldown-days 7
39
+ - name: Create pull request
40
+ env:
41
+ GH_TOKEN: ${{ secrets.FASTAPI_ROUTER_VERSIONING_LATEST_CHANGES }}
42
+ BASE_BRANCH: ${{ github.event.repository.default_branch }}
43
+ run: |
44
+ set -euo pipefail
45
+ if git diff --quiet; then
46
+ echo "No pre-commit hook updates available"
47
+ exit 0
48
+ fi
49
+ git config user.name "github-actions[bot]"
50
+ git config user.email "github-actions[bot]@users.noreply.github.com"
51
+ branch="bump-pre-commit-hooks"
52
+ git switch -C "$branch"
53
+ git add .pre-commit-config.yaml
54
+ git commit -m "⬆ Bump pre-commit hooks"
55
+ git push --force origin "$branch"
56
+ if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[].number')" ]; then
57
+ gh pr create \
58
+ --base "$BASE_BRANCH" \
59
+ --head "$branch" \
60
+ --title "⬆ Bump pre-commit hooks" \
61
+ --body "Bump pre-commit hook versions via \`prek auto-update --freeze --cooldown-days 7\`." \
62
+ --label internal \
63
+ --label dependencies \
64
+ --label pre-commit
65
+ else
66
+ echo "PR for \"$branch\" already open; branch updated in place."
67
+ fi
@@ -22,16 +22,16 @@ jobs:
22
22
  env:
23
23
  GITHUB_CONTEXT: ${{ toJson(github) }}
24
24
  run: echo "$GITHUB_CONTEXT"
25
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
26
  with:
27
27
  ref: ${{ github.event.repository.default_branch }}
28
28
  persist-credentials: true
29
29
  - name: Set up Python
30
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
30
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
31
31
  with:
32
32
  python-version-file: ".python-version"
33
33
  - name: Install uv
34
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
34
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
35
35
  with:
36
36
  # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
37
37
  # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
@@ -19,7 +19,7 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  timeout-minutes: 5
21
21
  steps:
22
- - uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
22
+ - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
23
23
  if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }}
24
24
  - run: echo "Done adding labels"
25
25
  # Run this after labeler applied labels
@@ -28,7 +28,7 @@ jobs:
28
28
  env:
29
29
  GITHUB_CONTEXT: ${{ toJson(github) }}
30
30
  run: echo "$GITHUB_CONTEXT"
31
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
32
  with:
33
33
  ref: ${{ github.event.repository.default_branch }}
34
34
  # To allow latest-changes to commit to the main branch
@@ -18,19 +18,19 @@ jobs:
18
18
  run: echo "$GITHUB_CONTEXT"
19
19
 
20
20
  - name: Checkout code
21
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
22
  with:
23
23
  persist-credentials: false
24
24
  # Full history required for --from-ref/--to-ref diff in prek
25
25
  fetch-depth: 0
26
26
 
27
27
  - name: Set up Python
28
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
28
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
29
29
  with:
30
30
  python-version-file: ".python-version"
31
31
 
32
32
  - name: Setup uv
33
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
33
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
34
34
  with:
35
35
  # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
36
36
  # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
@@ -34,16 +34,16 @@ jobs:
34
34
  env:
35
35
  GITHUB_CONTEXT: ${{ toJson(github) }}
36
36
  run: echo "$GITHUB_CONTEXT"
37
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
37
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
38
  with:
39
39
  token: ${{ secrets.FASTAPI_ROUTER_VERSIONING_LATEST_CHANGES }} # zizmor: ignore[secrets-outside-env]
40
40
  persist-credentials: true
41
41
  - name: Set up Python
42
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
42
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
43
43
  with:
44
44
  python-version-file: ".python-version"
45
45
  - name: Install uv
46
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
46
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
47
47
  with:
48
48
  # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
49
49
  # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
@@ -20,15 +20,15 @@ jobs:
20
20
  env:
21
21
  GITHUB_CONTEXT: ${{ toJson(github) }}
22
22
  run: echo "$GITHUB_CONTEXT"
23
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
24
  with:
25
25
  persist-credentials: false
26
26
  - name: Set up Python
27
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
27
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
28
28
  with:
29
29
  python-version-file: ".python-version"
30
30
  - name: Install uv
31
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
31
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
32
32
  with:
33
33
  # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
34
34
  # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
@@ -18,7 +18,7 @@ jobs:
18
18
  outputs:
19
19
  src: ${{ steps.filter.outputs.src }}
20
20
  steps:
21
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
21
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
22
  with:
23
23
  persist-credentials: false
24
24
  - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
@@ -46,11 +46,11 @@ jobs:
46
46
  env:
47
47
  GITHUB_CONTEXT: ${{ toJson(github) }}
48
48
  run: echo "$GITHUB_CONTEXT"
49
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
49
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
50
50
  with:
51
51
  persist-credentials: false
52
52
  - name: Set up Python
53
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
53
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
54
54
  with:
55
55
  python-version-file: ".python-version"
56
56
  - name: Install build dependencies
@@ -26,7 +26,7 @@ jobs:
26
26
  outputs:
27
27
  src: ${{ steps.filter.outputs.src }}
28
28
  steps:
29
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
29
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
30
  with:
31
31
  persist-credentials: false
32
32
  # For pull requests it's not necessary to checkout the code but for the main branch it is
@@ -72,17 +72,17 @@ jobs:
72
72
  GITHUB_CONTEXT: ${{ toJson(github) }}
73
73
  run: echo "$GITHUB_CONTEXT"
74
74
 
75
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
75
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
76
76
  with:
77
77
  persist-credentials: false
78
78
 
79
79
  - name: Set up Python
80
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
80
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
81
81
  with:
82
82
  python-version: ${{ matrix.python-version }}
83
83
 
84
84
  - name: Setup uv
85
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
85
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
86
86
  with:
87
87
  # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
88
88
  # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
@@ -130,12 +130,12 @@ jobs:
130
130
  GITHUB_CONTEXT: ${{ toJson(github) }}
131
131
  run: echo "$GITHUB_CONTEXT"
132
132
 
133
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
133
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
134
134
  with:
135
135
  persist-credentials: false
136
136
 
137
137
  - name: Setup uv
138
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
138
+ uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
139
139
  with:
140
140
  # Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
141
141
  # See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
@@ -18,8 +18,8 @@ jobs:
18
18
  security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
19
19
  steps:
20
20
  - name: Checkout repository
21
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
22
  with:
23
23
  persist-credentials: false
24
24
  - name: Run zizmor
25
- uses: zizmorcore/zizmor-action@192e21d79ab29983730a13d1382995c2307fbcaa # v0.5.7
25
+ uses: zizmorcore/zizmor-action@6599ee8b7a49aef6a770f63d261d214911a7ce02 # v0.6.0
@@ -15,7 +15,7 @@ repos:
15
15
  - id: trailing-whitespace
16
16
 
17
17
  - repo: https://github.com/crate-ci/typos
18
- rev: b859c0df7f391deba73030f79b957e62b4d81dc6 # varcon-core-v5.0.7
18
+ rev: bee27e3a4fd1ea2111cf90ab89cd076c870fce14 # frozen: v1.48.0
19
19
  hooks:
20
20
  - id: typos
21
21
  args: [ --force-exclude ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-router-versioning
3
- Version: 0.2.6
3
+ Version: 0.3.0
4
4
  Summary: Router-based API versioning for FastAPI, with per-version docs and a declarative route lifecycle.
5
5
  Project-URL: Homepage, https://github.com/mat81black/fastapi-router-versioning
6
6
  Project-URL: Repository, https://github.com/mat81black/fastapi-router-versioning
@@ -49,7 +49,6 @@ Running multiple API versions side by side usually means duplicating routers, ha
49
49
  - **Latest alias**: expose the newest version under a fixed `/latest` prefix clients can pin to
50
50
  - **Self-hosted docs assets**: point Swagger UI and ReDoc at your own JS/CSS for air-gapped deployments
51
51
  - **Reverse proxy and sub-app aware**: doc URLs pick up the ASGI `root_path` at request time
52
- - **Configurable validation error status**: swap FastAPI's default `422` for any code, applied consistently to runtime responses and every OpenAPI schema, including the app's own root schema
53
52
  - **Route composition support**: works across nested routers, WebSockets, `Depends`, and OpenAPI Callbacks
54
53
 
55
54
  ---
@@ -154,6 +153,15 @@ def legacy_route():
154
153
  A route without `@api_version` isn't excluded, it falls back to `default_version`
155
154
  (`(1, 0)` for SemVer, `"1"` for CalVer, unless overridden).
156
155
 
156
+ FastAPI's own `deprecated=True` (set directly on a route, or inherited from
157
+ `APIRouter(deprecated=True)`) is preserved as-is: `RouterVersioner` copies it into every
158
+ version unconditionally, on top of whatever `deprecate_in` computes. `deprecate_in` only
159
+ ever turns deprecation *on* for versions at or after its boundary, it never turns off a
160
+ `deprecated=True` that was already set natively. If you set both on the same route, it
161
+ shows as deprecated in every version, including ones before `deprecate_in`'s boundary.
162
+ Use one or the other: `deprecated=True` for an unconditional, version-independent flag,
163
+ `deprecate_in` for a per-version lifecycle.
164
+
157
165
  ---
158
166
 
159
167
  ## `RouterVersioner` reference
@@ -180,8 +188,6 @@ A route without `@api_version` isn't excluded, it falls back to `default_version
180
188
  | `redoc_js_url` | `str \| None` | FastAPI CDN | Custom URL for the ReDoc JS bundle |
181
189
  | `redoc_favicon_url` | `str \| None` | FastAPI favicon | Custom URL for the ReDoc favicon |
182
190
  | `redoc_with_google_fonts` | `bool` | `True` | Set `False` to stop ReDoc from loading Google Fonts |
183
- | `validation_error_code` | `int` | `422` | Status code returned for request validation errors; also replaces the `422` entry everywhere it appears in the OpenAPI schema, root schema included |
184
- | `handle_validation_exceptions` | `bool` | `True` | Set `False` to only patch the schema and register your own `RequestValidationError` handler |
185
191
 
186
192
  `.versionize()` returns the list of versions it activated. It can only be called once per
187
193
  instance; a second call raises `RuntimeError`, since it mutates the live FastAPI app in a way
@@ -290,6 +296,41 @@ RouterVersioner(
290
296
  ).versionize()
291
297
  ```
292
298
 
299
+ ### Custom validation error status code
300
+
301
+ Changing FastAPI's default `422` for request validation errors is not something
302
+ `RouterVersioner` does itself: it's a separate, general-purpose concern, handled by the
303
+ [fastapi-validation-override](https://pypi.org/project/fastapi-validation-override/) package.
304
+ `openapi_hook` is the integration point: it lets you re-apply the same patch to every
305
+ per-version schema that `RouterVersioner` generates, so all of them, root schema included,
306
+ stay consistent.
307
+
308
+ ```bash
309
+ uv add fastapi-validation-override
310
+ ```
311
+
312
+ ```python
313
+ from fastapi_validation_override import override_validation_error, patch_422_responses
314
+
315
+ # 1. Registers the runtime handler and patches the app's own root /openapi.json.
316
+ override_validation_error(app, status_code=400)
317
+
318
+ # 2. Re-applies the same patch to each version's own schema.
319
+ def versioning_openapi_hook(schema: dict, version: tuple[int, int]) -> dict:
320
+ return patch_422_responses(schema, "400")
321
+
322
+ RouterVersioner(
323
+ app=app,
324
+ routers=router,
325
+ version_format=VersionFormat.SEMVER,
326
+ openapi_hook=versioning_openapi_hook,
327
+ ).versionize()
328
+ # Validation failures now return 400, both at runtime and in every schema
329
+ # (root, and every /vX_Y/openapi.json)
330
+ ```
331
+
332
+ See [`examples/validation_override_integration_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_override_integration_app.py).
333
+
293
334
  ### OpenAPI Callbacks and Webhooks
294
335
 
295
336
  Route-level **Callbacks** need no special handling: a `callbacks=[...]` argument on a route
@@ -373,63 +414,18 @@ RouterVersioner(
373
414
  [`examples/download_static_assets.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/download_static_assets.py) downloads the required files in one step;
374
415
  [`examples/self_hosted_docs_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/self_hosted_docs_app.py) wires them into a full app.
375
416
 
376
- ### Validation error status code
377
-
378
- `validation_error_code` changes what FastAPI returns for a failed request body/query/path
379
- validation, `422` by default, both at runtime and in the schema:
380
-
381
- ```python
382
- RouterVersioner(
383
- app=app,
384
- routers=router,
385
- version_format=VersionFormat.SEMVER,
386
- validation_error_code=400,
387
- ).versionize()
388
- # Validation failures now return 400; every 422 entry in the schema becomes 400
389
- ```
390
-
391
- Set `handle_validation_exceptions=False` to keep the schema change while writing your own
392
- response body:
393
-
394
- ```python
395
- from fastapi import Request
396
- from fastapi.exceptions import RequestValidationError
397
- from fastapi.responses import JSONResponse
398
-
399
- @app.exception_handler(RequestValidationError)
400
- async def my_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
401
- return JSONResponse(status_code=400, content={"errors": exc.errors()})
402
-
403
- RouterVersioner(
404
- app=app,
405
- routers=router,
406
- version_format=VersionFormat.SEMVER,
407
- validation_error_code=400,
408
- handle_validation_exceptions=False, # schema updated; the handler above owns the response
409
- ).versionize()
410
- ```
411
-
412
- This also patches the app's own root `/docs`, `/redoc`, and `/openapi.json`, not just the
413
- versioned ones, so there's nothing left showing the stale `422`.
414
-
415
417
  Sharing one app across several `RouterVersioner` instances only makes sense for one reason:
416
418
  mixing `version_format` values, SemVer for one group of routes and CalVer for another, on
417
419
  the same app. Splitting modules that share a `version_format` doesn't need a second instance;
418
420
  pass them all to one `RouterVersioner` via `routers=[...]` instead (see
419
421
  [`multi_router_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/multi_router_app.py)).
420
- If you do share an app across instances, two rules are enforced for you:
421
-
422
- - Every instance with `handle_validation_exceptions=True` must agree on
423
- `validation_error_code`. FastAPI's exception handler is registered once per app, not per
424
- router, so a mismatch raises `RuntimeError` when the second instance is constructed rather
425
- than silently keeping whichever handler was registered first.
426
- - Every instance needs its own `prefix_format`/`latest_prefix`. Two instances that resolve to
427
- the same prefix would otherwise overwrite each other's docs/openapi routes at the same
428
- path; this raises `RuntimeError` too.
422
+ If you do share an app across instances, one rule is enforced for you: every instance needs
423
+ its own `prefix_format`/`latest_prefix`. Two instances that resolve to the same prefix would
424
+ otherwise overwrite each other's docs/openapi routes at the same path; this raises
425
+ `RuntimeError`.
429
426
 
430
- For modules that genuinely don't need to coordinate at all, including a different
431
- `validation_error_code` each, mount them as separate FastAPI sub-applications instead (next
432
- section).
427
+ For modules that genuinely don't need to coordinate at all, mount them as separate FastAPI
428
+ sub-applications instead (next section).
433
429
 
434
430
  ### Reverse proxy and sub-application mounting
435
431
 
@@ -444,7 +440,7 @@ parent.mount("/api", app) # root_path="/api" is injected per request
444
440
 
445
441
  A mounted sub-application is a separate `FastAPI()` instance with its own `app.state`, so a
446
442
  `RouterVersioner` attached to it is entirely independent from one attached to the parent, or
447
- to another sub-application, no shared `validation_error_code`, no prefix coordination needed.
443
+ to another sub-application, no prefix coordination needed.
448
444
  See [`examples/mounted_subapps_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/mounted_subapps_app.py).
449
445
 
450
446
  ### Callback hook
@@ -477,9 +473,8 @@ RouterVersioner(
477
473
  | [`multi_router_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/multi_router_app.py) | Several routers versioned together under one instance |
478
474
  | [`self_hosted_docs_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/self_hosted_docs_app.py) | Swagger UI and ReDoc served from local static assets |
479
475
  | [`openapi_hook_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/openapi_hook_app.py) | Per-version OpenAPI schema edits via `openapi_hook` |
480
- | [`validation_error_code_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_error_code_app.py) | `400` instead of `422`, handled automatically by `RouterVersioner` |
481
- | [`validation_error_code_custom_handler_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_error_code_custom_handler_app.py) | Same, but with `handle_validation_exceptions=False` and a custom handler |
482
476
  | [`mounted_subapps_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/mounted_subapps_app.py) | Independently versioned modules as separate `app.mount()` sub-applications |
477
+ | [`validation_override_integration_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_override_integration_app.py) | Custom validation error status code via `fastapi-validation-override` and `openapi_hook` |
483
478
 
484
479
  ---
485
480
 
@@ -18,7 +18,6 @@ Running multiple API versions side by side usually means duplicating routers, ha
18
18
  - **Latest alias**: expose the newest version under a fixed `/latest` prefix clients can pin to
19
19
  - **Self-hosted docs assets**: point Swagger UI and ReDoc at your own JS/CSS for air-gapped deployments
20
20
  - **Reverse proxy and sub-app aware**: doc URLs pick up the ASGI `root_path` at request time
21
- - **Configurable validation error status**: swap FastAPI's default `422` for any code, applied consistently to runtime responses and every OpenAPI schema, including the app's own root schema
22
21
  - **Route composition support**: works across nested routers, WebSockets, `Depends`, and OpenAPI Callbacks
23
22
 
24
23
  ---
@@ -123,6 +122,15 @@ def legacy_route():
123
122
  A route without `@api_version` isn't excluded, it falls back to `default_version`
124
123
  (`(1, 0)` for SemVer, `"1"` for CalVer, unless overridden).
125
124
 
125
+ FastAPI's own `deprecated=True` (set directly on a route, or inherited from
126
+ `APIRouter(deprecated=True)`) is preserved as-is: `RouterVersioner` copies it into every
127
+ version unconditionally, on top of whatever `deprecate_in` computes. `deprecate_in` only
128
+ ever turns deprecation *on* for versions at or after its boundary, it never turns off a
129
+ `deprecated=True` that was already set natively. If you set both on the same route, it
130
+ shows as deprecated in every version, including ones before `deprecate_in`'s boundary.
131
+ Use one or the other: `deprecated=True` for an unconditional, version-independent flag,
132
+ `deprecate_in` for a per-version lifecycle.
133
+
126
134
  ---
127
135
 
128
136
  ## `RouterVersioner` reference
@@ -149,8 +157,6 @@ A route without `@api_version` isn't excluded, it falls back to `default_version
149
157
  | `redoc_js_url` | `str \| None` | FastAPI CDN | Custom URL for the ReDoc JS bundle |
150
158
  | `redoc_favicon_url` | `str \| None` | FastAPI favicon | Custom URL for the ReDoc favicon |
151
159
  | `redoc_with_google_fonts` | `bool` | `True` | Set `False` to stop ReDoc from loading Google Fonts |
152
- | `validation_error_code` | `int` | `422` | Status code returned for request validation errors; also replaces the `422` entry everywhere it appears in the OpenAPI schema, root schema included |
153
- | `handle_validation_exceptions` | `bool` | `True` | Set `False` to only patch the schema and register your own `RequestValidationError` handler |
154
160
 
155
161
  `.versionize()` returns the list of versions it activated. It can only be called once per
156
162
  instance; a second call raises `RuntimeError`, since it mutates the live FastAPI app in a way
@@ -259,6 +265,41 @@ RouterVersioner(
259
265
  ).versionize()
260
266
  ```
261
267
 
268
+ ### Custom validation error status code
269
+
270
+ Changing FastAPI's default `422` for request validation errors is not something
271
+ `RouterVersioner` does itself: it's a separate, general-purpose concern, handled by the
272
+ [fastapi-validation-override](https://pypi.org/project/fastapi-validation-override/) package.
273
+ `openapi_hook` is the integration point: it lets you re-apply the same patch to every
274
+ per-version schema that `RouterVersioner` generates, so all of them, root schema included,
275
+ stay consistent.
276
+
277
+ ```bash
278
+ uv add fastapi-validation-override
279
+ ```
280
+
281
+ ```python
282
+ from fastapi_validation_override import override_validation_error, patch_422_responses
283
+
284
+ # 1. Registers the runtime handler and patches the app's own root /openapi.json.
285
+ override_validation_error(app, status_code=400)
286
+
287
+ # 2. Re-applies the same patch to each version's own schema.
288
+ def versioning_openapi_hook(schema: dict, version: tuple[int, int]) -> dict:
289
+ return patch_422_responses(schema, "400")
290
+
291
+ RouterVersioner(
292
+ app=app,
293
+ routers=router,
294
+ version_format=VersionFormat.SEMVER,
295
+ openapi_hook=versioning_openapi_hook,
296
+ ).versionize()
297
+ # Validation failures now return 400, both at runtime and in every schema
298
+ # (root, and every /vX_Y/openapi.json)
299
+ ```
300
+
301
+ See [`examples/validation_override_integration_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_override_integration_app.py).
302
+
262
303
  ### OpenAPI Callbacks and Webhooks
263
304
 
264
305
  Route-level **Callbacks** need no special handling: a `callbacks=[...]` argument on a route
@@ -342,63 +383,18 @@ RouterVersioner(
342
383
  [`examples/download_static_assets.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/download_static_assets.py) downloads the required files in one step;
343
384
  [`examples/self_hosted_docs_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/self_hosted_docs_app.py) wires them into a full app.
344
385
 
345
- ### Validation error status code
346
-
347
- `validation_error_code` changes what FastAPI returns for a failed request body/query/path
348
- validation, `422` by default, both at runtime and in the schema:
349
-
350
- ```python
351
- RouterVersioner(
352
- app=app,
353
- routers=router,
354
- version_format=VersionFormat.SEMVER,
355
- validation_error_code=400,
356
- ).versionize()
357
- # Validation failures now return 400; every 422 entry in the schema becomes 400
358
- ```
359
-
360
- Set `handle_validation_exceptions=False` to keep the schema change while writing your own
361
- response body:
362
-
363
- ```python
364
- from fastapi import Request
365
- from fastapi.exceptions import RequestValidationError
366
- from fastapi.responses import JSONResponse
367
-
368
- @app.exception_handler(RequestValidationError)
369
- async def my_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
370
- return JSONResponse(status_code=400, content={"errors": exc.errors()})
371
-
372
- RouterVersioner(
373
- app=app,
374
- routers=router,
375
- version_format=VersionFormat.SEMVER,
376
- validation_error_code=400,
377
- handle_validation_exceptions=False, # schema updated; the handler above owns the response
378
- ).versionize()
379
- ```
380
-
381
- This also patches the app's own root `/docs`, `/redoc`, and `/openapi.json`, not just the
382
- versioned ones, so there's nothing left showing the stale `422`.
383
-
384
386
  Sharing one app across several `RouterVersioner` instances only makes sense for one reason:
385
387
  mixing `version_format` values, SemVer for one group of routes and CalVer for another, on
386
388
  the same app. Splitting modules that share a `version_format` doesn't need a second instance;
387
389
  pass them all to one `RouterVersioner` via `routers=[...]` instead (see
388
390
  [`multi_router_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/multi_router_app.py)).
389
- If you do share an app across instances, two rules are enforced for you:
390
-
391
- - Every instance with `handle_validation_exceptions=True` must agree on
392
- `validation_error_code`. FastAPI's exception handler is registered once per app, not per
393
- router, so a mismatch raises `RuntimeError` when the second instance is constructed rather
394
- than silently keeping whichever handler was registered first.
395
- - Every instance needs its own `prefix_format`/`latest_prefix`. Two instances that resolve to
396
- the same prefix would otherwise overwrite each other's docs/openapi routes at the same
397
- path; this raises `RuntimeError` too.
391
+ If you do share an app across instances, one rule is enforced for you: every instance needs
392
+ its own `prefix_format`/`latest_prefix`. Two instances that resolve to the same prefix would
393
+ otherwise overwrite each other's docs/openapi routes at the same path; this raises
394
+ `RuntimeError`.
398
395
 
399
- For modules that genuinely don't need to coordinate at all, including a different
400
- `validation_error_code` each, mount them as separate FastAPI sub-applications instead (next
401
- section).
396
+ For modules that genuinely don't need to coordinate at all, mount them as separate FastAPI
397
+ sub-applications instead (next section).
402
398
 
403
399
  ### Reverse proxy and sub-application mounting
404
400
 
@@ -413,7 +409,7 @@ parent.mount("/api", app) # root_path="/api" is injected per request
413
409
 
414
410
  A mounted sub-application is a separate `FastAPI()` instance with its own `app.state`, so a
415
411
  `RouterVersioner` attached to it is entirely independent from one attached to the parent, or
416
- to another sub-application, no shared `validation_error_code`, no prefix coordination needed.
412
+ to another sub-application, no prefix coordination needed.
417
413
  See [`examples/mounted_subapps_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/mounted_subapps_app.py).
418
414
 
419
415
  ### Callback hook
@@ -446,9 +442,8 @@ RouterVersioner(
446
442
  | [`multi_router_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/multi_router_app.py) | Several routers versioned together under one instance |
447
443
  | [`self_hosted_docs_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/self_hosted_docs_app.py) | Swagger UI and ReDoc served from local static assets |
448
444
  | [`openapi_hook_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/openapi_hook_app.py) | Per-version OpenAPI schema edits via `openapi_hook` |
449
- | [`validation_error_code_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_error_code_app.py) | `400` instead of `422`, handled automatically by `RouterVersioner` |
450
- | [`validation_error_code_custom_handler_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_error_code_custom_handler_app.py) | Same, but with `handle_validation_exceptions=False` and a custom handler |
451
445
  | [`mounted_subapps_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/mounted_subapps_app.py) | Independently versioned modules as separate `app.mount()` sub-applications |
446
+ | [`validation_override_integration_app.py`](https://github.com/mat81black/fastapi-router-versioning/blob/main/examples/validation_override_integration_app.py) | Custom validation error status code via `fastapi-validation-override` and `openapi_hook` |
452
447
 
453
448
  ---
454
449