Compare commits

...

21 Commits

Author SHA1 Message Date
github-actions[bot]
2bd97a6013 📝 Update release notes
[skip ci]
2026-05-27 20:58:15 +00:00
Yurii Motov
a3558be5f8 🔒️ Improve GitHub actions security (#15607) 2026-05-27 22:57:52 +02:00
github-actions[bot]
c9343d2cf0 📝 Update release notes
[skip ci]
2026-05-27 20:53:41 +00:00
Yurii Motov
a53efbcae9 ⚰️ Remove ruff and coverage ignores for non-existing files (#15610)
Co-authored-by: AshNicolus <yashnema52@gmail.com>
2026-05-27 22:53:15 +02:00
github-actions[bot]
ad09734fa3 📝 Update release notes
[skip ci]
2026-05-27 20:23:09 +00:00
Yurii Motov
57535ef85b Use custom changing_dir instead of CLIRunner.isolated_filesystem to set working dir (#15616) 2026-05-27 22:22:44 +02:00
github-actions[bot]
dbfd55cea3 📝 Update release notes
[skip ci]
2026-05-27 10:47:52 +00:00
Yurii Motov
59d4a80fcf Add httpx2 test dependency to avoid deprecation warning (#15603) 2026-05-27 12:47:28 +02:00
github-actions[bot]
6cbdde2315 📝 Update release notes
[skip ci]
2026-05-24 13:03:53 +00:00
dependabot[bot]
1464678ba6 ⬆ Bump the python-packages group with 15 updates (#15594)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-24 15:03:23 +02:00
github-actions[bot]
1a84bbc00b 📝 Update release notes
[skip ci]
2026-05-24 11:07:04 +00:00
Yurii Motov
3fdc54edab 👷 Configure Dependabot to group updates and update weekly (#15560) 2026-05-24 13:06:37 +02:00
github-actions[bot]
09ba2cec98 📝 Update release notes
[skip ci]
2026-05-24 11:01:06 +00:00
Alexander Li
3e3d38930e ✏️ Use Annotated in inline example in docs/en/docs/tutorial/body-multiple-params.md (#15591) 2026-05-24 13:00:37 +02:00
github-actions[bot]
84f205c8f7 📝 Update release notes
[skip ci]
2026-05-24 10:57:12 +00:00
Ömer 🇹🇷
7baefe7144 📝 Remove "NGINX Unit" from the list of ASGI-servers in docs (#15475) 2026-05-24 12:56:40 +02:00
github-actions[bot]
2895c51ba8 📝 Update release notes
[skip ci]
2026-05-24 10:29:48 +00:00
zadevhub
21c46919fc 📝 Update docs/en/docs/tutorial/security/oauth2-jwt.md (#14781)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
Co-authored-by: Yurii Motov <109919500+YuriiMotov@users.noreply.github.com>
2026-05-24 10:29:22 +00:00
Sebastián Ramírez
8206485753 🔖 Release version 0.136.3 2026-05-23 20:51:45 +02:00
github-actions[bot]
c910e0139f 📝 Update release notes
[skip ci]
2026-05-23 18:40:42 +00:00
Sebastián Ramírez
063b5bf582 ♻️ Do not accept underscore headers when using convert_underscores=True (the default) (#15589) 2026-05-23 18:35:05 +00:00
35 changed files with 726 additions and 435 deletions

View File

@@ -4,26 +4,47 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
cooldown:
default-days: 7
commit-message:
prefix:
labels:
- "internal"
- "dependencies"
- "github_actions"
groups:
github-actions:
patterns:
- "*"
# Python
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
cooldown:
default-days: 7
commit-message:
prefix:
groups:
python-packages:
dependency-type: "development"
patterns:
- "*"
# pre-commit
- package-ecosystem: "pre-commit"
directory: "/"
schedule:
interval: "daily"
interval: "weekly"
cooldown:
default-days: 7
commit-message:
prefix:
labels:
- "internal"
- "dependencies"
- "pre-commit"
groups:
pre-commit:
patterns:
- "*"

View File

@@ -13,6 +13,7 @@ jobs:
add-to-project:
name: Add to project
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/add-to-project@5afcf98fcd03f1c2f92c3c83f58ae24323cc57fd # v2.0.0
with:

View File

@@ -16,6 +16,7 @@ jobs:
# Required permissions
permissions:
pull-requests: read
timeout-minutes: 5
# Set job outputs to values from filter step
outputs:
docs: ${{ steps.filter.outputs.docs }}
@@ -42,6 +43,7 @@ jobs:
- changes
if: ${{ needs.changes.outputs.docs == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
langs: ${{ steps.show-langs.outputs.langs }}
steps:
@@ -55,6 +57,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |
@@ -73,6 +77,7 @@ jobs:
- langs
if: ${{ needs.changes.outputs.docs == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 7
strategy:
matrix:
lang: ${{ fromJson(needs.langs.outputs.langs) }}
@@ -91,6 +96,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -33,6 +33,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -16,6 +16,7 @@ jobs:
issues: write
pull-requests: write
statuses: write
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -31,6 +32,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: false
- name: Install GitHub Actions dependencies

View File

@@ -12,6 +12,7 @@ jobs:
contents: read
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check if PRs have merge conflicts
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3

View File

@@ -15,6 +15,7 @@ permissions:
jobs:
check-author:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check if author is org member or allowed bot
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

View File

@@ -23,6 +23,7 @@ jobs:
permissions:
issues: write
pull-requests: write
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:

View File

@@ -13,6 +13,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
pull-requests: write
timeout-minutes: 7
steps:
- name: Dump GitHub context
env:
@@ -28,6 +29,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -17,6 +17,7 @@ jobs:
contents: read
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }}
@@ -28,6 +29,7 @@ jobs:
permissions:
pull-requests: read
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: agilepathway/label-checker@c3d16ad512e7cea5961df85ff2486bb774caf3c5 # v1.6.65
with:

View File

@@ -22,6 +22,7 @@ jobs:
latest-changes:
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:

View File

@@ -24,6 +24,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
discussions: write
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -39,6 +40,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -33,6 +33,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -15,6 +15,7 @@ env:
jobs:
pre-commit:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -48,6 +49,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
cache-dependency-glob: |
pyproject.toml
@@ -84,6 +87,7 @@ jobs:
needs:
- pre-commit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:

View File

@@ -13,6 +13,7 @@ jobs:
permissions:
id-token: write
contents: read
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -28,7 +29,10 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: "false"
- name: Build distribution
run: uv build
- name: Publish

View File

@@ -12,6 +12,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
statuses: write
timeout-minutes: 5
steps:
- name: Dump GitHub context
@@ -27,6 +28,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
cache-dependency-glob: |
pyproject.toml

View File

@@ -18,6 +18,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -33,6 +34,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -14,6 +14,7 @@ permissions: {}
jobs:
test-redistribute:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -57,6 +58,7 @@ jobs:
needs:
- test-redistribute
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2

View File

@@ -25,6 +25,7 @@ jobs:
permissions:
pull-requests: read
# Set job outputs to values from filter step
timeout-minutes: 5
outputs:
src: ${{ steps.filter.outputs.src }}
steps:
@@ -50,6 +51,7 @@ jobs:
needs:
- changes
if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master'
timeout-minutes: 10
strategy:
matrix:
os: [ windows-latest, macos-latest ]
@@ -81,6 +83,11 @@ jobs:
uv-resolution: highest
codspeed: codspeed
deprecated-tests: "no-deprecation"
- os: ubuntu-latest
python-version: "3.13"
uv-resolution: highest
deprecated-tests: "no-deprecation"
without-httpx2: true
- os: ubuntu-latest
python-version: "3.14"
coverage: coverage
@@ -113,6 +120,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |
@@ -129,15 +138,19 @@ jobs:
- name: Install deprecated libraries just for testing
if: matrix.deprecated-tests == 'test-deprecation'
run: uv pip install orjson ujson
- name: Uninstall httpx2 to run tests with httpx
if: matrix.without-httpx2 == 'true'
run: uv pip uninstall httpx2
- name: Reinstall SQLAlchemy without Cython extensions
if: matrix.python-version == '3.14t' && matrix.os == 'ubuntu-latest'
run: "DISABLE_SQLALCHEMY_CEXT=1 uv pip install --force-reinstall --no-binary :all: sqlalchemy"
- run: mkdir coverage
- name: Test
run: uv run --no-sync bash scripts/test-cov.sh
run: uv run --no-sync bash scripts/test-cov.sh $PYTEST_OPTIONS
env:
COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.deprecated-tests}}
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.deprecated-tests}}
PYTEST_OPTIONS: ${{ (matrix.without-httpx2 == 'true') && '-W ignore::UserWarning' || '' }}
# Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow
- name: Store coverage files
if: matrix.coverage == 'coverage'
@@ -152,6 +165,7 @@ jobs:
- changes
if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 5
env:
UV_PYTHON: "3.13"
UV_RESOLUTION: highest
@@ -170,6 +184,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |
@@ -187,6 +203,7 @@ jobs:
needs:
- test
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -201,6 +218,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |
@@ -232,6 +251,7 @@ jobs:
- coverage-combine
- benchmark
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:

View File

@@ -13,6 +13,7 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 5
steps:
- name: Dump GitHub context
env:
@@ -28,6 +29,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
enable-cache: true
cache-dependency-glob: |

View File

@@ -60,6 +60,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
cache-dependency-glob: |
pyproject.toml
@@ -101,6 +103,8 @@ jobs:
- name: Setup uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.4"
cache-dependency-glob: |
pyproject.toml

24
.github/workflows/zizmor.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Zizmor
on:
push:
branches:
- main
workflow_dispatch:
permissions: {}
jobs:
zizmor:
name: Run zizmor
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3

View File

@@ -96,6 +96,6 @@ repos:
name: zizmor
language: python
entry: uv run zizmor .
files: ^\.github\/workflows\/
files: ^\.github/workflows/|^uv\.lock$
require_serial: true
pass_filenames: false

View File

@@ -56,7 +56,6 @@ There are several alternatives, including:
* [Hypercorn](https://hypercorn.readthedocs.io/): an ASGI server compatible with HTTP/2 and Trio among other features.
* [Daphne](https://github.com/django/daphne): the ASGI server built for Django Channels.
* [Granian](https://github.com/emmett-framework/granian): A Rust HTTP server for Python applications.
* [NGINX Unit](https://unit.nginx.org/howto/fastapi/): NGINX Unit is a lightweight and versatile web application runtime.
## Server Machine and Server Program { #server-machine-and-server-program }

View File

@@ -7,6 +7,27 @@ hide:
## Latest Changes
### Docs
* ✏️ Use `Annotated` in inline example in `docs/en/docs/tutorial/body-multiple-params.md`. PR [#15591](https://github.com/fastapi/fastapi/pull/15591) by [@TheArchons](https://github.com/TheArchons).
* 📝 Remove "NGINX Unit" from the list of ASGI-servers in docs. PR [#15475](https://github.com/fastapi/fastapi/pull/15475) by [@angryfoxx](https://github.com/angryfoxx).
* 📝 Update `docs/en/docs/tutorial/security/oauth2-jwt.md`. PR [#14781](https://github.com/fastapi/fastapi/pull/14781) by [@zadevhub](https://github.com/zadevhub).
### Internal
* 🔒️ Improve GitHub actions security. PR [#15607](https://github.com/fastapi/fastapi/pull/15607) by [@YuriiMotov](https://github.com/YuriiMotov).
* ⚰️ Remove ruff and coverage ignores for non-existing files. PR [#15610](https://github.com/fastapi/fastapi/pull/15610) by [@YuriiMotov](https://github.com/YuriiMotov).
* ✅ Use custom `changing_dir` instead of `CLIRunner.isolated_filesystem` to set working dir. PR [#15616](https://github.com/fastapi/fastapi/pull/15616) by [@YuriiMotov](https://github.com/YuriiMotov).
* ✅ Add `httpx2` test dependency to avoid deprecation warning. PR [#15603](https://github.com/fastapi/fastapi/pull/15603) by [@YuriiMotov](https://github.com/YuriiMotov).
* ⬆ Bump the python-packages group with 15 updates. PR [#15594](https://github.com/fastapi/fastapi/pull/15594) by [@dependabot[bot]](https://github.com/apps/dependabot).
* 👷 Configure Dependabot to group updates and update weekly. PR [#15560](https://github.com/fastapi/fastapi/pull/15560) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.136.3 (2026-05-23)
### Refactors
* ♻️ Do not accept underscore headers when using `convert_underscores=True` (the default). PR [#15589](https://github.com/fastapi/fastapi/pull/15589) by [@tiangolo](https://github.com/tiangolo).
## 0.136.2 (2026-05-23)
### Refactors

View File

@@ -126,7 +126,7 @@ By default, **FastAPI** will then expect its body directly.
But if you want it to expect a JSON with a key `item` and inside of it the model contents, as it does when you declare extra body parameters, you can use the special `Body` parameter `embed`:
```Python
item: Item = Body(embed=True)
item: Annotated[Item, Body(embed=True)]
```
as in:

View File

@@ -18,7 +18,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4
It is not encrypted, so, anyone could recover the information from the contents.
But it's signed. So, when you receive a token that you emitted, you can verify that you actually emitted it.
But it's signed. So, when you receive a token that you issued, you can verify that it was you who issued it.
That way, you can create a token with an expiration of, let's say, 1 week. And then when the user comes back the next day with the token, you know that user is still logged in to your system.

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.136.2"
__version__ = "0.136.3"
from starlette import status as status

View File

@@ -826,6 +826,10 @@ def request_params_to_args(
if value is not None:
params_to_process[get_validation_alias(field)] = value
processed_keys.add(alias or get_validation_alias(field))
# For headers with convert_underscores=True, mark both the converted
# header name and the original field alias as processed to avoid
# accepting the original alias as an extra header.
processed_keys.add(get_validation_alias(field))
for key in received_params.keys():
if key not in processed_keys:

View File

@@ -146,6 +146,7 @@ docs = [
]
docs-tests = [
"httpx >=0.23.0,<1.0.0",
"httpx2>=2.0.0",
"ruff >=0.14.14",
]
github-actions = [
@@ -242,7 +243,6 @@ relative_files = true
context = '${CONTEXT}'
omit = [
"tests/benchmarks/*",
"docs_src/response_model/tutorial003_04_py39.py",
"docs_src/response_model/tutorial003_04_py310.py",
"docs_src/dependencies/tutorial013_an_py310.py", # temporary code example?
"docs_src/dependencies/tutorial014_an_py310.py", # temporary code example?
@@ -279,41 +279,21 @@ ignore = [
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"]
"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"]
"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"]
"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"]
"docs_src/custom_response/tutorial007_py310.py" = ["B007"]
"docs_src/custom_response/tutorial007_py39.py" = ["B007"]
"docs_src/dataclasses/tutorial003_py39.py" = ["I001"]
"docs_src/dependencies/tutorial007_py310.py" = ["F821"]
"docs_src/dependencies/tutorial007_py39.py" = ["F821"]
"docs_src/dependencies/tutorial008_an_py310.py" = ["F821"]
"docs_src/dependencies/tutorial008_an_py39.py" = ["F821"]
"docs_src/dependencies/tutorial008_py310.py" = ["F821"]
"docs_src/dependencies/tutorial008_py39.py" = ["F821"]
"docs_src/dependencies/tutorial008b_an_py310.py" = ["B904"]
"docs_src/dependencies/tutorial008b_an_py39.py" = ["B904"]
"docs_src/dependencies/tutorial008b_py310.py" = ["B904"]
"docs_src/dependencies/tutorial008b_py39.py" = ["B904"]
"docs_src/dependencies/tutorial009_py310.py" = ["F821"]
"docs_src/dependencies/tutorial009_py39.py" = ["F821"]
"docs_src/dependencies/tutorial010_py310.py" = ["F821"]
"docs_src/dependencies/tutorial010_py39.py" = ["F821"]
"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"]
"docs_src/path_operation_advanced_configuration/tutorial007_py310.py" = ["B904"]
"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"]
"docs_src/query_params_str_validations/tutorial012_an_py310.py" = ["B006"]
"docs_src/query_params_str_validations/tutorial012_an_py39.py" = ["B006"]
"docs_src/query_params_str_validations/tutorial013_an_py310.py" = ["B006"]
"docs_src/query_params_str_validations/tutorial013_an_py39.py" = ["B006"]
"docs_src/security/tutorial004_an_py310.py" = ["B904"]
"docs_src/security/tutorial004_an_py39.py" = ["B904"]
"docs_src/security/tutorial004_py310.py" = ["B904"]
"docs_src/security/tutorial004_py39.py" = ["B904"]
"docs_src/security/tutorial005_an_py310.py" = ["B904"]
"docs_src/security/tutorial005_an_py39.py" = ["B904"]
"docs_src/security/tutorial005_py310.py" = ["B904"]
"docs_src/security/tutorial005_py39.py" = ["B904"]
"docs_src/json_base64_bytes/tutorial001_py310.py" = ["UP012"]
"docs_src/stream_json_lines/tutorial001_py310.py" = ["UP028"]
"docs_src/stream_data/tutorial001_py310.py" = ["UP028"]

View File

@@ -1,5 +1,8 @@
import os
import shutil
import sys
from collections.abc import Generator
from contextlib import contextmanager
from pathlib import Path
import pytest
@@ -23,11 +26,20 @@ def pytest_collection_modifyitems(config, items: list[pytest.Item]) -> None:
item.add_marker(skip_on_windows)
@contextmanager
def changing_dir(directory: str | Path) -> Generator[None, None, None]:
initial_dir = os.getcwd()
os.chdir(directory)
try:
yield
finally:
os.chdir(initial_dir)
@pytest.fixture(name="runner")
def get_runner():
runner = CliRunner()
with runner.isolated_filesystem():
yield runner
def get_runner(tmp_path: Path):
with changing_dir(tmp_path):
yield CliRunner()
@pytest.fixture(name="root_dir")

View File

@@ -11,6 +11,10 @@ class Model(BaseModel):
model_config = {"extra": "allow"}
class AuthHeaders(BaseModel):
x_user_id: str
@app.get("/query")
async def query_model_with_extra(data: Model = Query()):
return data
@@ -26,6 +30,11 @@ async def cookies_model_with_extra(data: Model = Cookie()):
return data
@app.get("/header-requires-hyphen")
async def header_model_requires_hyphen(data: AuthHeaders = Header()):
return data
def test_query_pass_extra_list():
client = TestClient(app)
resp = client.get(
@@ -91,6 +100,32 @@ def test_header_pass_extra_single():
assert resp_json["param2"] == "456"
def test_header_model_prefers_hyphenated_header_with_convert_underscores():
client = TestClient(app)
resp = client.get(
"/header-requires-hyphen",
headers=[
("x-user-id", "hyphenated-value"),
("x_user_id", "underscore-value"),
],
)
assert resp.status_code == 200
assert resp.json() == {"x_user_id": "hyphenated-value"}
def test_header_model_rejects_underscore_header_with_convert_underscores():
client = TestClient(app)
resp = client.get(
"/header-requires-hyphen", headers={"x_user_id": "underscore-value"}
)
assert resp.status_code == 422
assert resp.json()["detail"][0]["loc"] == ["header", "x_user_id"]
def test_cookie_pass_extra_list():
client = TestClient(app)
client.cookies = [

View File

@@ -1,6 +1,7 @@
import importlib
import pytest
from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@@ -68,7 +69,9 @@ def test_header_param_model_invalid(client: TestClient):
"x_tag": [],
"host": "testserver",
"accept": "*/*",
"accept-encoding": "gzip, deflate",
"accept-encoding": IsOneOf(
"gzip, deflate", "gzip, deflate, zstd"
),
"connection": "keep-alive",
"user-agent": "testclient",
},

View File

@@ -1,6 +1,7 @@
import importlib
import pytest
from dirty_equals import IsOneOf
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
@@ -66,7 +67,9 @@ def test_header_param_model_no_underscore(client: TestClient):
"traceparent": "123",
"x_tag": [],
"accept": "*/*",
"accept-encoding": "gzip, deflate",
"accept-encoding": IsOneOf(
"gzip, deflate", "gzip, deflate, zstd"
),
"connection": "keep-alive",
"user-agent": "testclient",
"save-data": "true",
@@ -105,7 +108,9 @@ def test_header_param_model_invalid(client: TestClient):
"x_tag": [],
"host": "testserver",
"accept": "*/*",
"accept-encoding": "gzip, deflate",
"accept-encoding": IsOneOf(
"gzip, deflate", "gzip, deflate, zstd"
),
"connection": "keep-alive",
"user-agent": "testclient",
},

913
uv.lock generated
View File

File diff suppressed because it is too large Load Diff