Browse Source

Merge remote-tracking branch 'upstream/master' into fix-nested-annotated-types-in-field_annotation_is_scalar_sequence

pull/14874/head
Yurii Motov 4 months ago
parent
commit
20b848cf6b
  1. 9
      .github/workflows/test-redistribute.yml
  2. 7
      .github/workflows/test.yml
  3. 10
      README.md
  4. 14
      docs/de/docs/_llm-test.md
  5. 10
      docs/en/docs/_llm-test.md
  6. 61
      docs/en/docs/advanced/advanced-python-types.md
  7. 2
      docs/en/docs/advanced/dataclasses.md
  8. 2
      docs/en/docs/advanced/path-operation-advanced-configuration.md
  9. 8
      docs/en/docs/alternatives.md
  10. 5
      docs/en/docs/css/custom.css
  11. 10
      docs/en/docs/deployment/docker.md
  12. 2
      docs/en/docs/deployment/https.md
  13. 10
      docs/en/docs/features.md
  14. 10
      docs/en/docs/index.md
  15. 162
      docs/en/docs/python-types.md
  16. 51
      docs/en/docs/release-notes.md
  17. 7
      docs/en/docs/tutorial/body-multiple-params.md
  18. 2
      docs/en/docs/tutorial/body.md
  19. 8
      docs/en/docs/tutorial/cookie-param-models.md
  20. 24
      docs/en/docs/tutorial/dependencies/classes-as-dependencies.md
  21. 2
      docs/en/docs/tutorial/dependencies/dependencies-with-yield.md
  22. 2
      docs/en/docs/tutorial/dependencies/index.md
  23. 6
      docs/en/docs/tutorial/dependencies/sub-dependencies.md
  24. 4
      docs/en/docs/tutorial/extra-models.md
  25. 4
      docs/en/docs/tutorial/first-steps.md
  26. 4
      docs/en/docs/tutorial/path-operation-configuration.md
  27. 8
      docs/en/docs/tutorial/path-params.md
  28. 34
      docs/en/docs/tutorial/query-params-str-validations.md
  29. 2
      docs/en/docs/tutorial/query-params.md
  30. 2
      docs/en/docs/tutorial/request-forms.md
  31. 2
      docs/en/docs/tutorial/response-model.md
  32. 2
      docs/en/docs/tutorial/schema-extra-example.md
  33. 2
      docs/en/docs/virtual-environments.md
  34. 1
      docs/en/mkdocs.yml
  35. 14
      docs/es/docs/_llm-test.md
  36. 12
      docs/ko/docs/_llm-test.md
  37. 18
      docs/pt/docs/_llm-test.md
  38. 10
      docs/ru/docs/_llm-test.md
  39. 54
      fastapi-slim/README.md
  40. 2
      fastapi/__init__.py
  41. 30
      fastapi/_compat/shared.py
  42. 26
      fastapi/_compat/v2.py
  43. 354
      fastapi/applications.py
  44. 3
      fastapi/background.py
  45. 10
      fastapi/datastructures.py
  46. 32
      fastapi/dependencies/models.py
  47. 92
      fastapi/dependencies/utils.py
  48. 13
      fastapi/encoders.py
  49. 16
      fastapi/exceptions.py
  50. 8
      fastapi/openapi/docs.py
  51. 326
      fastapi/openapi/models.py
  52. 31
      fastapi/openapi/utils.py
  53. 380
      fastapi/param_functions.py
  54. 446
      fastapi/params.py
  55. 437
      fastapi/routing.py
  56. 26
      fastapi/security/api_key.py
  57. 36
      fastapi/security/http.py
  58. 42
      fastapi/security/oauth2.py
  59. 8
      fastapi/security/open_id_connect_url.py
  60. 5
      fastapi/security/utils.py
  61. 7
      fastapi/types.py
  62. 18
      fastapi/utils.py
  63. 28
      pdm_build.py
  64. 26
      pyproject.toml
  65. 64
      scripts/general-llm-prompt.md
  66. 8
      tests/test_dependencies_utils.py
  67. 46
      tests/test_list_bytes_file_order_preserved_issue_14811.py
  68. 6
      tests/test_request_params/test_path/test_required_str.py
  69. 12
      tests/test_router_circular_import.py
  70. 60
      tests/test_router_events.py
  71. 4
      tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py
  72. 4
      tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py
  73. 4
      tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py
  74. 39
      tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py
  75. 2300
      uv.lock

9
.github/workflows/test-redistribute.yml

@ -12,11 +12,6 @@ on:
jobs: jobs:
test-redistribute: test-redistribute:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
package:
- fastapi
- fastapi-slim
steps: steps:
- name: Dump GitHub context - name: Dump GitHub context
env: env:
@ -30,8 +25,6 @@ jobs:
- name: Install build dependencies - name: Install build dependencies
run: pip install build run: pip install build
- name: Build source distribution - name: Build source distribution
env:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
run: python -m build --sdist run: python -m build --sdist
- name: Decompress source distribution - name: Decompress source distribution
run: | run: |
@ -41,8 +34,6 @@ jobs:
run: | run: |
cd dist/fastapi*/ cd dist/fastapi*/
pip install --group tests --editable .[all] pip install --group tests --editable .[all]
env:
TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }}
- name: Run source distribution tests - name: Run source distribution tests
run: | run: |
cd dist/fastapi*/ cd dist/fastapi*/

7
.github/workflows/test.yml

@ -14,6 +14,7 @@ on:
env: env:
UV_NO_SYNC: true UV_NO_SYNC: true
INLINE_SNAPSHOT_DEFAULT_FLAGS: review
jobs: jobs:
changes: changes:
@ -55,14 +56,10 @@ jobs:
- starlette-pypi - starlette-pypi
- starlette-git - starlette-git
include: include:
- os: ubuntu-latest
python-version: "3.9"
coverage: coverage
uv-resolution: lowest-direct
- os: macos-latest - os: macos-latest
python-version: "3.10" python-version: "3.10"
coverage: coverage coverage: coverage
uv-resolution: highest uv-resolution: lowest-direct
- os: windows-latest - os: windows-latest
python-version: "3.12" python-version: "3.12"
coverage: coverage coverage: coverage

10
README.md

@ -34,7 +34,7 @@ The key features are:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). * **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * * **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * * **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging. * **Intuitive**: Great editor support. <dfn title="also known as auto-complete, autocompletion, IntelliSense">Completion</dfn> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs. * **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. * **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
* **Robust**: Get production-ready code. With automatic interactive documentation. * **Robust**: Get production-ready code. With automatic interactive documentation.
@ -371,7 +371,7 @@ item: Item
* Validation of data: * Validation of data:
* Automatic and clear errors when the data is invalid. * Automatic and clear errors when the data is invalid.
* Validation even for deeply nested JSON objects. * Validation even for deeply nested JSON objects.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from: * <dfn title="also known as: serialization, parsing, marshalling">Conversion</dfn> of input data: coming from the network to Python data and types. Reading from:
* JSON. * JSON.
* Path parameters. * Path parameters.
* Query parameters. * Query parameters.
@ -379,7 +379,7 @@ item: Item
* Headers. * Headers.
* Forms. * Forms.
* Files. * Files.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of output data: converting from Python data and types to network data (as JSON): * <dfn title="also known as: serialization, parsing, marshalling">Conversion</dfn> of output data: converting from Python data and types to network data (as JSON):
* Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc).
* `datetime` objects. * `datetime` objects.
* `UUID` objects. * `UUID` objects.
@ -442,7 +442,7 @@ For a more complete example including more features, see the <a href="https://fa
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. * Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
* How to set **validation constraints** as `maximum_length` or `regex`. * How to set **validation constraints** as `maximum_length` or `regex`.
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system. * A very powerful and easy to use **<dfn title="also known as components, resources, providers, services, injectables">Dependency Injection</dfn>** system.
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. * Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). * More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).
* **GraphQL** integration with <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> and other libraries. * **GraphQL** integration with <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> and other libraries.
@ -527,7 +527,7 @@ Used by Starlette:
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Required if you want to use the `TestClient`. * <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Required if you want to use the `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration. * <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, with `request.form()`. * <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <dfn title="converting the string that comes from an HTTP request into Python data">"parsing"</dfn>, with `request.form()`.
Used by FastAPI: Used by FastAPI:

14
docs/de/docs/_llm-test.md

@ -35,7 +35,7 @@ Siehe Abschnitt `### Content of code snippets` im allgemeinen Prompt in `scripts
//// tab | Test //// tab | Test
Gestern schrieb mein Freund: „Wenn man unkorrekt korrekt schreibt, hat man es unkorrekt geschrieben“. Worauf ich antwortete: „Korrekt, aber ‚unkorrekt‘ ist unkorrekterweise nicht ‚„unkorrekt“‘“. Gestern schrieb mein Freund: „Wenn man ‚incorrectly‘ korrekt schreibt, hat man es falsch geschrieben“. Worauf ich antwortete: „Korrekt, aber ‚incorrectly‘ ist inkorrekterweise nicht ‚„incorrectly“‘“.
/// note | Hinweis /// note | Hinweis
@ -202,11 +202,6 @@ Hier einige Dinge, die in HTML-„abbr“-Elemente gepackt sind (einige sind erf
* <abbr title="XML Web Token">XWT</abbr> * <abbr title="XML Web Token">XWT</abbr>
* <abbr title="Paralleles Server-Gateway-Interface">PSGI</abbr> * <abbr title="Paralleles Server-Gateway-Interface">PSGI</abbr>
### Das abbr gibt eine Erklärung { #the-abbr-gives-an-explanation }
* <abbr title="Eine Gruppe von Maschinen, die so konfiguriert sind, dass sie verbunden sind und in irgendeiner Weise zusammenarbeiten.">Cluster</abbr>
* <abbr title="Eine Methode des Machine Learning, die künstliche neuronale Netze mit zahlreichen versteckten Schichten zwischen Eingabe- und Ausgabeschicht verwendet und so eine umfassende interne Struktur entwickelt">Deep Learning</abbr>
### Das abbr gibt eine vollständige Phrase und eine Erklärung { #the-abbr-gives-a-full-phrase-and-an-explanation } ### Das abbr gibt eine vollständige Phrase und eine Erklärung { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network – Mozilla-Entwicklernetzwerk: Dokumentation für Entwickler, geschrieben von den Firefox-Leuten">MDN</abbr> * <abbr title="Mozilla Developer Network – Mozilla-Entwicklernetzwerk: Dokumentation für Entwickler, geschrieben von den Firefox-Leuten">MDN</abbr>
@ -224,6 +219,11 @@ Siehe Abschnitt `### HTML abbr elements` im allgemeinen Prompt in `scripts/trans
//// ////
## HTML „dfn“-Elemente { #html-dfn-elements }
* <dfn title="Eine Gruppe von Maschinen, die so konfiguriert sind, dass sie verbunden sind und in irgendeiner Weise zusammenarbeiten.">Cluster</dfn>
* <dfn title="Eine Methode des Machine Learning, die künstliche neuronale Netze mit zahlreichen versteckten Schichten zwischen Eingabe- und Ausgabeschicht verwendet und so eine umfassende interne Struktur entwickelt">Deep Learning</dfn>
## Überschriften { #headings } ## Überschriften { #headings }
//// tab | Test //// tab | Test
@ -248,7 +248,7 @@ Die einzige strenge Regel für Überschriften ist, dass das LLM den Hash-Teil in
Siehe Abschnitt `### Headings` im allgemeinen Prompt in `scripts/translate.py`. Siehe Abschnitt `### Headings` im allgemeinen Prompt in `scripts/translate.py`.
Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headings` in `docs/de/llm-prompt.md`. Für einige sprachsspezifische Anweisungen, siehe z. B. den Abschnitt `### Headings` in `docs/de/llm-prompt.md`.
//// ////

10
docs/en/docs/_llm-test.md

@ -202,11 +202,6 @@ Here some things wrapped in HTML "abbr" elements (Some are invented):
* <abbr title="XML Web Token">XWT</abbr> * <abbr title="XML Web Token">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface">PSGI</abbr> * <abbr title="Parallel Server Gateway Interface">PSGI</abbr>
### The abbr gives an explanation { #the-abbr-gives-an-explanation }
* <abbr title="A group of machines that are configured to be connected and work together in some way.">cluster</abbr>
* <abbr title="A method of machine learning that uses artificial neural networks with numerous hidden layers between input and output layers, thereby developing a comprehensive internal structure">Deep Learning</abbr>
### The abbr gives a full phrase and an explanation { #the-abbr-gives-a-full-phrase-and-an-explanation } ### The abbr gives a full phrase and an explanation { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network: documentation for developers, written by the Firefox people">MDN</abbr> * <abbr title="Mozilla Developer Network: documentation for developers, written by the Firefox people">MDN</abbr>
@ -224,6 +219,11 @@ See section `### HTML abbr elements` in the general prompt in `scripts/translate
//// ////
## HTML "dfn" elements { #html-dfn-elements }
* <dfn title="A group of machines that are configured to be connected and work together in some way.">cluster</dfn>
* <dfn title="A method of machine learning that uses artificial neural networks with numerous hidden layers between input and output layers, thereby developing a comprehensive internal structure">Deep Learning</dfn>
## Headings { #headings } ## Headings { #headings }
//// tab | Test //// tab | Test

61
docs/en/docs/advanced/advanced-python-types.md

@ -0,0 +1,61 @@
# Advanced Python Types { #advanced-python-types }
Here are some additional ideas that might be useful when working with Python types.
## Using `Union` or `Optional` { #using-union-or-optional }
If your code for some reason can't use `|`, for example if it's not in a type annotation but in something like `response_model=`, instead of using the vertical bar (`|`) you can use `Union` from `typing`.
For example, you could declare that something could be a `str` or `None`:
```python
from typing import Union
def say_hi(name: Union[str, None]):
print(f"Hi {name}!")
```
`typing` also has a shortcut to declare that something could be `None`, with `Optional`.
Here's a tip from my very **subjective** point of view:
* 🚨 Avoid using `Optional[SomeType]`
* Instead ✨ **use `Union[SomeType, None]`** ✨.
Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required.
I think `Union[SomeType, None]` is more explicit about what it means.
It's just about the words and names. But those words can affect how you and your teammates think about the code.
As an example, let's take this function:
```python
from typing import Optional
def say_hi(name: Optional[str]):
print(f"Hey {name}!")
```
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:
```Python
say_hi() # Oh, no, this throws an error! 😱
```
The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value:
```Python
say_hi(name=None) # This works, None is valid 🎉
```
The good news is, in most cases, you will be able to simply use `|` to define unions of types:
```python
def say_hi(name: str | None):
print(f"Hey {name}!")
```
So, normally you don't have to worry about names like `Optional` and `Union`. 😎

2
docs/en/docs/advanced/dataclasses.md

@ -64,7 +64,7 @@ In that case, you can simply swap the standard `dataclasses` with `pydantic.data
6. Here we are returning a dictionary that contains `items` which is a list of dataclasses. 6. Here we are returning a dictionary that contains `items` which is a list of dataclasses.
FastAPI is still capable of <abbr title="converting the data to a format that can be transmitted">serializing</abbr> the data to JSON. FastAPI is still capable of <dfn title="converting the data to a format that can be transmitted">serializing</dfn> the data to JSON.
7. Here the `response_model` is using a type annotation of a list of `Author` dataclasses. 7. Here the `response_model` is using a type annotation of a list of `Author` dataclasses.

2
docs/en/docs/advanced/path-operation-advanced-configuration.md

@ -141,7 +141,7 @@ You could do that with `openapi_extra`:
{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} {* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *}
In this example, we didn't declare any Pydantic model. In fact, the request body is not even <abbr title="converted from some plain format, like bytes, into Python objects">parsed</abbr> as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. In this example, we didn't declare any Pydantic model. In fact, the request body is not even <dfn title="converted from some plain format, like bytes, into Python objects">parsed</dfn> as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way.
Nevertheless, we can declare the expected schema for the request body. Nevertheless, we can declare the expected schema for the request body.

8
docs/en/docs/alternatives.md

@ -137,7 +137,7 @@ There are several Flask REST frameworks, but after investing the time and work i
### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow } ### <a href="https://marshmallow.readthedocs.io/en/stable/" class="external-link" target="_blank">Marshmallow</a> { #marshmallow }
One of the main features needed by API systems is data "<abbr title="also called marshalling, conversion">serialization</abbr>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc. One of the main features needed by API systems is data "<dfn title="also called marshalling, conversion">serialization</dfn>" which is taking data from the code (Python) and converting it into something that can be sent through the network. For example, converting an object containing data from a database into a JSON object. Converting `datetime` objects into strings, etc.
Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data. Another big feature needed by APIs is data validation, making sure that the data is valid, given certain parameters. For example, that some field is an `int`, and not some random string. This is especially useful for incoming data.
@ -145,7 +145,7 @@ Without a data validation system, you would have to do all the checks by hand, i
These features are what Marshmallow was built to provide. It is a great library, and I have used it a lot before. These features are what Marshmallow was built to provide. It is a great library, and I have used it a lot before.
But it was created before there existed Python type hints. So, to define every <abbr title="the definition of how data should be formed">schema</abbr> you need to use specific utils and classes provided by Marshmallow. But it was created before there existed Python type hints. So, to define every <dfn title="the definition of how data should be formed">schema</dfn> you need to use specific utils and classes provided by Marshmallow.
/// check | Inspired **FastAPI** to /// check | Inspired **FastAPI** to
@ -155,7 +155,7 @@ Use code to define "schemas" that provide data types and validation, automatical
### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs } ### <a href="https://webargs.readthedocs.io/en/latest/" class="external-link" target="_blank">Webargs</a> { #webargs }
Another big feature required by APIs is <abbr title="reading and converting to Python data">parsing</abbr> data from incoming requests. Another big feature required by APIs is <dfn title="reading and converting to Python data">parsing</dfn> data from incoming requests.
Webargs is a tool that was made to provide that on top of several frameworks, including Flask. Webargs is a tool that was made to provide that on top of several frameworks, including Flask.
@ -419,7 +419,7 @@ Handle all the data validation, data serialization and automatic model documenta
### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette } ### <a href="https://www.starlette.dev/" class="external-link" target="_blank">Starlette</a> { #starlette }
Starlette is a lightweight <abbr title="The new standard for building asynchronous Python web applications">ASGI</abbr> framework/toolkit, which is ideal for building high-performance asyncio services. Starlette is a lightweight <dfn title="The new standard for building asynchronous Python web applications">ASGI</dfn> framework/toolkit, which is ideal for building high-performance asyncio services.
It is very simple and intuitive. It's designed to be easily extensible, and have modular components. It is very simple and intuitive. It's designed to be easily extensible, and have modular components.

5
docs/en/docs/css/custom.css

@ -203,3 +203,8 @@ Inspired by Termynal's CSS tricks with modifications
-webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
} }
.md-typeset dfn {
border-bottom: .05rem dotted var(--md-default-fg-color--light);
cursor: help;
}

10
docs/en/docs/deployment/docker.md

@ -14,7 +14,7 @@ In a hurry and already know this stuff? Jump to the [`Dockerfile` below 👇](#b
<summary>Dockerfile Preview 👀</summary> <summary>Dockerfile Preview 👀</summary>
```Dockerfile ```Dockerfile
FROM python:3.9 FROM python:3.14
WORKDIR /code WORKDIR /code
@ -166,7 +166,7 @@ Now in the same project directory create a file `Dockerfile` with:
```{ .dockerfile .annotate } ```{ .dockerfile .annotate }
# (1)! # (1)!
FROM python:3.9 FROM python:3.14
# (2)! # (2)!
WORKDIR /code WORKDIR /code
@ -390,7 +390,7 @@ If your FastAPI is a single file, for example, `main.py` without an `./app` dire
Then you would just have to change the corresponding paths to copy the file inside the `Dockerfile`: Then you would just have to change the corresponding paths to copy the file inside the `Dockerfile`:
```{ .dockerfile .annotate hl_lines="10 13" } ```{ .dockerfile .annotate hl_lines="10 13" }
FROM python:3.9 FROM python:3.14
WORKDIR /code WORKDIR /code
@ -454,7 +454,7 @@ Without using containers, making applications run on startup and with restarts c
## Replication - Number of Processes { #replication-number-of-processes } ## Replication - Number of Processes { #replication-number-of-processes }
If you have a <abbr title="A group of machines that are configured to be connected and work together in some way.">cluster</abbr> of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Uvicorn with workers) in each container. If you have a <dfn title="A group of machines that are configured to be connected and work together in some way.">cluster</dfn> of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or another similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Uvicorn with workers) in each container.
One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**. One of those distributed container management systems like Kubernetes normally has some integrated way of handling **replication of containers** while still supporting **load balancing** for the incoming requests. All at the **cluster level**.
@ -499,7 +499,7 @@ Of course, there are **special cases** where you could want to have **a containe
In those cases, you can use the `--workers` command line option to set the number of workers that you want to run: In those cases, you can use the `--workers` command line option to set the number of workers that you want to run:
```{ .dockerfile .annotate } ```{ .dockerfile .annotate }
FROM python:3.9 FROM python:3.14
WORKDIR /code WORKDIR /code

2
docs/en/docs/deployment/https.md

@ -65,7 +65,7 @@ Here's an example of how an HTTPS API could look like, step by step, paying atte
It would probably all start by you **acquiring** some **domain name**. Then, you would configure it in a DNS server (possibly your same cloud provider). It would probably all start by you **acquiring** some **domain name**. Then, you would configure it in a DNS server (possibly your same cloud provider).
You would probably get a cloud server (a virtual machine) or something similar, and it would have a <abbr title="That doesn't change">fixed</abbr> **public IP address**. You would probably get a cloud server (a virtual machine) or something similar, and it would have a <dfn title="Doesn't change over time. Not dynamic.">fixed</dfn> **public IP address**.
In the DNS server(s) you would configure a record (an "`A record`") to point **your domain** to the public **IP address of your server**. In the DNS server(s) you would configure a record (an "`A record`") to point **your domain** to the public **IP address of your server**.

10
docs/en/docs/features.md

@ -6,7 +6,7 @@
### Based on open standards { #based-on-open-standards } ### Based on open standards { #based-on-open-standards }
* <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> for API creation, including declarations of <abbr title="also known as: endpoints, routes">path</abbr> <abbr title="also known as HTTP methods, as POST, GET, PUT, DELETE">operations</abbr>, parameters, request bodies, security, etc. * <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank"><strong>OpenAPI</strong></a> for API creation, including declarations of <dfn title="also known as: endpoints, routes">path</dfn> <dfn title="also known as HTTP methods, as POST, GET, PUT, DELETE">operations</dfn>, parameters, request bodies, security, etc.
* Automatic data model documentation with <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (as OpenAPI itself is based on JSON Schema). * Automatic data model documentation with <a href="https://json-schema.org/" class="external-link" target="_blank"><strong>JSON Schema</strong></a> (as OpenAPI itself is based on JSON Schema).
* Designed around these standards, after a meticulous study. Instead of an afterthought layer on top. * Designed around these standards, after a meticulous study. Instead of an afterthought layer on top.
* This also allows using automatic **client code generation** in many languages. * This also allows using automatic **client code generation** in many languages.
@ -136,7 +136,7 @@ All built as reusable tools and components that are easy to integrate with your
### Dependency Injection { #dependency-injection } ### Dependency Injection { #dependency-injection }
FastAPI includes an extremely easy to use, but extremely powerful <abbr title='also known as "components", "resources", "services", "providers"'><strong>Dependency Injection</strong></abbr> system. FastAPI includes an extremely easy to use, but extremely powerful <dfn title='also known as "components", "resources", "services", "providers"'><strong>Dependency Injection</strong></dfn> system.
* Even dependencies can have dependencies, creating a hierarchy or **"graph" of dependencies**. * Even dependencies can have dependencies, creating a hierarchy or **"graph" of dependencies**.
* All **automatically handled** by the framework. * All **automatically handled** by the framework.
@ -153,8 +153,8 @@ Any integration is designed to be so simple to use (with dependencies) that you
### Tested { #tested } ### Tested { #tested }
* 100% <abbr title="The amount of code that is automatically tested">test coverage</abbr>. * 100% <dfn title="The amount of code that is automatically tested">test coverage</dfn>.
* 100% <abbr title="Python type annotations, with this your editor and external tools can give you better support">type annotated</abbr> code base. * 100% <dfn title="Python type annotations, with this your editor and external tools can give you better support">type annotated</dfn> code base.
* Used in production applications. * Used in production applications.
## Starlette features { #starlette-features } ## Starlette features { #starlette-features }
@ -190,7 +190,7 @@ With **FastAPI** you get all of **Pydantic**'s features (as FastAPI is based on
* **No brainfuck**: * **No brainfuck**:
* No new schema definition micro-language to learn. * No new schema definition micro-language to learn.
* If you know Python types you know how to use Pydantic. * If you know Python types you know how to use Pydantic.
* Plays nicely with your **<abbr title="Integrated Development Environment: similar to a code editor">IDE</abbr>/<abbr title="A program that checks for code errors">linter</abbr>/brain**: * Plays nicely with your **<abbr title="Integrated Development Environment: similar to a code editor">IDE</abbr>/<dfn title="A program that checks for code errors">linter</dfn>/brain**:
* Because pydantic data structures are just instances of classes you define; auto-completion, linting, mypy and your intuition should all work properly with your validated data. * Because pydantic data structures are just instances of classes you define; auto-completion, linting, mypy and your intuition should all work properly with your validated data.
* Validate **complex structures**: * Validate **complex structures**:
* Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc. * Use of hierarchical Pydantic models, Python `typing`’s `List` and `Dict`, etc.

10
docs/en/docs/index.md

@ -40,7 +40,7 @@ The key features are:
* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). * **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance).
* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * * **Fast to code**: Increase the speed to develop features by about 200% to 300%. *
* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * * **Fewer bugs**: Reduce about 40% of human (developer) induced errors. *
* **Intuitive**: Great editor support. <abbr title="also known as auto-complete, autocompletion, IntelliSense">Completion</abbr> everywhere. Less time debugging. * **Intuitive**: Great editor support. <dfn title="also known as auto-complete, autocompletion, IntelliSense">Completion</dfn> everywhere. Less time debugging.
* **Easy**: Designed to be easy to use and learn. Less time reading docs. * **Easy**: Designed to be easy to use and learn. Less time reading docs.
* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. * **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
* **Robust**: Get production-ready code. With automatic interactive documentation. * **Robust**: Get production-ready code. With automatic interactive documentation.
@ -368,7 +368,7 @@ item: Item
* Validation of data: * Validation of data:
* Automatic and clear errors when the data is invalid. * Automatic and clear errors when the data is invalid.
* Validation even for deeply nested JSON objects. * Validation even for deeply nested JSON objects.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of input data: coming from the network to Python data and types. Reading from: * <dfn title="also known as: serialization, parsing, marshalling">Conversion</dfn> of input data: coming from the network to Python data and types. Reading from:
* JSON. * JSON.
* Path parameters. * Path parameters.
* Query parameters. * Query parameters.
@ -376,7 +376,7 @@ item: Item
* Headers. * Headers.
* Forms. * Forms.
* Files. * Files.
* <abbr title="also known as: serialization, parsing, marshalling">Conversion</abbr> of output data: converting from Python data and types to network data (as JSON): * <dfn title="also known as: serialization, parsing, marshalling">Conversion</dfn> of output data: converting from Python data and types to network data (as JSON):
* Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc).
* `datetime` objects. * `datetime` objects.
* `UUID` objects. * `UUID` objects.
@ -439,7 +439,7 @@ For a more complete example including more features, see the <a href="https://fa
* Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**. * Declaration of **parameters** from other different places as: **headers**, **cookies**, **form fields** and **files**.
* How to set **validation constraints** as `maximum_length` or `regex`. * How to set **validation constraints** as `maximum_length` or `regex`.
* A very powerful and easy to use **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system. * A very powerful and easy to use **<dfn title="also known as components, resources, providers, services, injectables">Dependency Injection</dfn>** system.
* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. * Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth.
* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). * More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic).
* **GraphQL** integration with <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> and other libraries. * **GraphQL** integration with <a href="https://strawberry.rocks" class="external-link" target="_blank">Strawberry</a> and other libraries.
@ -524,7 +524,7 @@ Used by Starlette:
* <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Required if you want to use the `TestClient`. * <a href="https://www.python-httpx.org" target="_blank"><code>httpx</code></a> - Required if you want to use the `TestClient`.
* <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration. * <a href="https://jinja.palletsprojects.com" target="_blank"><code>jinja2</code></a> - Required if you want to use the default template configuration.
* <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>, with `request.form()`. * <a href="https://github.com/Kludex/python-multipart" target="_blank"><code>python-multipart</code></a> - Required if you want to support form <dfn title="converting the string that comes from an HTTP request into Python data">"parsing"</dfn>, with `request.form()`.
Used by FastAPI: Used by FastAPI:

162
docs/en/docs/python-types.md

@ -2,7 +2,7 @@
Python has support for optional "type hints" (also called "type annotations"). Python has support for optional "type hints" (also called "type annotations").
These **"type hints"** or annotations are a special syntax that allow declaring the <abbr title="for example: str, int, float, bool">type</abbr> of a variable. These **"type hints"** or annotations are a special syntax that allow declaring the <dfn title="for example: str, int, float, bool">type</dfn> of a variable.
By declaring types for your variables, editors and tools can give you better support. By declaring types for your variables, editors and tools can give you better support.
@ -34,7 +34,7 @@ The function does the following:
* Takes a `first_name` and `last_name`. * Takes a `first_name` and `last_name`.
* Converts the first letter of each one to upper case with `title()`. * Converts the first letter of each one to upper case with `title()`.
* <abbr title="Puts them together, as one. With the contents of one after the other.">Concatenates</abbr> them with a space in the middle. * <dfn title="Puts them together, as one. With the contents of one after the other.">Concatenates</dfn> them with a space in the middle.
{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} {* ../../docs_src/python_types/tutorial001_py39.py hl[2] *}
@ -135,27 +135,30 @@ You can use, for example:
{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} {* ../../docs_src/python_types/tutorial005_py39.py hl[1] *}
### Generic types with type parameters { #generic-types-with-type-parameters } ### `typing` module { #typing-module }
There are some data structures that can contain other values, like `dict`, `list`, `set` and `tuple`. And the internal values can have their own type too. For some additional use cases, you might need to import some things from the standard library `typing` module, for example when you want to declare that something has "any type", you can use `Any` from `typing`:
These types that have internal types are called "**generic**" types. And it's possible to declare them, even with their internal types. ```python
from typing import Any
To declare those types and the internal types, you can use the standard Python module `typing`. It exists specifically to support these type hints.
#### Newer versions of Python { #newer-versions-of-python } def some_function(data: Any):
print(data)
The syntax using `typing` is **compatible** with all versions, from Python 3.6 to the latest ones, including Python 3.9, Python 3.10, etc. ```
As Python advances, **newer versions** come with improved support for these type annotations and in many cases you won't even need to import and use the `typing` module to declare the type annotations. ### Generic types { #generic-types }
If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity. Some types can take "type parameters" in square brackets, to define their internal types, for example a "list of strings" would be declared `list[str]`.
In all the docs there are examples compatible with each version of Python (when there's a difference). These types that can take type parameters are called **Generic types** or **Generics**.
For example "**Python 3.6+**" means it's compatible with Python 3.6 or above (including 3.7, 3.8, 3.9, 3.10, etc). And "**Python 3.9+**" means it's compatible with Python 3.9 or above (including 3.10, etc). You can use the same builtin types as generics (with square brackets and types inside):
If you can use the **latest versions of Python**, use the examples for the latest version, those will have the **best and simplest syntax**, for example, "**Python 3.10+**". * `list`
* `tuple`
* `set`
* `dict`
#### List { #list } #### List { #list }
@ -220,44 +223,20 @@ This means:
You can declare that a variable can be any of **several types**, for example, an `int` or a `str`. You can declare that a variable can be any of **several types**, for example, an `int` or a `str`.
In Python 3.6 and above (including Python 3.10) you can use the `Union` type from `typing` and put inside the square brackets the possible types to accept. To define it you use the <dfn title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</dfn> to separate both types.
In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr>.
//// tab | Python 3.10+ This is called a "union", because the variable can be anything in the union of those two sets of types.
```Python hl_lines="1" ```Python hl_lines="1"
{!> ../../docs_src/python_types/tutorial008b_py310.py!} {!> ../../docs_src/python_types/tutorial008b_py310.py!}
``` ```
//// This means that `item` could be an `int` or a `str`.
//// tab | Python 3.9+
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial008b_py39.py!}
```
////
In both cases this means that `item` could be an `int` or a `str`.
#### Possibly `None` { #possibly-none } #### Possibly `None` { #possibly-none }
You can declare that a value could have a type, like `str`, but that it could also be `None`. You can declare that a value could have a type, like `str`, but that it could also be `None`.
In Python 3.6 and above (including Python 3.10) you can declare it by importing and using `Optional` from the `typing` module.
```Python hl_lines="1 4"
{!../../docs_src/python_types/tutorial009_py39.py!}
```
Using `Optional[str]` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
`Optional[Something]` is actually a shortcut for `Union[Something, None]`, they are equivalent.
This also means that in Python 3.10, you can use `Something | None`:
//// tab | Python 3.10+ //// tab | Python 3.10+
```Python hl_lines="1" ```Python hl_lines="1"
@ -266,96 +245,7 @@ This also means that in Python 3.10, you can use `Something | None`:
//// ////
//// tab | Python 3.9+ Using `str | None` instead of just `str` will let the editor help you detect errors where you could be assuming that a value is always a `str`, when it could actually be `None` too.
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009_py39.py!}
```
////
//// tab | Python 3.9+ alternative
```Python hl_lines="1 4"
{!> ../../docs_src/python_types/tutorial009b_py39.py!}
```
////
#### Using `Union` or `Optional` { #using-union-or-optional }
If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view:
* 🚨 Avoid using `Optional[SomeType]`
* Instead ✨ **use `Union[SomeType, None]`** ✨.
Both are equivalent and underneath they are the same, but I would recommend `Union` instead of `Optional` because the word "**optional**" would seem to imply that the value is optional, and it actually means "it can be `None`", even if it's not optional and is still required.
I think `Union[SomeType, None]` is more explicit about what it means.
It's just about the words and names. But those words can affect how you and your teammates think about the code.
As an example, let's take this function:
{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *}
The parameter `name` is defined as `Optional[str]`, but it is **not optional**, you cannot call the function without the parameter:
```Python
say_hi() # Oh, no, this throws an error! 😱
```
The `name` parameter is **still required** (not *optional*) because it doesn't have a default value. Still, `name` accepts `None` as the value:
```Python
say_hi(name=None) # This works, None is valid 🎉
```
The good news is, once you are on Python 3.10 you won't have to worry about that, as you will be able to simply use `|` to define unions of types:
{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *}
And then you won't have to worry about names like `Optional` and `Union`. 😎
#### Generic types { #generic-types }
These types that take type parameters in square brackets are called **Generic types** or **Generics**, for example:
//// tab | Python 3.10+
You can use the same builtin types as generics (with square brackets and types inside):
* `list`
* `tuple`
* `set`
* `dict`
And the same as with previous Python versions, from the `typing` module:
* `Union`
* `Optional`
* ...and others.
In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the <abbr title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</abbr> to declare unions of types, that's a lot better and simpler.
////
//// tab | Python 3.9+
You can use the same builtin types as generics (with square brackets and types inside):
* `list`
* `tuple`
* `set`
* `dict`
And generics from the `typing` module:
* `Union`
* `Optional`
* ...and others.
////
### Classes as types { #classes-as-types } ### Classes as types { #classes-as-types }
@ -403,17 +293,11 @@ To learn more about <a href="https://docs.pydantic.dev/" class="external-link" t
You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}. You will see a lot more of all this in practice in the [Tutorial - User Guide](tutorial/index.md){.internal-link target=_blank}.
/// tip
Pydantic has a special behavior when you use `Optional` or `Union[Something, None]` without a default value, you can read more about it in the Pydantic docs about <a href="https://docs.pydantic.dev/2.3/usage/models/#required-fields" class="external-link" target="_blank">Required Optional fields</a>.
///
## Type Hints with Metadata Annotations { #type-hints-with-metadata-annotations } ## Type Hints with Metadata Annotations { #type-hints-with-metadata-annotations }
Python also has a feature that allows putting **additional <abbr title="Data about the data, in this case, information about the type, e.g. a description.">metadata</abbr>** in these type hints using `Annotated`. Python also has a feature that allows putting **additional <dfn title="Data about the data, in this case, information about the type, e.g. a description.">metadata</dfn>** in these type hints using `Annotated`.
Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`. You can import `Annotated` from `typing`.
{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} {* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *}

51
docs/en/docs/release-notes.md

@ -7,10 +7,61 @@ hide:
## Latest Changes ## Latest Changes
### Breaking Changes
* ➖ Drop support for Python 3.9. PR [#14897](https://github.com/fastapi/fastapi/pull/14897) by [@tiangolo](https://github.com/tiangolo).
### Refactors
* 🎨 Update internal types for Python 3.10. PR [#14898](https://github.com/fastapi/fastapi/pull/14898) by [@tiangolo](https://github.com/tiangolo).
## 0.128.8
### Docs
* 📝 Fix grammar in `docs/en/docs/tutorial/first-steps.md`. PR [#14708](https://github.com/fastapi/fastapi/pull/14708) by [@SanjanaS10](https://github.com/SanjanaS10).
### Internal
* 🔨 Tweak PDM hook script. PR [#14895](https://github.com/fastapi/fastapi/pull/14895) by [@tiangolo](https://github.com/tiangolo).
* ♻️ Update build setup for `fastapi-slim`, deprecate it, and make it only depend on `fastapi`. PR [#14894](https://github.com/fastapi/fastapi/pull/14894) by [@tiangolo](https://github.com/tiangolo).
## 0.128.7
### Features
* ✨ Show a clear error on attempt to include router into itself. PR [#14258](https://github.com/fastapi/fastapi/pull/14258) by [@JavierSanchezCastro](https://github.com/JavierSanchezCastro).
* ✨ Replace `dict` by `Mapping` on `HTTPException.headers`. PR [#12997](https://github.com/fastapi/fastapi/pull/12997) by [@rijenkii](https://github.com/rijenkii).
### Refactors
* ♻️ Simplify reading files in memory, do it sequentially instead of (fake) parallel. PR [#14884](https://github.com/fastapi/fastapi/pull/14884) by [@tiangolo](https://github.com/tiangolo).
### Docs
* 📝 Use `dfn` tag for definitions instead of `abbr` in docs. PR [#14744](https://github.com/fastapi/fastapi/pull/14744) by [@YuriiMotov](https://github.com/YuriiMotov).
### Internal
* ✅ Tweak comment in test to reference PR. PR [#14885](https://github.com/fastapi/fastapi/pull/14885) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Update LLM-prompt for `abbr` and `dfn` tags. PR [#14747](https://github.com/fastapi/fastapi/pull/14747) by [@YuriiMotov](https://github.com/YuriiMotov).
* ✅ Test order for the submitted byte Files. PR [#14828](https://github.com/fastapi/fastapi/pull/14828) by [@valentinDruzhinin](https://github.com/valentinDruzhinin).
* 🔧 Configure `test` workflow to run tests with `inline-snapshot=review`. PR [#14876](https://github.com/fastapi/fastapi/pull/14876) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.128.6
### Fixes
* 🐛 Fix `on_startup` and `on_shutdown` parameters of `APIRouter`. PR [#14873](https://github.com/fastapi/fastapi/pull/14873) by [@YuriiMotov](https://github.com/YuriiMotov).
### Translations ### Translations
* 🌐 Update translations for zh (update-outdated). PR [#14843](https://github.com/fastapi/fastapi/pull/14843) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for zh (update-outdated). PR [#14843](https://github.com/fastapi/fastapi/pull/14843) by [@tiangolo](https://github.com/tiangolo).
### Internal
* ✅ Fix parameterized tests with snapshots. PR [#14875](https://github.com/fastapi/fastapi/pull/14875) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.128.5 ## 0.128.5
### Refactors ### Refactors

7
docs/en/docs/tutorial/body-multiple-params.md

@ -106,13 +106,6 @@ As, by default, singular values are interpreted as query parameters, you don't h
q: str | None = None q: str | None = None
``` ```
Or in Python 3.9:
```Python
q: Union[str, None] = None
```
For example: For example:
{* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *} {* ../../docs_src/body_multiple_params/tutorial004_an_py310.py hl[28] *}

2
docs/en/docs/tutorial/body.md

@ -155,7 +155,7 @@ The function parameters will be recognized as follows:
FastAPI will know that the value of `q` is not required because of the default value `= None`. FastAPI will know that the value of `q` is not required because of the default value `= None`.
The `str | None` (Python 3.10+) or `Union` in `Union[str, None]` (Python 3.9+) is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`. The `str | None` is not used by FastAPI to determine that the value is not required, it will know it's not required because it has a default value of `= None`.
But adding the type annotations will allow your editor to give you better support and detect errors. But adding the type annotations will allow your editor to give you better support and detect errors.

8
docs/en/docs/tutorial/cookie-param-models.md

@ -46,7 +46,7 @@ But even if you **fill the data** and click "Execute", because the docs UI works
In some special use cases (probably not very common), you might want to **restrict** the cookies that you want to receive. In some special use cases (probably not very common), you might want to **restrict** the cookies that you want to receive.
Your API now has the power to control its own <abbr title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. 🍪">cookie consent</abbr>. 🤪🍪 Your API now has the power to control its own <dfn title="This is a joke, just in case. It has nothing to do with cookie consents, but it's funny that even the API can now reject the poor cookies. Have a cookie. 🍪">cookie consent</dfn>. 🤪🍪
You can use Pydantic's model configuration to `forbid` any `extra` fields: You can use Pydantic's model configuration to `forbid` any `extra` fields:
@ -54,9 +54,9 @@ You can use Pydantic's model configuration to `forbid` any `extra` fields:
If a client tries to send some **extra cookies**, they will receive an **error** response. If a client tries to send some **extra cookies**, they will receive an **error** response.
Poor cookie banners with all their effort to get your consent for the <abbr title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. ☕">API to reject it</abbr>. 🍪 Poor cookie banners with all their effort to get your consent for the <dfn title="This is another joke. Don't pay attention to me. Have some coffee for your cookie. ☕">API to reject it</dfn>. 🍪
For example, if the client tries to send a `santa_tracker` cookie with a value of `good-list-please`, the client will receive an **error** response telling them that the `santa_tracker` <abbr title="Santa disapproves the lack of cookies. 🎅 Okay, no more cookie jokes.">cookie is not allowed</abbr>: For example, if the client tries to send a `santa_tracker` cookie with a value of `good-list-please`, the client will receive an **error** response telling them that the `santa_tracker` <dfn title="Santa disapproves the lack of cookies. 🎅 Okay, no more cookie jokes.">cookie is not allowed</dfn>:
```json ```json
{ {
@ -73,4 +73,4 @@ For example, if the client tries to send a `santa_tracker` cookie with a value o
## Summary { #summary } ## Summary { #summary }
You can use **Pydantic models** to declare <abbr title="Have a last cookie before you go. 🍪">**cookies**</abbr> in **FastAPI**. 😎 You can use **Pydantic models** to declare <dfn title="Have a last cookie before you go. 🍪">**cookies**</dfn> in **FastAPI**. 😎

24
docs/en/docs/tutorial/dependencies/classes-as-dependencies.md

@ -101,7 +101,7 @@ Now you can declare your dependency using this class.
Notice how we write `CommonQueryParams` twice in the above code: Notice how we write `CommonQueryParams` twice in the above code:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python ```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip
@ -137,7 +137,7 @@ It is from this one that FastAPI will extract the declared parameters and that i
In this case, the first `CommonQueryParams`, in: In this case, the first `CommonQueryParams`, in:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python ```Python
commons: Annotated[CommonQueryParams, ... commons: Annotated[CommonQueryParams, ...
@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ...
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip
@ -163,7 +163,7 @@ commons: CommonQueryParams ...
You could actually write just: You could actually write just:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python ```Python
commons: Annotated[Any, Depends(CommonQueryParams)] commons: Annotated[Any, Depends(CommonQueryParams)]
@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)]
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip
@ -197,7 +197,7 @@ But declaring the type is encouraged as that way your editor will know what will
But you see that we are having some code repetition here, writing `CommonQueryParams` twice: But you see that we are having some code repetition here, writing `CommonQueryParams` twice:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python ```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip
@ -225,7 +225,7 @@ For those specific cases, you can do the following:
Instead of writing: Instead of writing:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python ```Python
commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip
@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...you write: ...you write:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python ```Python
commons: Annotated[CommonQueryParams, Depends()] commons: Annotated[CommonQueryParams, Depends()]
@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()]
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip

2
docs/en/docs/tutorial/dependencies/dependencies-with-yield.md

@ -1,6 +1,6 @@
# Dependencies with yield { #dependencies-with-yield } # Dependencies with yield { #dependencies-with-yield }
FastAPI supports dependencies that do some <abbr title='sometimes also called "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code", etc.'>extra steps after finishing</abbr>. FastAPI supports dependencies that do some <dfn title='sometimes also called "exit code", "cleanup code", "teardown code", "closing code", "context manager exit code", etc.'>extra steps after finishing</dfn>.
To do this, use `yield` instead of `return`, and write the extra steps (code) after. To do this, use `yield` instead of `return`, and write the extra steps (code) after.

2
docs/en/docs/tutorial/dependencies/index.md

@ -1,6 +1,6 @@
# Dependencies { #dependencies } # Dependencies { #dependencies }
**FastAPI** has a very powerful but intuitive **<abbr title="also known as components, resources, providers, services, injectables">Dependency Injection</abbr>** system. **FastAPI** has a very powerful but intuitive **<dfn title="also known as components, resources, providers, services, injectables">Dependency Injection</dfn>** system.
It is designed to be very simple to use, and to make it very easy for any developer to integrate other components with **FastAPI**. It is designed to be very simple to use, and to make it very easy for any developer to integrate other components with **FastAPI**.

6
docs/en/docs/tutorial/dependencies/sub-dependencies.md

@ -58,11 +58,11 @@ query_extractor --> query_or_cookie_extractor --> read_query
If one of your dependencies is declared multiple times for the same *path operation*, for example, multiple dependencies have a common sub-dependency, **FastAPI** will know to call that sub-dependency only once per request. If one of your dependencies is declared multiple times for the same *path operation*, for example, multiple dependencies have a common sub-dependency, **FastAPI** will know to call that sub-dependency only once per request.
And it will save the returned value in a <abbr title="A utility/system to store computed/generated values, to reuse them instead of computing them again.">"cache"</abbr> and pass it to all the "dependants" that need it in that specific request, instead of calling the dependency multiple times for the same request. And it will save the returned value in a <dfn title="A utility/system to store computed/generated values, to reuse them instead of computing them again.">"cache"</dfn> and pass it to all the "dependants" that need it in that specific request, instead of calling the dependency multiple times for the same request.
In an advanced scenario where you know you need the dependency to be called at every step (possibly multiple times) in the same request instead of using the "cached" value, you can set the parameter `use_cache=False` when using `Depends`: In an advanced scenario where you know you need the dependency to be called at every step (possibly multiple times) in the same request instead of using the "cached" value, you can set the parameter `use_cache=False` when using `Depends`:
//// tab | Python 3.9+ //// tab | Python 3.10+
```Python hl_lines="1" ```Python hl_lines="1"
async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]): async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cache=False)]):
@ -71,7 +71,7 @@ async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_ca
//// ////
//// tab | Python 3.9+ non-Annotated //// tab | Python 3.10+ non-Annotated
/// tip /// tip

4
docs/en/docs/tutorial/extra-models.md

@ -190,7 +190,7 @@ But if we put that in the assignment `response_model=PlaneItem | CarItem` we wou
The same way, you can declare responses of lists of objects. The same way, you can declare responses of lists of objects.
For that, use the standard Python `typing.List` (or just `list` in Python 3.9 and above): For that, use the standard Python `list`:
{* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *} {* ../../docs_src/extra_models/tutorial004_py39.py hl[18] *}
@ -200,7 +200,7 @@ You can also declare a response using a plain arbitrary `dict`, declaring just t
This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic model) beforehand. This is useful if you don't know the valid field/attribute names (that would be needed for a Pydantic model) beforehand.
In this case, you can use `typing.Dict` (or just `dict` in Python 3.9 and above): In this case, you can use `dict`:
{* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *} {* ../../docs_src/extra_models/tutorial005_py39.py hl[6] *}

4
docs/en/docs/tutorial/first-steps.md

@ -54,7 +54,7 @@ In the output, there's a line with something like:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
That line shows the URL where your app is being served, in your local machine. That line shows the URL where your app is being served on your local machine.
### Check it { #check-it } ### Check it { #check-it }
@ -271,7 +271,7 @@ We are going to call them "**operations**" too.
The `@app.get("/")` tells **FastAPI** that the function right below is in charge of handling requests that go to: The `@app.get("/")` tells **FastAPI** that the function right below is in charge of handling requests that go to:
* the path `/` * the path `/`
* using a <abbr title="an HTTP GET method"><code>get</code> operation</abbr> * using a <dfn title="an HTTP GET method"><code>get</code> operation</dfn>
/// info | `@decorator` Info /// info | `@decorator` Info

4
docs/en/docs/tutorial/path-operation-configuration.md

@ -56,7 +56,7 @@ You can add a `summary` and `description`:
## Description from docstring { #description-from-docstring } ## Description from docstring { #description-from-docstring }
As descriptions tend to be long and cover multiple lines, you can declare the *path operation* description in the function <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr> and **FastAPI** will read it from there. As descriptions tend to be long and cover multiple lines, you can declare the *path operation* description in the function <dfn title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</dfn> and **FastAPI** will read it from there.
You can write <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation). You can write <a href="https://en.wikipedia.org/wiki/Markdown" class="external-link" target="_blank">Markdown</a> in the docstring, it will be interpreted and displayed correctly (taking into account docstring indentation).
@ -90,7 +90,7 @@ So, if you don't provide one, **FastAPI** will automatically generate one of "Su
## Deprecate a *path operation* { #deprecate-a-path-operation } ## Deprecate a *path operation* { #deprecate-a-path-operation }
If you need to mark a *path operation* as <abbr title="obsolete, recommended not to use it">deprecated</abbr>, but without removing it, pass the parameter `deprecated`: If you need to mark a *path operation* as <dfn title="obsolete, recommended not to use it">deprecated</dfn>, but without removing it, pass the parameter `deprecated`:
{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} {* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *}

8
docs/en/docs/tutorial/path-params.md

@ -26,7 +26,7 @@ This will give you editor support inside of your function, with error checks, co
/// ///
## Data <abbr title="also known as: serialization, parsing, marshalling">conversion</abbr> { #data-conversion } ## Data <dfn title="also known as: serialization, parsing, marshalling">conversion</dfn> { #data-conversion }
If you run this example and open your browser at <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, you will see a response of: If you run this example and open your browser at <a href="http://127.0.0.1:8000/items/3" class="external-link" target="_blank">http://127.0.0.1:8000/items/3</a>, you will see a response of:
@ -38,7 +38,7 @@ If you run this example and open your browser at <a href="http://127.0.0.1:8000/
Notice that the value your function received (and returned) is `3`, as a Python `int`, not a string `"3"`. Notice that the value your function received (and returned) is `3`, as a Python `int`, not a string `"3"`.
So, with that type declaration, **FastAPI** gives you automatic request <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr>. So, with that type declaration, **FastAPI** gives you automatic request <dfn title="converting the string that comes from an HTTP request into Python data">"parsing"</dfn>.
/// ///
@ -144,7 +144,7 @@ Then create class attributes with fixed values, which will be the available vali
/// tip /// tip
If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning <abbr title="Technically, Deep Learning model architectures">models</abbr>. If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Learning <dfn title="Technically, Deep Learning model architectures">models</dfn>.
/// ///
@ -242,7 +242,7 @@ In that case, the URL would be: `/files//home/johndoe/myfile.txt`, with a double
With **FastAPI**, by using short, intuitive and standard Python type declarations, you get: With **FastAPI**, by using short, intuitive and standard Python type declarations, you get:
* Editor support: error checks, autocompletion, etc. * Editor support: error checks, autocompletion, etc.
* Data "<abbr title="converting the string that comes from an HTTP request into Python data">parsing</abbr>" * Data "<dfn title="converting the string that comes from an HTTP request into Python data">parsing</dfn>"
* Data validation * Data validation
* API annotation and automatic documentation * API annotation and automatic documentation

34
docs/en/docs/tutorial/query-params-str-validations.md

@ -47,40 +47,16 @@ Now it's the time to use it with FastAPI. 🚀
We had this type annotation: We had this type annotation:
//// tab | Python 3.10+
```Python ```Python
q: str | None = None q: str | None = None
``` ```
////
//// tab | Python 3.9+
```Python
q: Union[str, None] = None
```
////
What we will do is wrap that with `Annotated`, so it becomes: What we will do is wrap that with `Annotated`, so it becomes:
//// tab | Python 3.10+
```Python ```Python
q: Annotated[str | None] = None q: Annotated[str | None] = None
``` ```
////
//// tab | Python 3.9+
```Python
q: Annotated[Union[str, None]] = None
```
////
Both of those versions mean the same thing, `q` is a parameter that can be a `str` or `None`, and by default, it is `None`. Both of those versions mean the same thing, `q` is a parameter that can be a `str` or `None`, and by default, it is `None`.
Now let's jump to the fun stuff. 🎉 Now let's jump to the fun stuff. 🎉
@ -109,7 +85,7 @@ FastAPI will now:
## Alternative (old): `Query` as the default value { #alternative-old-query-as-the-default-value } ## Alternative (old): `Query` as the default value { #alternative-old-query-as-the-default-value }
Previous versions of FastAPI (before <abbr title="before 2023-03">0.95.0</abbr>) required you to use `Query` as the default value of your parameter, instead of putting it in `Annotated`, there's a high chance that you will see code using it around, so I'll explain it to you. Previous versions of FastAPI (before <dfn title="before 2023-03">0.95.0</dfn>) required you to use `Query` as the default value of your parameter, instead of putting it in `Annotated`, there's a high chance that you will see code using it around, so I'll explain it to you.
/// tip /// tip
@ -192,7 +168,7 @@ You can also add a parameter `min_length`:
## Add regular expressions { #add-regular-expressions } ## Add regular expressions { #add-regular-expressions }
You can define a <abbr title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings.">regular expression</abbr> `pattern` that the parameter should match: You can define a <dfn title="A regular expression, regex or regexp is a sequence of characters that define a search pattern for strings.">regular expression</dfn> `pattern` that the parameter should match:
{* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *}
@ -372,7 +348,7 @@ Then you can declare an `alias`, and that alias is what will be used to find the
Now let's say you don't like this parameter anymore. Now let's say you don't like this parameter anymore.
You have to leave it there a while because there are clients using it, but you want the docs to clearly show it as <abbr title="obsolete, recommended not to use it">deprecated</abbr>. You have to leave it there a while because there are clients using it, but you want the docs to clearly show it as <dfn title="obsolete, recommended not to use it">deprecated</dfn>.
Then pass the parameter `deprecated=True` to `Query`: Then pass the parameter `deprecated=True` to `Query`:
@ -402,7 +378,7 @@ Pydantic also has <a href="https://docs.pydantic.dev/latest/concepts/validators/
/// ///
For example, this custom validator checks that the item ID starts with `isbn-` for an <abbr title="ISBN means International Standard Book Number">ISBN</abbr> book number or with `imdb-` for an <abbr title="IMDB (Internet Movie Database) is a website with information about movies">IMDB</abbr> movie URL ID: For example, this custom validator checks that the item ID starts with `isbn-` for an <abbr title="International Standard Book Number">ISBN</abbr> book number or with `imdb-` for an <abbr title="Internet Movie Database: a website with information about movies">IMDB</abbr> movie URL ID:
{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} {* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *}
@ -436,7 +412,7 @@ Did you notice? a string using `value.startswith()` can take a tuple, and it wil
#### A Random Item { #a-random-item } #### A Random Item { #a-random-item }
With `data.items()` we get an <abbr title="Something we can iterate on with a for loop, like a list, set, etc.">iterable object</abbr> with tuples containing the key and value for each dictionary item. With `data.items()` we get an <dfn title="Something we can iterate on with a for loop, like a list, set, etc.">iterable object</dfn> with tuples containing the key and value for each dictionary item.
We convert this iterable object into a proper `list` with `list(data.items())`. We convert this iterable object into a proper `list` with `list(data.items())`.

2
docs/en/docs/tutorial/query-params.md

@ -24,7 +24,7 @@ But when you declare them with Python types (in the example above, as `int`), th
All the same process that applied for path parameters also applies for query parameters: All the same process that applied for path parameters also applies for query parameters:
* Editor support (obviously) * Editor support (obviously)
* Data <abbr title="converting the string that comes from an HTTP request into Python data">"parsing"</abbr> * Data <dfn title="converting the string that comes from an HTTP request into Python data">"parsing"</dfn>
* Data validation * Data validation
* Automatic documentation * Automatic documentation

2
docs/en/docs/tutorial/request-forms.md

@ -28,7 +28,7 @@ Create form parameters the same way you would for `Body` or `Query`:
For example, in one of the ways the OAuth2 specification can be used (called "password flow") it is required to send a `username` and `password` as form fields. For example, in one of the ways the OAuth2 specification can be used (called "password flow") it is required to send a `username` and `password` as form fields.
The <abbr title="specification">spec</abbr> requires the fields to be exactly named `username` and `password`, and to be sent as form fields, not JSON. The <dfn title="specification">spec</dfn> requires the fields to be exactly named `username` and `password`, and to be sent as form fields, not JSON.
With `Form` you can declare the same configurations as with `Body` (and `Query`, `Path`, `Cookie`), including validation, examples, an alias (e.g. `user-name` instead of `username`), etc. With `Form` you can declare the same configurations as with `Body` (and `Query`, `Path`, `Cookie`), including validation, examples, an alias (e.g. `user-name` instead of `username`), etc.

2
docs/en/docs/tutorial/response-model.md

@ -201,7 +201,7 @@ This will also work because `RedirectResponse` is a subclass of `Response`, and
But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object) and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that type annotation, and will fail. But when you return some other arbitrary object that is not a valid Pydantic type (e.g. a database object) and you annotate it like that in the function, FastAPI will try to create a Pydantic response model from that type annotation, and will fail.
The same would happen if you had something like a <abbr title='A union between multiple types means "any of these types".'>union</abbr> between different types where one or more of them are not valid Pydantic types, for example this would fail 💥: The same would happen if you had something like a <dfn title='A union between multiple types means "any of these types".'>union</dfn> between different types where one or more of them are not valid Pydantic types, for example this would fail 💥:
{* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *} {* ../../docs_src/response_model/tutorial003_04_py310.py hl[8] *}

2
docs/en/docs/tutorial/schema-extra-example.md

@ -74,7 +74,7 @@ You can of course also pass multiple `examples`:
When you do this, the examples will be part of the internal **JSON Schema** for that body data. When you do this, the examples will be part of the internal **JSON Schema** for that body data.
Nevertheless, at the <abbr title="2023-08-26">time of writing this</abbr>, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple examples for the data in **JSON Schema**. But read below for a workaround. Nevertheless, at the <dfn title="2023-08-26">time of writing this</dfn>, Swagger UI, the tool in charge of showing the docs UI, doesn't support showing multiple examples for the data in **JSON Schema**. But read below for a workaround.
### OpenAPI-specific `examples` { #openapi-specific-examples } ### OpenAPI-specific `examples` { #openapi-specific-examples }

2
docs/en/docs/virtual-environments.md

@ -53,7 +53,7 @@ $ cd awesome-project
## Create a Virtual Environment { #create-a-virtual-environment } ## Create a Virtual Environment { #create-a-virtual-environment }
When you start working on a Python project **for the first time**, create a virtual environment **<abbr title="there are other options, this is a simple guideline">inside your project</abbr>**. When you start working on a Python project **for the first time**, create a virtual environment **<dfn title="there are other options, this is a simple guideline">inside your project</dfn>**.
/// tip /// tip

1
docs/en/mkdocs.yml

@ -191,6 +191,7 @@ nav:
- advanced/openapi-webhooks.md - advanced/openapi-webhooks.md
- advanced/wsgi.md - advanced/wsgi.md
- advanced/generate-clients.md - advanced/generate-clients.md
- advanced/advanced-python-types.md
- fastapi-cli.md - fastapi-cli.md
- Deployment: - Deployment:
- deployment/index.md - deployment/index.md

14
docs/es/docs/_llm-test.md

@ -202,15 +202,10 @@ Aquí algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas):
* <abbr title="XML Web Token - Token web XML">XWT</abbr> * <abbr title="XML Web Token - Token web XML">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Interfaz de pasarela de servidor paralela">PSGI</abbr> * <abbr title="Parallel Server Gateway Interface - Interfaz de pasarela de servidor paralela">PSGI</abbr>
### El abbr da una explicación { #the-abbr-gives-an-explanation }
* <abbr title="Un grupo de máquinas configuradas para estar conectadas y trabajar juntas de alguna manera.">clúster</abbr>
* <abbr title="Un método de machine learning que usa redes neuronales artificiales con numerosas capas ocultas entre las capas de entrada y salida, desarrollando así una estructura interna completa">Deep Learning</abbr>
### El abbr da una frase completa y una explicación { #the-abbr-gives-a-full-phrase-and-an-explanation } ### El abbr da una frase completa y una explicación { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - Red de Desarrolladores de Mozilla: documentación para desarrolladores, escrita por la gente de Firefox">MDN</abbr> * <abbr title="Mozilla Developer Network - Red de Desarrolladores de Mozilla: documentación para desarrolladores, escrita por la gente de Firefox">MDN</abbr>
* <abbr title="Input/Output: lectura o escritura de disco, comunicaciones de red.">I/O</abbr>. * <abbr title="Input/Output - Entrada/Salida: lectura o escritura de disco, comunicaciones de red.">I/O</abbr>.
//// ////
@ -224,6 +219,11 @@ Consulta la sección `### HTML abbr elements` en el prompt general en `scripts/t
//// ////
## Elementos HTML "dfn" { #html-dfn-elements }
* <dfn title="Un grupo de máquinas configuradas para estar conectadas y trabajar juntas de alguna manera.">clúster</dfn>
* <dfn title="Un método de Machine Learning que usa redes neuronales artificiales con numerosas capas ocultas entre las capas de entrada y salida, desarrollando así una estructura interna completa">Deep Learning</dfn>
## Encabezados { #headings } ## Encabezados { #headings }
//// tab | Prueba //// tab | Prueba
@ -433,7 +433,7 @@ Para instrucciones específicas del idioma, mira p. ej. la sección `### Heading
* el motor de plantillas * el motor de plantillas
* la anotación de tipos * la anotación de tipos
* las anotaciones de tipos * la anotación de tipos
* el worker del servidor * el worker del servidor
* el worker de Uvicorn * el worker de Uvicorn

12
docs/ko/docs/_llm-test.md

@ -132,7 +132,7 @@ works(foo="bar") # 이건 동작합니다 🎉
일부 텍스트 일부 텍스트
/// ///
/// note Technical details | 기술 세부사항 /// note | 기술 세부사항
일부 텍스트 일부 텍스트
/// ///
@ -202,11 +202,6 @@ works(foo="bar") # 이건 동작합니다 🎉
* <abbr title="XML Web Token - XML 웹 토큰">XWT</abbr> * <abbr title="XML Web Token - XML 웹 토큰">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - 병렬 서버 게이트웨이 인터페이스">PSGI</abbr> * <abbr title="Parallel Server Gateway Interface - 병렬 서버 게이트웨이 인터페이스">PSGI</abbr>
### abbr가 설명을 제공 { #the-abbr-gives-an-explanation }
* <abbr title="어떤 방식으로든 서로 연결되고 함께 작동하도록 구성된 머신들의 집합입니다.">cluster</abbr>
* <abbr title="입력과 출력 계층 사이에 수많은 은닉 계층을 둔 인공 신경망을 사용하는 머신 러닝 방법으로, 이를 통해 포괄적인 내부 구조를 형성합니다">Deep Learning</abbr>
### abbr가 전체 문구와 설명을 제공 { #the-abbr-gives-a-full-phrase-and-an-explanation } ### abbr가 전체 문구와 설명을 제공 { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - 모질라 개발자 네트워크: Firefox를 만드는 사람들이 작성한 개발자용 문서">MDN</abbr> * <abbr title="Mozilla Developer Network - 모질라 개발자 네트워크: Firefox를 만드는 사람들이 작성한 개발자용 문서">MDN</abbr>
@ -224,6 +219,11 @@ works(foo="bar") # 이건 동작합니다 🎉
//// ////
## HTML "dfn" 요소 { #html-dfn-elements }
* <dfn title="어떤 방식으로든 서로 연결되고 함께 작동하도록 구성된 머신들의 집합입니다.">클러스터</dfn>
* <dfn title="입력과 출력 계층 사이에 수많은 은닉 계층을 둔 인공 신경망을 사용하는 머신 러닝 방법으로, 이를 통해 포괄적인 내부 구조를 형성합니다">딥 러닝</dfn>
## 제목 { #headings } ## 제목 { #headings }
//// tab | 테스트 //// tab | 테스트

18
docs/pt/docs/_llm-test.md

@ -197,15 +197,10 @@ Aqui estão algumas coisas envolvidas em elementos HTML "abbr" (algumas são inv
### O abbr fornece uma frase completa { #the-abbr-gives-a-full-phrase } ### O abbr fornece uma frase completa { #the-abbr-gives-a-full-phrase }
* <abbr title="Getting Things Done">GTD</abbr> * <abbr title="Getting Things Done – Fazer as Coisas">GTD</abbr>
* <abbr title="less than - menos que"><code>lt</code></abbr> * <abbr title="less than – menos que"><code>lt</code></abbr>
* <abbr title="XML Web Token">XWT</abbr> * <abbr title="XML Web Token – Token Web XML">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Interface de Gateway de Servidor Paralelo">PSGI</abbr> * <abbr title="Parallel Server Gateway Interface – Interface de Gateway de Servidor Paralelo">PSGI</abbr>
### O abbr fornece uma explicação { #the-abbr-gives-an-explanation }
* <abbr title="Um grupo de máquinas configuradas para estarem conectadas e trabalharem juntas de alguma forma.">cluster</abbr>
* <abbr title="Um método de aprendizado de máquina que usa redes neurais artificiais com numerosas camadas ocultas entre as camadas de entrada e saída, desenvolvendo assim uma estrutura interna abrangente">Deep Learning</abbr>
### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation } ### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation }
@ -224,6 +219,11 @@ Veja a seção `### HTML abbr elements` no prompt geral em `scripts/translate.py
//// ////
## Elementos HTML "dfn" { #html-dfn-elements }
* <dfn title="Um grupo de máquinas configuradas para estarem conectadas e trabalharem juntas de alguma forma.">cluster</dfn>
* <dfn title="Um método de aprendizado de máquina que usa redes neurais artificiais com numerosas camadas ocultas entre as camadas de entrada e saída, desenvolvendo assim uma estrutura interna abrangente">Deep Learning</dfn>
## Títulos { #headings } ## Títulos { #headings }
//// tab | Teste //// tab | Teste

10
docs/ru/docs/_llm-test.md

@ -202,11 +202,6 @@ works(foo="bar") # Это работает 🎉
* <abbr title="XML Web Token - XML веб‑токен">XWT</abbr> * <abbr title="XML Web Token - XML веб‑токен">XWT</abbr>
* <abbr title="Parallel Server Gateway Interface - Параллельный серверный интерфейс шлюза">PSGI</abbr> * <abbr title="Parallel Server Gateway Interface - Параллельный серверный интерфейс шлюза">PSGI</abbr>
### abbr даёт объяснение { #the-abbr-gives-an-explanation }
* <abbr title="Группа машин, которые настроены на соединение и совместную работу определённым образом.">кластер</abbr>
* <abbr title="Метод машинного обучения, который использует искусственные нейронные сети с многочисленными скрытыми слоями между входным и выходным слоями, тем самым формируя сложную внутреннюю структуру">Глубокое обучение</abbr>
### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation } ### abbr даёт полную расшифровку и объяснение { #the-abbr-gives-a-full-phrase-and-an-explanation }
* <abbr title="Mozilla Developer Network - Сеть разработчиков Mozilla: документация для разработчиков, созданная командой Firefox">MDN</abbr> * <abbr title="Mozilla Developer Network - Сеть разработчиков Mozilla: документация для разработчиков, созданная командой Firefox">MDN</abbr>
@ -224,6 +219,11 @@ works(foo="bar") # Это работает 🎉
//// ////
## HTML-элементы "dfn" { #html-dfn-elements }
* <dfn title="Группа машин, которые настроены на соединение и совместную работу определённым образом.">кластер</dfn>
* <dfn title="Метод машинного обучения, который использует искусственные нейронные сети с многочисленными скрытыми слоями между входным и выходным слоями, тем самым формируя сложную внутреннюю структуру">Глубокое обучение</dfn>
## Заголовки { #headings } ## Заголовки { #headings }
//// tab | Тест //// tab | Тест

54
fastapi-slim/README.md

@ -0,0 +1,54 @@
<p align="center">
<a href="https://fastapi.tiangolo.com"><img src="https://fastapi.tiangolo.com/img/logo-margin/logo-teal.png" alt="FastAPI"></a>
</p>
<p align="center">
<em>FastAPI framework, high performance, easy to learn, fast to code, ready for production</em>
</p>
<p align="center">
<a href="https://github.com/fastapi/fastapi/actions?query=workflow%3ATest+event%3Apush+branch%3Amaster" target="_blank">
<img src="https://github.com/fastapi/fastapi/actions/workflows/test.yml/badge.svg?event=push&branch=master" alt="Test">
</a>
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/fastapi" target="_blank">
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/fastapi.svg" alt="Coverage">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/v/fastapi?color=%2334D058&label=pypi%20package" alt="Package version">
</a>
<a href="https://pypi.org/project/fastapi" target="_blank">
<img src="https://img.shields.io/pypi/pyversions/fastapi.svg?color=%2334D058" alt="Supported Python versions">
</a>
</p>
---
**Documentation**: <a href="https://fastapi.tiangolo.com" target="_blank">https://fastapi.tiangolo.com</a>
**Source Code**: <a href="https://github.com/fastapi/fastapi" target="_blank">https://github.com/fastapi/fastapi</a>
---
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints.
## `fastapi-slim`
⚠️ Do not install this package. ⚠️
This package, `fastapi-slim`, does nothing other than depend on `fastapi`.
All the functionality has been integrated into `fastapi`.
The only reason this package exists is as a migration path for old projects that used to depend on `fastapi-slim`, so that they can get the latest version of `fastapi`.
You **should not** install this package.
Install instead:
```bash
pip install fastapi
```
This package is deprecated and will stop receiving any updates and published versions.
## License
This project is licensed under the terms of the MIT license.

2
fastapi/__init__.py

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

30
fastapi/_compat/shared.py

@ -1,4 +1,3 @@
import sys
import types import types
import typing import typing
import warnings import warnings
@ -8,27 +7,26 @@ from dataclasses import is_dataclass
from typing import ( from typing import (
Annotated, Annotated,
Any, Any,
TypeGuard,
TypeVar, TypeVar,
Union, Union,
get_args,
get_origin,
) )
from fastapi.types import UnionType from fastapi.types import UnionType
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION from pydantic.version import VERSION as PYDANTIC_VERSION
from starlette.datastructures import UploadFile from starlette.datastructures import UploadFile
from typing_extensions import TypeGuard, get_args, get_origin
_T = TypeVar("_T") _T = TypeVar("_T")
# Copy from Pydantic: pydantic/_internal/_typing_extra.py # Copy from Pydantic: pydantic/_internal/_typing_extra.py
if sys.version_info < (3, 10): WithArgsTypes: tuple[Any, ...] = (
WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined] typing._GenericAlias, # type: ignore[attr-defined]
else: types.GenericAlias,
WithArgsTypes: tuple[Any, ...] = ( types.UnionType,
typing._GenericAlias, # type: ignore[attr-defined] ) # pyright: ignore[reportAttributeAccessIssue]
types.GenericAlias,
types.UnionType,
) # pyright: ignore[reportAttributeAccessIssue]
PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
@ -47,7 +45,7 @@ sequence_types: tuple[type[Any], ...] = tuple(sequence_annotation_to_type.keys()
# Copy of Pydantic: pydantic/_internal/_utils.py with added TypeGuard # Copy of Pydantic: pydantic/_internal/_utils.py with added TypeGuard
def lenient_issubclass( def lenient_issubclass(
cls: Any, class_or_tuple: Union[type[_T], tuple[type[_T], ...], None] cls: Any, class_or_tuple: type[_T] | tuple[type[_T], ...] | None
) -> TypeGuard[type[_T]]: ) -> TypeGuard[type[_T]]:
try: try:
return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type]
@ -57,13 +55,13 @@ def lenient_issubclass(
raise # pragma: no cover raise # pragma: no cover
def _annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: def _annotation_is_sequence(annotation: type[Any] | None) -> bool:
if lenient_issubclass(annotation, (str, bytes)): if lenient_issubclass(annotation, (str, bytes)):
return False return False
return lenient_issubclass(annotation, sequence_types) return lenient_issubclass(annotation, sequence_types)
def field_annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: def field_annotation_is_sequence(annotation: type[Any] | None) -> bool:
origin = get_origin(annotation) origin = get_origin(annotation)
if origin is Annotated: if origin is Annotated:
@ -83,7 +81,7 @@ def value_is_sequence(value: Any) -> bool:
return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) return isinstance(value, sequence_types) and not isinstance(value, (str, bytes))
def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool: def _annotation_is_complex(annotation: type[Any] | None) -> bool:
return ( return (
lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
or _annotation_is_sequence(annotation) or _annotation_is_sequence(annotation)
@ -91,7 +89,7 @@ def _annotation_is_complex(annotation: Union[type[Any], None]) -> bool:
) )
def field_annotation_is_complex(annotation: Union[type[Any], None]) -> bool: def field_annotation_is_complex(annotation: type[Any] | None) -> bool:
origin = get_origin(annotation) origin = get_origin(annotation)
if origin is Union or origin is UnionType: if origin is Union or origin is UnionType:
return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
@ -112,7 +110,7 @@ def field_annotation_is_scalar(annotation: Any) -> bool:
return annotation is Ellipsis or not field_annotation_is_complex(annotation) return annotation is Ellipsis or not field_annotation_is_complex(annotation)
def field_annotation_is_scalar_sequence(annotation: Union[type[Any], None]) -> bool: def field_annotation_is_scalar_sequence(annotation: type[Any] | None) -> bool:
origin = get_origin(annotation) origin = get_origin(annotation)
if origin is Annotated: if origin is Annotated:

26
fastapi/_compat/v2.py

@ -8,8 +8,11 @@ from functools import lru_cache
from typing import ( from typing import (
Annotated, Annotated,
Any, Any,
Literal,
Union, Union,
cast, cast,
get_args,
get_origin,
) )
from fastapi._compat import lenient_issubclass, shared from fastapi._compat import lenient_issubclass, shared
@ -32,7 +35,6 @@ from pydantic_core import Url as Url
from pydantic_core.core_schema import ( from pydantic_core.core_schema import (
with_info_plain_validator_function as with_info_plain_validator_function, with_info_plain_validator_function as with_info_plain_validator_function,
) )
from typing_extensions import Literal, get_args, get_origin
RequiredParam = PydanticUndefined RequiredParam = PydanticUndefined
Undefined = PydanticUndefined Undefined = PydanticUndefined
@ -83,7 +85,7 @@ class ModelField:
field_info: FieldInfo field_info: FieldInfo
name: str name: str
mode: Literal["validation", "serialization"] = "validation" mode: Literal["validation", "serialization"] = "validation"
config: Union[ConfigDict, None] = None config: ConfigDict | None = None
@property @property
def alias(self) -> str: def alias(self) -> str:
@ -91,14 +93,14 @@ class ModelField:
return a if a is not None else self.name return a if a is not None else self.name
@property @property
def validation_alias(self) -> Union[str, None]: def validation_alias(self) -> str | None:
va = self.field_info.validation_alias va = self.field_info.validation_alias
if isinstance(va, str) and va: if isinstance(va, str) and va:
return va return va
return None return None
@property @property
def serialization_alias(self) -> Union[str, None]: def serialization_alias(self) -> str | None:
sa = self.field_info.serialization_alias sa = self.field_info.serialization_alias
return sa or None return sa or None
@ -143,7 +145,7 @@ class ModelField:
value: Any, value: Any,
values: dict[str, Any] = {}, # noqa: B006 values: dict[str, Any] = {}, # noqa: B006
*, *,
loc: tuple[Union[int, str], ...] = (), loc: tuple[int | str, ...] = (),
) -> tuple[Any, list[dict[str, Any]]]: ) -> tuple[Any, list[dict[str, Any]]]:
try: try:
return ( return (
@ -160,8 +162,8 @@ class ModelField:
value: Any, value: Any,
*, *,
mode: Literal["json", "python"] = "json", mode: Literal["json", "python"] = "json",
include: Union[IncEx, None] = None, include: IncEx | None = None,
exclude: Union[IncEx, None] = None, exclude: IncEx | None = None,
by_alias: bool = True, by_alias: bool = True,
exclude_unset: bool = False, exclude_unset: bool = False,
exclude_defaults: bool = False, exclude_defaults: bool = False,
@ -202,7 +204,7 @@ def get_schema_from_model_field(
], ],
separate_input_output_schemas: bool = True, separate_input_output_schemas: bool = True,
) -> dict[str, Any]: ) -> dict[str, Any]:
override_mode: Union[Literal["validation"], None] = ( override_mode: Literal["validation"] | None = (
None None
if (separate_input_output_schemas or _has_computed_fields(field)) if (separate_input_output_schemas or _has_computed_fields(field))
else "validation" else "validation"
@ -318,7 +320,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index]
def get_missing_field_error(loc: tuple[Union[int, str], ...]) -> dict[str, Any]: def get_missing_field_error(loc: tuple[int | str, ...]) -> dict[str, Any]:
error = ValidationError.from_exception_data( error = ValidationError.from_exception_data(
"Field required", [{"type": "missing", "loc": loc, "input": {}}] "Field required", [{"type": "missing", "loc": loc, "input": {}}]
).errors(include_url=False)[0] ).errors(include_url=False)[0]
@ -360,7 +362,7 @@ def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]:
# Duplicate of several schema functions from Pydantic v1 to make them compatible with # Duplicate of several schema functions from Pydantic v1 to make them compatible with
# Pydantic v2 and allow mixing the models # Pydantic v2 and allow mixing the models
TypeModelOrEnum = Union[type["BaseModel"], type[Enum]] TypeModelOrEnum = type["BaseModel"] | type[Enum]
TypeModelSet = set[TypeModelOrEnum] TypeModelSet = set[TypeModelOrEnum]
@ -377,7 +379,7 @@ def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str
def get_flat_models_from_model( def get_flat_models_from_model(
model: type["BaseModel"], known_models: Union[TypeModelSet, None] = None model: type["BaseModel"], known_models: TypeModelSet | None = None
) -> TypeModelSet: ) -> TypeModelSet:
known_models = known_models or set() known_models = known_models or set()
fields = get_model_fields(model) fields = get_model_fields(model)
@ -426,7 +428,7 @@ def get_flat_models_from_fields(
def _regenerate_error_with_loc( def _regenerate_error_with_loc(
*, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...] *, errors: Sequence[Any], loc_prefix: tuple[str | int, ...]
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
updated_loc_errors: list[Any] = [ updated_loc_errors: list[Any] = [
{**err, "loc": loc_prefix + err.get("loc", ())} for err in errors {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors

354
fastapi/applications.py

File diff suppressed because it is too large

3
fastapi/background.py

@ -1,4 +1,5 @@
from typing import Annotated, Any, Callable from collections.abc import Callable
from typing import Annotated, Any
from annotated_doc import Doc from annotated_doc import Doc
from starlette.background import BackgroundTasks as StarletteBackgroundTasks from starlette.background import BackgroundTasks as StarletteBackgroundTasks

10
fastapi/datastructures.py

@ -1,10 +1,8 @@
from collections.abc import Mapping from collections.abc import Callable, Mapping
from typing import ( from typing import (
Annotated, Annotated,
Any, Any,
BinaryIO, BinaryIO,
Callable,
Optional,
TypeVar, TypeVar,
cast, cast,
) )
@ -58,11 +56,11 @@ class UploadFile(StarletteUploadFile):
BinaryIO, BinaryIO,
Doc("The standard Python file object (non-async)."), Doc("The standard Python file object (non-async)."),
] ]
filename: Annotated[Optional[str], Doc("The original file name.")] filename: Annotated[str | None, Doc("The original file name.")]
size: Annotated[Optional[int], Doc("The size of the file in bytes.")] size: Annotated[int | None, Doc("The size of the file in bytes.")]
headers: Annotated[Headers, Doc("The headers of the request.")] headers: Annotated[Headers, Doc("The headers of the request.")]
content_type: Annotated[ content_type: Annotated[
Optional[str], Doc("The content type of the request, from the headers.") str | None, Doc("The content type of the request, from the headers.")
] ]
async def write( async def write(

32
fastapi/dependencies/models.py

@ -1,13 +1,13 @@
import inspect import inspect
import sys import sys
from collections.abc import Callable
from dataclasses import dataclass, field from dataclasses import dataclass, field
from functools import cached_property, partial from functools import cached_property, partial
from typing import Any, Callable, Optional, Union from typing import Any, Literal
from fastapi._compat import ModelField from fastapi._compat import ModelField
from fastapi.security.base import SecurityBase from fastapi.security.base import SecurityBase
from fastapi.types import DependencyCacheKey from fastapi.types import DependencyCacheKey
from typing_extensions import Literal
if sys.version_info >= (3, 13): # pragma: no cover if sys.version_info >= (3, 13): # pragma: no cover
from inspect import iscoroutinefunction from inspect import iscoroutinefunction
@ -15,7 +15,7 @@ else: # pragma: no cover
from asyncio import iscoroutinefunction from asyncio import iscoroutinefunction
def _unwrapped_call(call: Optional[Callable[..., Any]]) -> Any: def _unwrapped_call(call: Callable[..., Any] | None) -> Any:
if call is None: if call is None:
return call # pragma: no cover return call # pragma: no cover
unwrapped = inspect.unwrap(_impartial(call)) unwrapped = inspect.unwrap(_impartial(call))
@ -36,19 +36,19 @@ class Dependant:
cookie_params: list[ModelField] = field(default_factory=list) cookie_params: list[ModelField] = field(default_factory=list)
body_params: list[ModelField] = field(default_factory=list) body_params: list[ModelField] = field(default_factory=list)
dependencies: list["Dependant"] = field(default_factory=list) dependencies: list["Dependant"] = field(default_factory=list)
name: Optional[str] = None name: str | None = None
call: Optional[Callable[..., Any]] = None call: Callable[..., Any] | None = None
request_param_name: Optional[str] = None request_param_name: str | None = None
websocket_param_name: Optional[str] = None websocket_param_name: str | None = None
http_connection_param_name: Optional[str] = None http_connection_param_name: str | None = None
response_param_name: Optional[str] = None response_param_name: str | None = None
background_tasks_param_name: Optional[str] = None background_tasks_param_name: str | None = None
security_scopes_param_name: Optional[str] = None security_scopes_param_name: str | None = None
own_oauth_scopes: Optional[list[str]] = None own_oauth_scopes: list[str] | None = None
parent_oauth_scopes: Optional[list[str]] = None parent_oauth_scopes: list[str] | None = None
use_cache: bool = True use_cache: bool = True
path: Optional[str] = None path: str | None = None
scope: Union[Literal["function", "request"], None] = None scope: Literal["function", "request"] | None = None
@cached_property @cached_property
def oauth_scopes(self) -> list[str]: def oauth_scopes(self) -> list[str]:
@ -185,7 +185,7 @@ class Dependant:
return False return False
@cached_property @cached_property
def computed_scope(self) -> Union[str, None]: def computed_scope(self) -> str | None:
if self.scope: if self.scope:
return self.scope return self.scope
if self.is_gen_callable or self.is_async_gen_callable: if self.is_gen_callable or self.is_async_gen_callable:

92
fastapi/dependencies/utils.py

@ -1,21 +1,21 @@
import dataclasses import dataclasses
import inspect import inspect
import sys import sys
from collections.abc import Coroutine, Mapping, Sequence from collections.abc import Callable, Mapping, Sequence
from contextlib import AsyncExitStack, contextmanager from contextlib import AsyncExitStack, contextmanager
from copy import copy, deepcopy from copy import copy, deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from typing import ( from typing import (
Annotated, Annotated,
Any, Any,
Callable,
ForwardRef, ForwardRef,
Optional, Literal,
Union, Union,
cast, cast,
get_args,
get_origin,
) )
import anyio
from fastapi import params from fastapi import params
from fastapi._compat import ( from fastapi._compat import (
ModelField, ModelField,
@ -64,7 +64,6 @@ from starlette.datastructures import (
from starlette.requests import HTTPConnection, Request from starlette.requests import HTTPConnection, Request
from starlette.responses import Response from starlette.responses import Response
from starlette.websockets import WebSocket from starlette.websockets import WebSocket
from typing_extensions import Literal, get_args, get_origin
from typing_inspection.typing_objects import is_typealiastype from typing_inspection.typing_objects import is_typealiastype
multipart_not_installed_error = ( multipart_not_installed_error = (
@ -128,8 +127,8 @@ def get_flat_dependant(
dependant: Dependant, dependant: Dependant,
*, *,
skip_repeats: bool = False, skip_repeats: bool = False,
visited: Optional[list[DependencyCacheKey]] = None, visited: list[DependencyCacheKey] | None = None,
parent_oauth_scopes: Optional[list[str]] = None, parent_oauth_scopes: list[str] | None = None,
) -> Dependant: ) -> Dependant:
if visited is None: if visited is None:
visited = [] visited = []
@ -200,20 +199,17 @@ def get_flat_params(dependant: Dependant) -> list[ModelField]:
def _get_signature(call: Callable[..., Any]) -> inspect.Signature: def _get_signature(call: Callable[..., Any]) -> inspect.Signature:
if sys.version_info >= (3, 10): try:
try: signature = inspect.signature(call, eval_str=True)
signature = inspect.signature(call, eval_str=True) except NameError:
except NameError: # Handle type annotations with if TYPE_CHECKING, not used by FastAPI
# Handle type annotations with if TYPE_CHECKING, not used by FastAPI # e.g. dependency return types
# e.g. dependency return types if sys.version_info >= (3, 14):
if sys.version_info >= (3, 14): from annotationlib import Format
from annotationlib import Format
signature = inspect.signature(call, annotation_format=Format.FORWARDREF)
signature = inspect.signature(call, annotation_format=Format.FORWARDREF) else:
else: signature = inspect.signature(call)
signature = inspect.signature(call)
else:
signature = inspect.signature(call)
return signature return signature
@ -259,11 +255,11 @@ def get_dependant(
*, *,
path: str, path: str,
call: Callable[..., Any], call: Callable[..., Any],
name: Optional[str] = None, name: str | None = None,
own_oauth_scopes: Optional[list[str]] = None, own_oauth_scopes: list[str] | None = None,
parent_oauth_scopes: Optional[list[str]] = None, parent_oauth_scopes: list[str] | None = None,
use_cache: bool = True, use_cache: bool = True,
scope: Union[Literal["function", "request"], None] = None, scope: Literal["function", "request"] | None = None,
) -> Dependant: ) -> Dependant:
dependant = Dependant( dependant = Dependant(
call=call, call=call,
@ -332,7 +328,7 @@ def get_dependant(
def add_non_field_param_to_dependency( def add_non_field_param_to_dependency(
*, param_name: str, type_annotation: Any, dependant: Dependant *, param_name: str, type_annotation: Any, dependant: Dependant
) -> Optional[bool]: ) -> bool | None:
if lenient_issubclass(type_annotation, Request): if lenient_issubclass(type_annotation, Request):
dependant.request_param_name = param_name dependant.request_param_name = param_name
return True return True
@ -357,8 +353,8 @@ def add_non_field_param_to_dependency(
@dataclass @dataclass
class ParamDetails: class ParamDetails:
type_annotation: Any type_annotation: Any
depends: Optional[params.Depends] depends: params.Depends | None
field: Optional[ModelField] field: ModelField | None
def analyze_param( def analyze_param(
@ -400,7 +396,7 @@ def analyze_param(
) )
] ]
if fastapi_specific_annotations: if fastapi_specific_annotations:
fastapi_annotation: Union[FieldInfo, params.Depends, None] = ( fastapi_annotation: FieldInfo | params.Depends | None = (
fastapi_specific_annotations[-1] fastapi_specific_annotations[-1]
) )
else: else:
@ -561,20 +557,20 @@ async def _solve_generator(
class SolvedDependency: class SolvedDependency:
values: dict[str, Any] values: dict[str, Any]
errors: list[Any] errors: list[Any]
background_tasks: Optional[StarletteBackgroundTasks] background_tasks: StarletteBackgroundTasks | None
response: Response response: Response
dependency_cache: dict[DependencyCacheKey, Any] dependency_cache: dict[DependencyCacheKey, Any]
async def solve_dependencies( async def solve_dependencies(
*, *,
request: Union[Request, WebSocket], request: Request | WebSocket,
dependant: Dependant, dependant: Dependant,
body: Optional[Union[dict[str, Any], FormData]] = None, body: dict[str, Any] | FormData | None = None,
background_tasks: Optional[StarletteBackgroundTasks] = None, background_tasks: StarletteBackgroundTasks | None = None,
response: Optional[Response] = None, response: Response | None = None,
dependency_overrides_provider: Optional[Any] = None, dependency_overrides_provider: Any | None = None,
dependency_cache: Optional[dict[DependencyCacheKey, Any]] = None, dependency_cache: dict[DependencyCacheKey, Any] | None = None,
# TODO: remove this parameter later, no longer used, not removing it yet as some # TODO: remove this parameter later, no longer used, not removing it yet as some
# people might be monkey patching this function (although that's not supported) # people might be monkey patching this function (although that's not supported)
async_exit_stack: AsyncExitStack, async_exit_stack: AsyncExitStack,
@ -722,7 +718,7 @@ def _is_json_field(field: ModelField) -> bool:
def _get_multidict_value( def _get_multidict_value(
field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None field: ModelField, values: Mapping[str, Any], alias: str | None = None
) -> Any: ) -> Any:
alias = alias or get_validation_alias(field) alias = alias or get_validation_alias(field)
if ( if (
@ -754,7 +750,7 @@ def _get_multidict_value(
def request_params_to_args( def request_params_to_args(
fields: Sequence[ModelField], fields: Sequence[ModelField],
received_params: Union[Mapping[str, Any], QueryParams, Headers], received_params: Mapping[str, Any] | QueryParams | Headers,
) -> tuple[dict[str, Any], list[Any]]: ) -> tuple[dict[str, Any], list[Any]]:
values: dict[str, Any] = {} values: dict[str, Any] = {}
errors: list[dict[str, Any]] = [] errors: list[dict[str, Any]] = []
@ -902,17 +898,9 @@ async def _extract_form_body(
): ):
# For types # For types
assert isinstance(value, sequence_types) assert isinstance(value, sequence_types)
results: list[Union[bytes, str]] = [] results: list[bytes | str] = []
for sub_value in value:
async def process_fn( results.append(await sub_value.read())
fn: Callable[[], Coroutine[Any, Any, Any]],
) -> None:
result = await fn()
results.append(result) # noqa: B023
async with anyio.create_task_group() as tg:
for sub_value in value:
tg.start_soon(process_fn, sub_value.read)
value = serialize_sequence_value(field=field, value=results) value = serialize_sequence_value(field=field, value=results)
if value is not None: if value is not None:
values[get_validation_alias(field)] = value values[get_validation_alias(field)] = value
@ -929,7 +917,7 @@ async def _extract_form_body(
async def request_body_to_args( async def request_body_to_args(
body_fields: list[ModelField], body_fields: list[ModelField],
received_body: Optional[Union[dict[str, Any], FormData]], received_body: dict[str, Any] | FormData | None,
embed_body_fields: bool, embed_body_fields: bool,
) -> tuple[dict[str, Any], list[dict[str, Any]]]: ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
values: dict[str, Any] = {} values: dict[str, Any] = {}
@ -959,7 +947,7 @@ async def request_body_to_args(
return {first_field.name: v_}, errors_ return {first_field.name: v_}, errors_
for field in body_fields: for field in body_fields:
loc = ("body", get_validation_alias(field)) loc = ("body", get_validation_alias(field))
value: Optional[Any] = None value: Any | None = None
if body_to_process is not None: if body_to_process is not None:
try: try:
value = body_to_process.get(get_validation_alias(field)) value = body_to_process.get(get_validation_alias(field))
@ -979,7 +967,7 @@ async def request_body_to_args(
def get_body_field( def get_body_field(
*, flat_dependant: Dependant, name: str, embed_body_fields: bool *, flat_dependant: Dependant, name: str, embed_body_fields: bool
) -> Optional[ModelField]: ) -> ModelField | None:
""" """
Get a ModelField representing the request body for a path operation, combining Get a ModelField representing the request body for a path operation, combining
all body parameters into a single field if necessary. all body parameters into a single field if necessary.

13
fastapi/encoders.py

@ -1,6 +1,7 @@
import dataclasses import dataclasses
import datetime import datetime
from collections import defaultdict, deque from collections import defaultdict, deque
from collections.abc import Callable
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from ipaddress import ( from ipaddress import (
@ -14,7 +15,7 @@ from ipaddress import (
from pathlib import Path, PurePath from pathlib import Path, PurePath
from re import Pattern from re import Pattern
from types import GeneratorType from types import GeneratorType
from typing import Annotated, Any, Callable, Optional, Union from typing import Annotated, Any
from uuid import UUID from uuid import UUID
from annotated_doc import Doc from annotated_doc import Doc
@ -33,13 +34,13 @@ from ._compat import (
# Taken from Pydantic v1 as is # Taken from Pydantic v1 as is
def isoformat(o: Union[datetime.date, datetime.time]) -> str: def isoformat(o: datetime.date | datetime.time) -> str:
return o.isoformat() return o.isoformat()
# Adapted from Pydantic v1 # Adapted from Pydantic v1
# TODO: pv2 should this return strings instead? # TODO: pv2 should this return strings instead?
def decimal_encoder(dec_value: Decimal) -> Union[int, float]: def decimal_encoder(dec_value: Decimal) -> int | float:
""" """
Encodes a Decimal as int if there's no exponent, otherwise float Encodes a Decimal as int if there's no exponent, otherwise float
@ -118,7 +119,7 @@ def jsonable_encoder(
), ),
], ],
include: Annotated[ include: Annotated[
Optional[IncEx], IncEx | None,
Doc( Doc(
""" """
Pydantic's `include` parameter, passed to Pydantic models to set the Pydantic's `include` parameter, passed to Pydantic models to set the
@ -127,7 +128,7 @@ def jsonable_encoder(
), ),
] = None, ] = None,
exclude: Annotated[ exclude: Annotated[
Optional[IncEx], IncEx | None,
Doc( Doc(
""" """
Pydantic's `exclude` parameter, passed to Pydantic models to set the Pydantic's `exclude` parameter, passed to Pydantic models to set the
@ -177,7 +178,7 @@ def jsonable_encoder(
), ),
] = False, ] = False,
custom_encoder: Annotated[ custom_encoder: Annotated[
Optional[dict[Any, Callable[[Any], Any]]], dict[Any, Callable[[Any], Any]] | None,
Doc( Doc(
""" """
Pydantic's `custom_encoder` parameter, passed to Pydantic models to define Pydantic's `custom_encoder` parameter, passed to Pydantic models to define

16
fastapi/exceptions.py

@ -1,5 +1,5 @@
from collections.abc import Sequence from collections.abc import Mapping, Sequence
from typing import Annotated, Any, Optional, TypedDict, Union from typing import Annotated, Any, TypedDict
from annotated_doc import Doc from annotated_doc import Doc
from pydantic import BaseModel, create_model from pydantic import BaseModel, create_model
@ -68,7 +68,7 @@ class HTTPException(StarletteHTTPException):
), ),
] = None, ] = None,
headers: Annotated[ headers: Annotated[
Optional[dict[str, str]], Mapping[str, str] | None,
Doc( Doc(
""" """
Any headers to send to the client in the response. Any headers to send to the client in the response.
@ -137,7 +137,7 @@ class WebSocketException(StarletteWebSocketException):
), ),
], ],
reason: Annotated[ reason: Annotated[
Union[str, None], str | None,
Doc( Doc(
""" """
The reason to close the WebSocket connection. The reason to close the WebSocket connection.
@ -176,7 +176,7 @@ class ValidationException(Exception):
self, self,
errors: Sequence[Any], errors: Sequence[Any],
*, *,
endpoint_ctx: Optional[EndpointContext] = None, endpoint_ctx: EndpointContext | None = None,
) -> None: ) -> None:
self._errors = errors self._errors = errors
self.endpoint_ctx = endpoint_ctx self.endpoint_ctx = endpoint_ctx
@ -215,7 +215,7 @@ class RequestValidationError(ValidationException):
errors: Sequence[Any], errors: Sequence[Any],
*, *,
body: Any = None, body: Any = None,
endpoint_ctx: Optional[EndpointContext] = None, endpoint_ctx: EndpointContext | None = None,
) -> None: ) -> None:
super().__init__(errors, endpoint_ctx=endpoint_ctx) super().__init__(errors, endpoint_ctx=endpoint_ctx)
self.body = body self.body = body
@ -226,7 +226,7 @@ class WebSocketRequestValidationError(ValidationException):
self, self,
errors: Sequence[Any], errors: Sequence[Any],
*, *,
endpoint_ctx: Optional[EndpointContext] = None, endpoint_ctx: EndpointContext | None = None,
) -> None: ) -> None:
super().__init__(errors, endpoint_ctx=endpoint_ctx) super().__init__(errors, endpoint_ctx=endpoint_ctx)
@ -237,7 +237,7 @@ class ResponseValidationError(ValidationException):
errors: Sequence[Any], errors: Sequence[Any],
*, *,
body: Any = None, body: Any = None,
endpoint_ctx: Optional[EndpointContext] = None, endpoint_ctx: EndpointContext | None = None,
) -> None: ) -> None:
super().__init__(errors, endpoint_ctx=endpoint_ctx) super().__init__(errors, endpoint_ctx=endpoint_ctx)
self.body = body self.body = body

8
fastapi/openapi/docs.py

@ -1,5 +1,5 @@
import json import json
from typing import Annotated, Any, Optional from typing import Annotated, Any
from annotated_doc import Doc from annotated_doc import Doc
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
@ -85,7 +85,7 @@ def get_swagger_ui_html(
), ),
] = "https://fastapi.tiangolo.com/img/favicon.png", ] = "https://fastapi.tiangolo.com/img/favicon.png",
oauth2_redirect_url: Annotated[ oauth2_redirect_url: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
The OAuth2 redirect URL, it is normally automatically handled by FastAPI. The OAuth2 redirect URL, it is normally automatically handled by FastAPI.
@ -96,7 +96,7 @@ def get_swagger_ui_html(
), ),
] = None, ] = None,
init_oauth: Annotated[ init_oauth: Annotated[
Optional[dict[str, Any]], dict[str, Any] | None,
Doc( Doc(
""" """
A dictionary with Swagger UI OAuth2 initialization configurations. A dictionary with Swagger UI OAuth2 initialization configurations.
@ -107,7 +107,7 @@ def get_swagger_ui_html(
), ),
] = None, ] = None,
swagger_ui_parameters: Annotated[ swagger_ui_parameters: Annotated[
Optional[dict[str, Any]], dict[str, Any] | None,
Doc( Doc(
""" """
Configuration parameters for Swagger UI. Configuration parameters for Swagger UI.

326
fastapi/openapi/models.py

@ -1,6 +1,6 @@
from collections.abc import Iterable, Mapping from collections.abc import Callable, Iterable, Mapping
from enum import Enum from enum import Enum
from typing import Annotated, Any, Callable, Optional, Union from typing import Annotated, Any, Literal, Optional, Union
from fastapi._compat import with_info_plain_validator_function from fastapi._compat import with_info_plain_validator_function
from fastapi.logger import logger from fastapi.logger import logger
@ -10,7 +10,7 @@ from pydantic import (
Field, Field,
GetJsonSchemaHandler, GetJsonSchemaHandler,
) )
from typing_extensions import Literal, TypedDict from typing_extensions import TypedDict
from typing_extensions import deprecated as typing_deprecated from typing_extensions import deprecated as typing_deprecated
try: try:
@ -59,37 +59,37 @@ class BaseModelWithConfig(BaseModel):
class Contact(BaseModelWithConfig): class Contact(BaseModelWithConfig):
name: Optional[str] = None name: str | None = None
url: Optional[AnyUrl] = None url: AnyUrl | None = None
email: Optional[EmailStr] = None email: EmailStr | None = None
class License(BaseModelWithConfig): class License(BaseModelWithConfig):
name: str name: str
identifier: Optional[str] = None identifier: str | None = None
url: Optional[AnyUrl] = None url: AnyUrl | None = None
class Info(BaseModelWithConfig): class Info(BaseModelWithConfig):
title: str title: str
summary: Optional[str] = None summary: str | None = None
description: Optional[str] = None description: str | None = None
termsOfService: Optional[str] = None termsOfService: str | None = None
contact: Optional[Contact] = None contact: Contact | None = None
license: Optional[License] = None license: License | None = None
version: str version: str
class ServerVariable(BaseModelWithConfig): class ServerVariable(BaseModelWithConfig):
enum: Annotated[Optional[list[str]], Field(min_length=1)] = None enum: Annotated[list[str] | None, Field(min_length=1)] = None
default: str default: str
description: Optional[str] = None description: str | None = None
class Server(BaseModelWithConfig): class Server(BaseModelWithConfig):
url: Union[AnyUrl, str] url: AnyUrl | str
description: Optional[str] = None description: str | None = None
variables: Optional[dict[str, ServerVariable]] = None variables: dict[str, ServerVariable] | None = None
class Reference(BaseModel): class Reference(BaseModel):
@ -98,19 +98,19 @@ class Reference(BaseModel):
class Discriminator(BaseModel): class Discriminator(BaseModel):
propertyName: str propertyName: str
mapping: Optional[dict[str, str]] = None mapping: dict[str, str] | None = None
class XML(BaseModelWithConfig): class XML(BaseModelWithConfig):
name: Optional[str] = None name: str | None = None
namespace: Optional[str] = None namespace: str | None = None
prefix: Optional[str] = None prefix: str | None = None
attribute: Optional[bool] = None attribute: bool | None = None
wrapped: Optional[bool] = None wrapped: bool | None = None
class ExternalDocumentation(BaseModelWithConfig): class ExternalDocumentation(BaseModelWithConfig):
description: Optional[str] = None description: str | None = None
url: AnyUrl url: AnyUrl
@ -123,80 +123,80 @@ SchemaType = Literal[
class Schema(BaseModelWithConfig): class Schema(BaseModelWithConfig):
# Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu
# Core Vocabulary # Core Vocabulary
schema_: Optional[str] = Field(default=None, alias="$schema") schema_: str | None = Field(default=None, alias="$schema")
vocabulary: Optional[str] = Field(default=None, alias="$vocabulary") vocabulary: str | None = Field(default=None, alias="$vocabulary")
id: Optional[str] = Field(default=None, alias="$id") id: str | None = Field(default=None, alias="$id")
anchor: Optional[str] = Field(default=None, alias="$anchor") anchor: str | None = Field(default=None, alias="$anchor")
dynamicAnchor: Optional[str] = Field(default=None, alias="$dynamicAnchor") dynamicAnchor: str | None = Field(default=None, alias="$dynamicAnchor")
ref: Optional[str] = Field(default=None, alias="$ref") ref: str | None = Field(default=None, alias="$ref")
dynamicRef: Optional[str] = Field(default=None, alias="$dynamicRef") dynamicRef: str | None = Field(default=None, alias="$dynamicRef")
defs: Optional[dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs") defs: dict[str, "SchemaOrBool"] | None = Field(default=None, alias="$defs")
comment: Optional[str] = Field(default=None, alias="$comment") comment: str | None = Field(default=None, alias="$comment")
# Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s
# A Vocabulary for Applying Subschemas # A Vocabulary for Applying Subschemas
allOf: Optional[list["SchemaOrBool"]] = None allOf: list["SchemaOrBool"] | None = None
anyOf: Optional[list["SchemaOrBool"]] = None anyOf: list["SchemaOrBool"] | None = None
oneOf: Optional[list["SchemaOrBool"]] = None oneOf: list["SchemaOrBool"] | None = None
not_: Optional["SchemaOrBool"] = Field(default=None, alias="not") not_: Optional["SchemaOrBool"] = Field(default=None, alias="not")
if_: Optional["SchemaOrBool"] = Field(default=None, alias="if") if_: Optional["SchemaOrBool"] = Field(default=None, alias="if")
then: Optional["SchemaOrBool"] = None then: Optional["SchemaOrBool"] = None
else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") else_: Optional["SchemaOrBool"] = Field(default=None, alias="else")
dependentSchemas: Optional[dict[str, "SchemaOrBool"]] = None dependentSchemas: dict[str, "SchemaOrBool"] | None = None
prefixItems: Optional[list["SchemaOrBool"]] = None prefixItems: list["SchemaOrBool"] | None = None
items: Optional["SchemaOrBool"] = None items: Optional["SchemaOrBool"] = None
contains: Optional["SchemaOrBool"] = None contains: Optional["SchemaOrBool"] = None
properties: Optional[dict[str, "SchemaOrBool"]] = None properties: dict[str, "SchemaOrBool"] | None = None
patternProperties: Optional[dict[str, "SchemaOrBool"]] = None patternProperties: dict[str, "SchemaOrBool"] | None = None
additionalProperties: Optional["SchemaOrBool"] = None additionalProperties: Optional["SchemaOrBool"] = None
propertyNames: Optional["SchemaOrBool"] = None propertyNames: Optional["SchemaOrBool"] = None
unevaluatedItems: Optional["SchemaOrBool"] = None unevaluatedItems: Optional["SchemaOrBool"] = None
unevaluatedProperties: Optional["SchemaOrBool"] = None unevaluatedProperties: Optional["SchemaOrBool"] = None
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural
# A Vocabulary for Structural Validation # A Vocabulary for Structural Validation
type: Optional[Union[SchemaType, list[SchemaType]]] = None type: SchemaType | list[SchemaType] | None = None
enum: Optional[list[Any]] = None enum: list[Any] | None = None
const: Optional[Any] = None const: Any | None = None
multipleOf: Optional[float] = Field(default=None, gt=0) multipleOf: float | None = Field(default=None, gt=0)
maximum: Optional[float] = None maximum: float | None = None
exclusiveMaximum: Optional[float] = None exclusiveMaximum: float | None = None
minimum: Optional[float] = None minimum: float | None = None
exclusiveMinimum: Optional[float] = None exclusiveMinimum: float | None = None
maxLength: Optional[int] = Field(default=None, ge=0) maxLength: int | None = Field(default=None, ge=0)
minLength: Optional[int] = Field(default=None, ge=0) minLength: int | None = Field(default=None, ge=0)
pattern: Optional[str] = None pattern: str | None = None
maxItems: Optional[int] = Field(default=None, ge=0) maxItems: int | None = Field(default=None, ge=0)
minItems: Optional[int] = Field(default=None, ge=0) minItems: int | None = Field(default=None, ge=0)
uniqueItems: Optional[bool] = None uniqueItems: bool | None = None
maxContains: Optional[int] = Field(default=None, ge=0) maxContains: int | None = Field(default=None, ge=0)
minContains: Optional[int] = Field(default=None, ge=0) minContains: int | None = Field(default=None, ge=0)
maxProperties: Optional[int] = Field(default=None, ge=0) maxProperties: int | None = Field(default=None, ge=0)
minProperties: Optional[int] = Field(default=None, ge=0) minProperties: int | None = Field(default=None, ge=0)
required: Optional[list[str]] = None required: list[str] | None = None
dependentRequired: Optional[dict[str, set[str]]] = None dependentRequired: dict[str, set[str]] | None = None
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c
# Vocabularies for Semantic Content With "format" # Vocabularies for Semantic Content With "format"
format: Optional[str] = None format: str | None = None
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten
# A Vocabulary for the Contents of String-Encoded Data # A Vocabulary for the Contents of String-Encoded Data
contentEncoding: Optional[str] = None contentEncoding: str | None = None
contentMediaType: Optional[str] = None contentMediaType: str | None = None
contentSchema: Optional["SchemaOrBool"] = None contentSchema: Optional["SchemaOrBool"] = None
# Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
# A Vocabulary for Basic Meta-Data Annotations # A Vocabulary for Basic Meta-Data Annotations
title: Optional[str] = None title: str | None = None
description: Optional[str] = None description: str | None = None
default: Optional[Any] = None default: Any | None = None
deprecated: Optional[bool] = None deprecated: bool | None = None
readOnly: Optional[bool] = None readOnly: bool | None = None
writeOnly: Optional[bool] = None writeOnly: bool | None = None
examples: Optional[list[Any]] = None examples: list[Any] | None = None
# Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object
# Schema Object # Schema Object
discriminator: Optional[Discriminator] = None discriminator: Discriminator | None = None
xml: Optional[XML] = None xml: XML | None = None
externalDocs: Optional[ExternalDocumentation] = None externalDocs: ExternalDocumentation | None = None
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
typing_deprecated( typing_deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
@ -206,14 +206,14 @@ class Schema(BaseModelWithConfig):
# Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents # Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents
# A JSON Schema MUST be an object or a boolean. # A JSON Schema MUST be an object or a boolean.
SchemaOrBool = Union[Schema, bool] SchemaOrBool = Schema | bool
class Example(TypedDict, total=False): class Example(TypedDict, total=False):
summary: Optional[str] summary: str | None
description: Optional[str] description: str | None
value: Optional[Any] value: Any | None
externalValue: Optional[AnyUrl] externalValue: AnyUrl | None
__pydantic_config__ = {"extra": "allow"} # type: ignore[misc] __pydantic_config__ = {"extra": "allow"} # type: ignore[misc]
@ -226,33 +226,33 @@ class ParameterInType(Enum):
class Encoding(BaseModelWithConfig): class Encoding(BaseModelWithConfig):
contentType: Optional[str] = None contentType: str | None = None
headers: Optional[dict[str, Union["Header", Reference]]] = None headers: dict[str, Union["Header", Reference]] | None = None
style: Optional[str] = None style: str | None = None
explode: Optional[bool] = None explode: bool | None = None
allowReserved: Optional[bool] = None allowReserved: bool | None = None
class MediaType(BaseModelWithConfig): class MediaType(BaseModelWithConfig):
schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") schema_: Schema | Reference | None = Field(default=None, alias="schema")
example: Optional[Any] = None example: Any | None = None
examples: Optional[dict[str, Union[Example, Reference]]] = None examples: dict[str, Example | Reference] | None = None
encoding: Optional[dict[str, Encoding]] = None encoding: dict[str, Encoding] | None = None
class ParameterBase(BaseModelWithConfig): class ParameterBase(BaseModelWithConfig):
description: Optional[str] = None description: str | None = None
required: Optional[bool] = None required: bool | None = None
deprecated: Optional[bool] = None deprecated: bool | None = None
# Serialization rules for simple scenarios # Serialization rules for simple scenarios
style: Optional[str] = None style: str | None = None
explode: Optional[bool] = None explode: bool | None = None
allowReserved: Optional[bool] = None allowReserved: bool | None = None
schema_: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema") schema_: Schema | Reference | None = Field(default=None, alias="schema")
example: Optional[Any] = None example: Any | None = None
examples: Optional[dict[str, Union[Example, Reference]]] = None examples: dict[str, Example | Reference] | None = None
# Serialization rules for more complex scenarios # Serialization rules for more complex scenarios
content: Optional[dict[str, MediaType]] = None content: dict[str, MediaType] | None = None
class Parameter(ParameterBase): class Parameter(ParameterBase):
@ -265,57 +265,57 @@ class Header(ParameterBase):
class RequestBody(BaseModelWithConfig): class RequestBody(BaseModelWithConfig):
description: Optional[str] = None description: str | None = None
content: dict[str, MediaType] content: dict[str, MediaType]
required: Optional[bool] = None required: bool | None = None
class Link(BaseModelWithConfig): class Link(BaseModelWithConfig):
operationRef: Optional[str] = None operationRef: str | None = None
operationId: Optional[str] = None operationId: str | None = None
parameters: Optional[dict[str, Union[Any, str]]] = None parameters: dict[str, Any | str] | None = None
requestBody: Optional[Union[Any, str]] = None requestBody: Any | str | None = None
description: Optional[str] = None description: str | None = None
server: Optional[Server] = None server: Server | None = None
class Response(BaseModelWithConfig): class Response(BaseModelWithConfig):
description: str description: str
headers: Optional[dict[str, Union[Header, Reference]]] = None headers: dict[str, Header | Reference] | None = None
content: Optional[dict[str, MediaType]] = None content: dict[str, MediaType] | None = None
links: Optional[dict[str, Union[Link, Reference]]] = None links: dict[str, Link | Reference] | None = None
class Operation(BaseModelWithConfig): class Operation(BaseModelWithConfig):
tags: Optional[list[str]] = None tags: list[str] | None = None
summary: Optional[str] = None summary: str | None = None
description: Optional[str] = None description: str | None = None
externalDocs: Optional[ExternalDocumentation] = None externalDocs: ExternalDocumentation | None = None
operationId: Optional[str] = None operationId: str | None = None
parameters: Optional[list[Union[Parameter, Reference]]] = None parameters: list[Parameter | Reference] | None = None
requestBody: Optional[Union[RequestBody, Reference]] = None requestBody: RequestBody | Reference | None = None
# Using Any for Specification Extensions # Using Any for Specification Extensions
responses: Optional[dict[str, Union[Response, Any]]] = None responses: dict[str, Response | Any] | None = None
callbacks: Optional[dict[str, Union[dict[str, "PathItem"], Reference]]] = None callbacks: dict[str, dict[str, "PathItem"] | Reference] | None = None
deprecated: Optional[bool] = None deprecated: bool | None = None
security: Optional[list[dict[str, list[str]]]] = None security: list[dict[str, list[str]]] | None = None
servers: Optional[list[Server]] = None servers: list[Server] | None = None
class PathItem(BaseModelWithConfig): class PathItem(BaseModelWithConfig):
ref: Optional[str] = Field(default=None, alias="$ref") ref: str | None = Field(default=None, alias="$ref")
summary: Optional[str] = None summary: str | None = None
description: Optional[str] = None description: str | None = None
get: Optional[Operation] = None get: Operation | None = None
put: Optional[Operation] = None put: Operation | None = None
post: Optional[Operation] = None post: Operation | None = None
delete: Optional[Operation] = None delete: Operation | None = None
options: Optional[Operation] = None options: Operation | None = None
head: Optional[Operation] = None head: Operation | None = None
patch: Optional[Operation] = None patch: Operation | None = None
trace: Optional[Operation] = None trace: Operation | None = None
servers: Optional[list[Server]] = None servers: list[Server] | None = None
parameters: Optional[list[Union[Parameter, Reference]]] = None parameters: list[Parameter | Reference] | None = None
class SecuritySchemeType(Enum): class SecuritySchemeType(Enum):
@ -327,7 +327,7 @@ class SecuritySchemeType(Enum):
class SecurityBase(BaseModelWithConfig): class SecurityBase(BaseModelWithConfig):
type_: SecuritySchemeType = Field(alias="type") type_: SecuritySchemeType = Field(alias="type")
description: Optional[str] = None description: str | None = None
class APIKeyIn(Enum): class APIKeyIn(Enum):
@ -349,11 +349,11 @@ class HTTPBase(SecurityBase):
class HTTPBearer(HTTPBase): class HTTPBearer(HTTPBase):
scheme: Literal["bearer"] = "bearer" scheme: Literal["bearer"] = "bearer"
bearerFormat: Optional[str] = None bearerFormat: str | None = None
class OAuthFlow(BaseModelWithConfig): class OAuthFlow(BaseModelWithConfig):
refreshUrl: Optional[str] = None refreshUrl: str | None = None
scopes: dict[str, str] = {} scopes: dict[str, str] = {}
@ -375,10 +375,10 @@ class OAuthFlowAuthorizationCode(OAuthFlow):
class OAuthFlows(BaseModelWithConfig): class OAuthFlows(BaseModelWithConfig):
implicit: Optional[OAuthFlowImplicit] = None implicit: OAuthFlowImplicit | None = None
password: Optional[OAuthFlowPassword] = None password: OAuthFlowPassword | None = None
clientCredentials: Optional[OAuthFlowClientCredentials] = None clientCredentials: OAuthFlowClientCredentials | None = None
authorizationCode: Optional[OAuthFlowAuthorizationCode] = None authorizationCode: OAuthFlowAuthorizationCode | None = None
class OAuth2(SecurityBase): class OAuth2(SecurityBase):
@ -393,41 +393,41 @@ class OpenIdConnect(SecurityBase):
openIdConnectUrl: str openIdConnectUrl: str
SecurityScheme = Union[APIKey, HTTPBase, OAuth2, OpenIdConnect, HTTPBearer] SecurityScheme = APIKey | HTTPBase | OAuth2 | OpenIdConnect | HTTPBearer
class Components(BaseModelWithConfig): class Components(BaseModelWithConfig):
schemas: Optional[dict[str, Union[Schema, Reference]]] = None schemas: dict[str, Schema | Reference] | None = None
responses: Optional[dict[str, Union[Response, Reference]]] = None responses: dict[str, Response | Reference] | None = None
parameters: Optional[dict[str, Union[Parameter, Reference]]] = None parameters: dict[str, Parameter | Reference] | None = None
examples: Optional[dict[str, Union[Example, Reference]]] = None examples: dict[str, Example | Reference] | None = None
requestBodies: Optional[dict[str, Union[RequestBody, Reference]]] = None requestBodies: dict[str, RequestBody | Reference] | None = None
headers: Optional[dict[str, Union[Header, Reference]]] = None headers: dict[str, Header | Reference] | None = None
securitySchemes: Optional[dict[str, Union[SecurityScheme, Reference]]] = None securitySchemes: dict[str, SecurityScheme | Reference] | None = None
links: Optional[dict[str, Union[Link, Reference]]] = None links: dict[str, Link | Reference] | None = None
# Using Any for Specification Extensions # Using Any for Specification Extensions
callbacks: Optional[dict[str, Union[dict[str, PathItem], Reference, Any]]] = None callbacks: dict[str, dict[str, PathItem] | Reference | Any] | None = None
pathItems: Optional[dict[str, Union[PathItem, Reference]]] = None pathItems: dict[str, PathItem | Reference] | None = None
class Tag(BaseModelWithConfig): class Tag(BaseModelWithConfig):
name: str name: str
description: Optional[str] = None description: str | None = None
externalDocs: Optional[ExternalDocumentation] = None externalDocs: ExternalDocumentation | None = None
class OpenAPI(BaseModelWithConfig): class OpenAPI(BaseModelWithConfig):
openapi: str openapi: str
info: Info info: Info
jsonSchemaDialect: Optional[str] = None jsonSchemaDialect: str | None = None
servers: Optional[list[Server]] = None servers: list[Server] | None = None
# Using Any for Specification Extensions # Using Any for Specification Extensions
paths: Optional[dict[str, Union[PathItem, Any]]] = None paths: dict[str, PathItem | Any] | None = None
webhooks: Optional[dict[str, Union[PathItem, Reference]]] = None webhooks: dict[str, PathItem | Reference] | None = None
components: Optional[Components] = None components: Components | None = None
security: Optional[list[dict[str, list[str]]]] = None security: list[dict[str, list[str]]] | None = None
tags: Optional[list[Tag]] = None tags: list[Tag] | None = None
externalDocs: Optional[ExternalDocumentation] = None externalDocs: ExternalDocumentation | None = None
Schema.model_rebuild() Schema.model_rebuild()

31
fastapi/openapi/utils.py

@ -3,7 +3,7 @@ import http.client
import inspect import inspect
import warnings import warnings
from collections.abc import Sequence from collections.abc import Sequence
from typing import Any, Optional, Union, cast from typing import Any, Literal, cast
from fastapi import routing from fastapi import routing
from fastapi._compat import ( from fastapi._compat import (
@ -38,7 +38,6 @@ from fastapi.utils import (
from pydantic import BaseModel from pydantic import BaseModel
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from starlette.routing import BaseRoute from starlette.routing import BaseRoute
from typing_extensions import Literal
validation_error_definition = { validation_error_definition = {
"title": "ValidationError", "title": "ValidationError",
@ -180,13 +179,13 @@ def _get_openapi_operation_parameters(
def get_openapi_operation_request_body( def get_openapi_operation_request_body(
*, *,
body_field: Optional[ModelField], body_field: ModelField | None,
model_name_map: ModelNameMap, model_name_map: ModelNameMap,
field_mapping: dict[ field_mapping: dict[
tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
], ],
separate_input_output_schemas: bool = True, separate_input_output_schemas: bool = True,
) -> Optional[dict[str, Any]]: ) -> dict[str, Any] | None:
if not body_field: if not body_field:
return None return None
assert isinstance(body_field, ModelField) assert isinstance(body_field, ModelField)
@ -279,7 +278,7 @@ def get_openapi_path(
else: else:
current_response_class = route.response_class current_response_class = route.response_class
assert current_response_class, "A response class is needed to generate OpenAPI" assert current_response_class, "A response class is needed to generate OpenAPI"
route_response_media_type: Optional[str] = current_response_class.media_type route_response_media_type: str | None = current_response_class.media_type
if route.include_in_schema: if route.include_in_schema:
for method in route.methods: for method in route.methods:
operation = get_openapi_operation_metadata( operation = get_openapi_operation_metadata(
@ -393,7 +392,7 @@ def get_openapi_path(
"An additional response must be a dict" "An additional response must be a dict"
) )
field = route.response_fields.get(additional_status_code) field = route.response_fields.get(additional_status_code)
additional_field_schema: Optional[dict[str, Any]] = None additional_field_schema: dict[str, Any] | None = None
if field: if field:
additional_field_schema = get_schema_from_model_field( additional_field_schema = get_schema_from_model_field(
field=field, field=field,
@ -408,7 +407,7 @@ def get_openapi_path(
.setdefault("schema", {}) .setdefault("schema", {})
) )
deep_dict_update(additional_schema, additional_field_schema) deep_dict_update(additional_schema, additional_field_schema)
status_text: Optional[str] = status_code_ranges.get( status_text: str | None = status_code_ranges.get(
str(additional_status_code).upper() str(additional_status_code).upper()
) or http.client.responses.get(int(additional_status_code)) ) or http.client.responses.get(int(additional_status_code))
description = ( description = (
@ -482,17 +481,17 @@ def get_openapi(
title: str, title: str,
version: str, version: str,
openapi_version: str = "3.1.0", openapi_version: str = "3.1.0",
summary: Optional[str] = None, summary: str | None = None,
description: Optional[str] = None, description: str | None = None,
routes: Sequence[BaseRoute], routes: Sequence[BaseRoute],
webhooks: Optional[Sequence[BaseRoute]] = None, webhooks: Sequence[BaseRoute] | None = None,
tags: Optional[list[dict[str, Any]]] = None, tags: list[dict[str, Any]] | None = None,
servers: Optional[list[dict[str, Union[str, Any]]]] = None, servers: list[dict[str, str | Any]] | None = None,
terms_of_service: Optional[str] = None, terms_of_service: str | None = None,
contact: Optional[dict[str, Union[str, Any]]] = None, contact: dict[str, str | Any] | None = None,
license_info: Optional[dict[str, Union[str, Any]]] = None, license_info: dict[str, str | Any] | None = None,
separate_input_output_schemas: bool = True, separate_input_output_schemas: bool = True,
external_docs: Optional[dict[str, Any]] = None, external_docs: dict[str, Any] | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
info: dict[str, Any] = {"title": title, "version": version} info: dict[str, Any] = {"title": title, "version": version}
if summary: if summary:

380
fastapi/param_functions.py

File diff suppressed because it is too large

446
fastapi/params.py

@ -1,14 +1,14 @@
import warnings import warnings
from collections.abc import Sequence from collections.abc import Callable, Sequence
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Annotated, Any, Callable, Optional, Union from typing import Annotated, Any, Literal
from fastapi.exceptions import FastAPIDeprecationWarning from fastapi.exceptions import FastAPIDeprecationWarning
from fastapi.openapi.models import Example from fastapi.openapi.models import Example
from pydantic import AliasChoices, AliasPath from pydantic import AliasChoices, AliasPath
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from typing_extensions import Literal, deprecated from typing_extensions import deprecated
from ._compat import ( from ._compat import (
Undefined, Undefined,
@ -31,45 +31,45 @@ class Param(FieldInfo): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
if example is not _Unset: if example is not _Unset:
@ -142,45 +142,45 @@ class Path(Param): # type: ignore[misc]
self, self,
default: Any = ..., default: Any = ...,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
assert default is ..., "Path parameters cannot have a default value" assert default is ..., "Path parameters cannot have a default value"
@ -226,45 +226,45 @@ class Query(Param): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
super().__init__( super().__init__(
@ -308,46 +308,46 @@ class Header(Param): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
convert_underscores: bool = True, convert_underscores: bool = True,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
self.convert_underscores = convert_underscores self.convert_underscores = convert_underscores
@ -392,45 +392,45 @@ class Cookie(Param): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
super().__init__( super().__init__(
@ -472,47 +472,47 @@ class Body(FieldInfo): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
embed: Union[bool, None] = None, embed: bool | None = None,
media_type: str = "application/json", media_type: str = "application/json",
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
self.embed = embed self.embed = embed
@ -584,46 +584,46 @@ class Form(Body): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
media_type: str = "application/x-www-form-urlencoded", media_type: str = "application/x-www-form-urlencoded",
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
super().__init__( super().__init__(
@ -666,46 +666,46 @@ class File(Form): # type: ignore[misc]
self, self,
default: Any = Undefined, default: Any = Undefined,
*, *,
default_factory: Union[Callable[[], Any], None] = _Unset, default_factory: Callable[[], Any] | None = _Unset,
annotation: Optional[Any] = None, annotation: Any | None = None,
media_type: str = "multipart/form-data", media_type: str = "multipart/form-data",
alias: Optional[str] = None, alias: str | None = None,
alias_priority: Union[int, None] = _Unset, alias_priority: int | None = _Unset,
validation_alias: Union[str, AliasPath, AliasChoices, None] = None, validation_alias: str | AliasPath | AliasChoices | None = None,
serialization_alias: Union[str, None] = None, serialization_alias: str | None = None,
title: Optional[str] = None, title: str | None = None,
description: Optional[str] = None, description: str | None = None,
gt: Optional[float] = None, gt: float | None = None,
ge: Optional[float] = None, ge: float | None = None,
lt: Optional[float] = None, lt: float | None = None,
le: Optional[float] = None, le: float | None = None,
min_length: Optional[int] = None, min_length: int | None = None,
max_length: Optional[int] = None, max_length: int | None = None,
pattern: Optional[str] = None, pattern: str | None = None,
regex: Annotated[ regex: Annotated[
Optional[str], str | None,
deprecated( deprecated(
"Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead."
), ),
] = None, ] = None,
discriminator: Union[str, None] = None, discriminator: str | None = None,
strict: Union[bool, None] = _Unset, strict: bool | None = _Unset,
multiple_of: Union[float, None] = _Unset, multiple_of: float | None = _Unset,
allow_inf_nan: Union[bool, None] = _Unset, allow_inf_nan: bool | None = _Unset,
max_digits: Union[int, None] = _Unset, max_digits: int | None = _Unset,
decimal_places: Union[int, None] = _Unset, decimal_places: int | None = _Unset,
examples: Optional[list[Any]] = None, examples: list[Any] | None = None,
example: Annotated[ example: Annotated[
Optional[Any], Any | None,
deprecated( deprecated(
"Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, "
"although still supported. Use examples instead." "although still supported. Use examples instead."
), ),
] = _Unset, ] = _Unset,
openapi_examples: Optional[dict[str, Example]] = None, openapi_examples: dict[str, Example] | None = None,
deprecated: Union[deprecated, str, bool, None] = None, deprecated: deprecated | str | bool | None = None,
include_in_schema: bool = True, include_in_schema: bool = True,
json_schema_extra: Union[dict[str, Any], None] = None, json_schema_extra: dict[str, Any] | None = None,
**extra: Any, **extra: Any,
): ):
super().__init__( super().__init__(
@ -745,11 +745,11 @@ class File(Form): # type: ignore[misc]
@dataclass(frozen=True) @dataclass(frozen=True)
class Depends: class Depends:
dependency: Optional[Callable[..., Any]] = None dependency: Callable[..., Any] | None = None
use_cache: bool = True use_cache: bool = True
scope: Union[Literal["function", "request"], None] = None scope: Literal["function", "request"] | None = None
@dataclass(frozen=True) @dataclass(frozen=True)
class Security(Depends): class Security(Depends):
scopes: Optional[Sequence[str]] = None scopes: Sequence[str] | None = None

437
fastapi/routing.py

File diff suppressed because it is too large

26
fastapi/security/api_key.py

@ -1,4 +1,4 @@
from typing import Annotated, Optional, Union from typing import Annotated
from annotated_doc import Doc from annotated_doc import Doc
from fastapi.openapi.models import APIKey, APIKeyIn from fastapi.openapi.models import APIKey, APIKeyIn
@ -13,8 +13,8 @@ class APIKeyBase(SecurityBase):
self, self,
location: APIKeyIn, location: APIKeyIn,
name: str, name: str,
description: Union[str, None], description: str | None,
scheme_name: Union[str, None], scheme_name: str | None,
auto_error: bool, auto_error: bool,
): ):
self.auto_error = auto_error self.auto_error = auto_error
@ -42,7 +42,7 @@ class APIKeyBase(SecurityBase):
headers={"WWW-Authenticate": "APIKey"}, headers={"WWW-Authenticate": "APIKey"},
) )
def check_api_key(self, api_key: Optional[str]) -> Optional[str]: def check_api_key(self, api_key: str | None) -> str | None:
if not api_key: if not api_key:
if self.auto_error: if self.auto_error:
raise self.make_not_authenticated_error() raise self.make_not_authenticated_error()
@ -90,7 +90,7 @@ class APIKeyQuery(APIKeyBase):
Doc("Query parameter name."), Doc("Query parameter name."),
], ],
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -100,7 +100,7 @@ class APIKeyQuery(APIKeyBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -137,7 +137,7 @@ class APIKeyQuery(APIKeyBase):
auto_error=auto_error, auto_error=auto_error,
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
api_key = request.query_params.get(self.model.name) api_key = request.query_params.get(self.model.name)
return self.check_api_key(api_key) return self.check_api_key(api_key)
@ -179,7 +179,7 @@ class APIKeyHeader(APIKeyBase):
*, *,
name: Annotated[str, Doc("Header name.")], name: Annotated[str, Doc("Header name.")],
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -189,7 +189,7 @@ class APIKeyHeader(APIKeyBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -225,7 +225,7 @@ class APIKeyHeader(APIKeyBase):
auto_error=auto_error, auto_error=auto_error,
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
api_key = request.headers.get(self.model.name) api_key = request.headers.get(self.model.name)
return self.check_api_key(api_key) return self.check_api_key(api_key)
@ -267,7 +267,7 @@ class APIKeyCookie(APIKeyBase):
*, *,
name: Annotated[str, Doc("Cookie name.")], name: Annotated[str, Doc("Cookie name.")],
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -277,7 +277,7 @@ class APIKeyCookie(APIKeyBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -313,6 +313,6 @@ class APIKeyCookie(APIKeyBase):
auto_error=auto_error, auto_error=auto_error,
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
api_key = request.cookies.get(self.model.name) api_key = request.cookies.get(self.model.name)
return self.check_api_key(api_key) return self.check_api_key(api_key)

36
fastapi/security/http.py

@ -1,6 +1,6 @@
import binascii import binascii
from base64 import b64decode from base64 import b64decode
from typing import Annotated, Optional from typing import Annotated
from annotated_doc import Doc from annotated_doc import Doc
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
@ -71,8 +71,8 @@ class HTTPBase(SecurityBase):
self, self,
*, *,
scheme: str, scheme: str,
scheme_name: Optional[str] = None, scheme_name: str | None = None,
description: Optional[str] = None, description: str | None = None,
auto_error: bool = True, auto_error: bool = True,
): ):
self.model: HTTPBaseModel = HTTPBaseModel( self.model: HTTPBaseModel = HTTPBaseModel(
@ -91,9 +91,7 @@ class HTTPBase(SecurityBase):
headers=self.make_authenticate_headers(), headers=self.make_authenticate_headers(),
) )
async def __call__( async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None:
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization) scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials): if not (authorization and scheme and credentials):
@ -143,7 +141,7 @@ class HTTPBasic(HTTPBase):
self, self,
*, *,
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -153,7 +151,7 @@ class HTTPBasic(HTTPBase):
), ),
] = None, ] = None,
realm: Annotated[ realm: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
HTTP Basic authentication realm. HTTP Basic authentication realm.
@ -161,7 +159,7 @@ class HTTPBasic(HTTPBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -203,7 +201,7 @@ class HTTPBasic(HTTPBase):
async def __call__( # type: ignore async def __call__( # type: ignore
self, request: Request self, request: Request
) -> Optional[HTTPBasicCredentials]: ) -> HTTPBasicCredentials | None:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization) scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "basic": if not authorization or scheme.lower() != "basic":
@ -256,9 +254,9 @@ class HTTPBearer(HTTPBase):
def __init__( def __init__(
self, self,
*, *,
bearerFormat: Annotated[Optional[str], Doc("Bearer token format.")] = None, bearerFormat: Annotated[str | None, Doc("Bearer token format.")] = None,
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -268,7 +266,7 @@ class HTTPBearer(HTTPBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -302,9 +300,7 @@ class HTTPBearer(HTTPBase):
self.scheme_name = scheme_name or self.__class__.__name__ self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error self.auto_error = auto_error
async def __call__( async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None:
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization) scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials): if not (authorization and scheme and credentials):
@ -362,7 +358,7 @@ class HTTPDigest(HTTPBase):
self, self,
*, *,
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -372,7 +368,7 @@ class HTTPDigest(HTTPBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -405,9 +401,7 @@ class HTTPDigest(HTTPBase):
self.scheme_name = scheme_name or self.__class__.__name__ self.scheme_name = scheme_name or self.__class__.__name__
self.auto_error = auto_error self.auto_error = auto_error
async def __call__( async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None:
self, request: Request
) -> Optional[HTTPAuthorizationCredentials]:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
scheme, credentials = get_authorization_scheme_param(authorization) scheme, credentials = get_authorization_scheme_param(authorization)
if not (authorization and scheme and credentials): if not (authorization and scheme and credentials):

42
fastapi/security/oauth2.py

@ -1,4 +1,4 @@
from typing import Annotated, Any, Optional, Union, cast from typing import Annotated, Any, cast
from annotated_doc import Doc from annotated_doc import Doc
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
@ -60,7 +60,7 @@ class OAuth2PasswordRequestForm:
self, self,
*, *,
grant_type: Annotated[ grant_type: Annotated[
Union[str, None], str | None,
Form(pattern="^password$"), Form(pattern="^password$"),
Doc( Doc(
""" """
@ -128,7 +128,7 @@ class OAuth2PasswordRequestForm:
), ),
] = "", ] = "",
client_id: Annotated[ client_id: Annotated[
Union[str, None], str | None,
Form(), Form(),
Doc( Doc(
""" """
@ -139,7 +139,7 @@ class OAuth2PasswordRequestForm:
), ),
] = None, ] = None,
client_secret: Annotated[ client_secret: Annotated[
Union[str, None], str | None,
Form(json_schema_extra={"format": "password"}), Form(json_schema_extra={"format": "password"}),
Doc( Doc(
""" """
@ -294,7 +294,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
), ),
] = "", ] = "",
client_id: Annotated[ client_id: Annotated[
Union[str, None], str | None,
Form(), Form(),
Doc( Doc(
""" """
@ -305,7 +305,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
), ),
] = None, ] = None,
client_secret: Annotated[ client_secret: Annotated[
Union[str, None], str | None,
Form(), Form(),
Doc( Doc(
""" """
@ -344,7 +344,7 @@ class OAuth2(SecurityBase):
self, self,
*, *,
flows: Annotated[ flows: Annotated[
Union[OAuthFlowsModel, dict[str, dict[str, Any]]], OAuthFlowsModel | dict[str, dict[str, Any]],
Doc( Doc(
""" """
The dictionary of OAuth2 flows. The dictionary of OAuth2 flows.
@ -352,7 +352,7 @@ class OAuth2(SecurityBase):
), ),
] = OAuthFlowsModel(), ] = OAuthFlowsModel(),
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -362,7 +362,7 @@ class OAuth2(SecurityBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -420,7 +420,7 @@ class OAuth2(SecurityBase):
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
if not authorization: if not authorization:
if self.auto_error: if self.auto_error:
@ -454,7 +454,7 @@ class OAuth2PasswordBearer(OAuth2):
), ),
], ],
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -464,7 +464,7 @@ class OAuth2PasswordBearer(OAuth2):
), ),
] = None, ] = None,
scopes: Annotated[ scopes: Annotated[
Optional[dict[str, str]], dict[str, str] | None,
Doc( Doc(
""" """
The OAuth2 scopes that would be required by the *path operations* that The OAuth2 scopes that would be required by the *path operations* that
@ -476,7 +476,7 @@ class OAuth2PasswordBearer(OAuth2):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -506,7 +506,7 @@ class OAuth2PasswordBearer(OAuth2):
), ),
] = True, ] = True,
refreshUrl: Annotated[ refreshUrl: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
The URL to refresh the token and obtain a new one. The URL to refresh the token and obtain a new one.
@ -533,7 +533,7 @@ class OAuth2PasswordBearer(OAuth2):
auto_error=auto_error, auto_error=auto_error,
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization) scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer": if not authorization or scheme.lower() != "bearer":
@ -562,7 +562,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
), ),
], ],
refreshUrl: Annotated[ refreshUrl: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
The URL to refresh the token and obtain a new one. The URL to refresh the token and obtain a new one.
@ -570,7 +570,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
), ),
] = None, ] = None,
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -580,7 +580,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
), ),
] = None, ] = None,
scopes: Annotated[ scopes: Annotated[
Optional[dict[str, str]], dict[str, str] | None,
Doc( Doc(
""" """
The OAuth2 scopes that would be required by the *path operations* that The OAuth2 scopes that would be required by the *path operations* that
@ -589,7 +589,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -639,7 +639,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
auto_error=auto_error, auto_error=auto_error,
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization) scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer": if not authorization or scheme.lower() != "bearer":
@ -666,7 +666,7 @@ class SecurityScopes:
def __init__( def __init__(
self, self,
scopes: Annotated[ scopes: Annotated[
Optional[list[str]], list[str] | None,
Doc( Doc(
""" """
This will be filled by FastAPI. This will be filled by FastAPI.

8
fastapi/security/open_id_connect_url.py

@ -1,4 +1,4 @@
from typing import Annotated, Optional from typing import Annotated
from annotated_doc import Doc from annotated_doc import Doc
from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
@ -31,7 +31,7 @@ class OpenIdConnect(SecurityBase):
), ),
], ],
scheme_name: Annotated[ scheme_name: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme name. Security scheme name.
@ -41,7 +41,7 @@ class OpenIdConnect(SecurityBase):
), ),
] = None, ] = None,
description: Annotated[ description: Annotated[
Optional[str], str | None,
Doc( Doc(
""" """
Security scheme description. Security scheme description.
@ -84,7 +84,7 @@ class OpenIdConnect(SecurityBase):
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> str | None:
authorization = request.headers.get("Authorization") authorization = request.headers.get("Authorization")
if not authorization: if not authorization:
if self.auto_error: if self.auto_error:

5
fastapi/security/utils.py

@ -1,8 +1,5 @@
from typing import Optional
def get_authorization_scheme_param( def get_authorization_scheme_param(
authorization_header_value: Optional[str], authorization_header_value: str | None,
) -> tuple[str, str]: ) -> tuple[str, str]:
if not authorization_header_value: if not authorization_header_value:
return "", "" return "", ""

7
fastapi/types.py

@ -1,11 +1,12 @@
import types import types
from collections.abc import Callable
from enum import Enum from enum import Enum
from typing import Any, Callable, Optional, TypeVar, Union from typing import Any, TypeVar, Union
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.main import IncEx as IncEx from pydantic.main import IncEx as IncEx
DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any])
UnionType = getattr(types, "UnionType", Union) UnionType = getattr(types, "UnionType", Union)
ModelNameMap = dict[Union[type[BaseModel], type[Enum]], str] ModelNameMap = dict[type[BaseModel] | type[Enum], str]
DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str] DependencyCacheKey = tuple[Callable[..., Any] | None, tuple[str, ...], str]

18
fastapi/utils.py

@ -3,8 +3,7 @@ import warnings
from typing import ( from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Any, Any,
Optional, Literal,
Union,
) )
import fastapi import fastapi
@ -17,7 +16,6 @@ from fastapi._compat import (
from fastapi.datastructures import DefaultPlaceholder, DefaultType from fastapi.datastructures import DefaultPlaceholder, DefaultType
from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError
from pydantic.fields import FieldInfo from pydantic.fields import FieldInfo
from typing_extensions import Literal
from ._compat import v2 from ._compat import v2
@ -25,7 +23,7 @@ if TYPE_CHECKING: # pragma: nocover
from .routing import APIRoute from .routing import APIRoute
def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: def is_body_allowed_for_status_code(status_code: int | str | None) -> bool:
if status_code is None: if status_code is None:
return True return True
# Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1 # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1
@ -60,9 +58,9 @@ _invalid_args_message = (
def create_model_field( def create_model_field(
name: str, name: str,
type_: Any, type_: Any,
default: Optional[Any] = Undefined, default: Any | None = Undefined,
field_info: Optional[FieldInfo] = None, field_info: FieldInfo | None = None,
alias: Optional[str] = None, alias: str | None = None,
mode: Literal["validation", "serialization"] = "validation", mode: Literal["validation", "serialization"] = "validation",
) -> ModelField: ) -> ModelField:
if annotation_is_pydantic_v1(type_): if annotation_is_pydantic_v1(type_):
@ -121,9 +119,9 @@ def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) ->
def get_value_or_default( def get_value_or_default(
first_item: Union[DefaultPlaceholder, DefaultType], first_item: DefaultPlaceholder | DefaultType,
*extra_items: Union[DefaultPlaceholder, DefaultType], *extra_items: DefaultPlaceholder | DefaultType,
) -> Union[DefaultPlaceholder, DefaultType]: ) -> DefaultPlaceholder | DefaultType:
""" """
Pass items or `DefaultPlaceholder`s by descending priority. Pass items or `DefaultPlaceholder`s by descending priority.

28
pdm_build.py

@ -3,18 +3,38 @@ from typing import Any
from pdm.backend.hooks import Context from pdm.backend.hooks import Context
TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "fastapi") TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE")
def pdm_build_initialize(context: Context) -> None: def pdm_build_initialize(context: Context) -> None:
metadata = context.config.metadata metadata = context.config.metadata
# Get main version
version = metadata["version"]
# Get custom config for the current package, from the env var # Get custom config for the current package, from the env var
config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ all_configs_config: dict[str, Any] = context.config.data["tool"]["tiangolo"][
"_internal-slim-build" "_internal-slim-build"
]["packages"].get(TIANGOLO_BUILD_PACKAGE) ]["packages"]
if not config:
if TIANGOLO_BUILD_PACKAGE not in all_configs_config:
return return
config = all_configs_config[TIANGOLO_BUILD_PACKAGE]
project_config: dict[str, Any] = config["project"] project_config: dict[str, Any] = config["project"]
# Override main [project] configs with custom configs for this package # Override main [project] configs with custom configs for this package
for key, value in project_config.items(): for key, value in project_config.items():
metadata[key] = value metadata[key] = value
# Get custom build config for the current package
build_config: dict[str, Any] = (
config.get("tool", {}).get("pdm", {}).get("build", {})
)
# Override PDM build config with custom build config for this package
for key, value in build_config.items():
context.config.build_config[key] = value
# Get main dependencies
dependencies: list[str] = metadata.get("dependencies", [])
# Sync versions in dependencies
new_dependencies = []
for dep in dependencies:
new_dep = f"{dep}>={version}"
new_dependencies.append(new_dep)
metadata["dependencies"] = new_dependencies

26
pyproject.toml

@ -9,7 +9,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code,
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT"
license-files = ["LICENSE"] license-files = ["LICENSE"]
requires-python = ">=3.9" requires-python = ">=3.10"
authors = [ authors = [
{ name = "Sebastián Ramírez", email = "[email protected]" }, { name = "Sebastián Ramírez", email = "[email protected]" },
] ]
@ -33,7 +33,6 @@ classifiers = [
"Framework :: Pydantic :: 2", "Framework :: Pydantic :: 2",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
@ -202,6 +201,29 @@ source-includes = [
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] [tool.tiangolo._internal-slim-build.packages.fastapi-slim.project]
name = "fastapi-slim" name = "fastapi-slim"
readme = "fastapi-slim/README.md"
dependencies = [
"fastapi",
]
optional-dependencies = {}
scripts = {}
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.tool.pdm.build]
# excludes needs to explicitly exclude the top level python packages,
# otherwise PDM includes them by default
# A "*" glob pattern can't be used here because in PDM internals, the patterns are put
# in a set (unordered, order varies) and each excluded file is assigned one of the
# glob patterns that matches, as the set is unordered, the matched pattern could be "*"
# independent of the order here. And then the internal code would give it a lower score
# than the one for a default included file.
# By not using "*" and explicitly excluding the top level packages, they get a higher
# score than the default inclusion
excludes = ["fastapi", "tests", "pdm_build.py"]
# source-includes needs to explicitly define some value because PDM will check the
# truthy value of the list, and if empty, will include some defaults, including "tests",
# an empty string doesn't match anything, but makes the list truthy, so that PDM
# doesn't override it during the build.
source-includes = [""]
[tool.mypy] [tool.mypy]
plugins = ["pydantic.mypy"] plugins = ["pydantic.mypy"]

64
scripts/general-llm-prompt.md

@ -353,7 +353,9 @@ Erstelle eine [Virtuelle Umgebung](../virtual-environments.md){.internal-link ta
Translate HTML abbr elements (`<abbr title="description">text</abbr>`) as follows: Translate HTML abbr elements (`<abbr title="description">text</abbr>`) as follows:
- If the text surrounded by the abbr element is an abbreviation (the text may be surrounded by further HTML or Markdown markup or quotes, for example <code>text</code> or `text` or "text", ignore that further markup when deciding if the text is an abbreviation), and if the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash (-) to the full phrase, followed by the translation of the full phrase. - The text inside abbr tag may be surrounded by further HTML or Markdown markup or quotes, for example <code>text</code> or `text` or "text". Preserve markup and only translate visible text inside the abbr element.
- If the description (the text inside the title attribute) contains the full phrase for this abbreviation, then append a dash (-) to the full phrase, followed by the translation of the full phrase.
Conversion scheme: Conversion scheme:
@ -421,45 +423,7 @@ Result (German):
<abbr title="Asynchrones Server-Gateway-Interface">ASGI</abbr> <abbr title="Asynchrones Server-Gateway-Interface">ASGI</abbr>
``` ```
- If the description is not a full phrase for an abbreviation which the abbr element surrounds, but some other information, then just translate the description. - If the title of abbr element contains a full phrase for that abbreviation, and other information, separated by a colon (`:`), then append a dash (`-`) and the translation of the full phrase to the original full phrase and translate the other information.
Conversion scheme:
Source (English):
```
<abbr title="{description}">{text}</abbr>
```
Result:
```
<abbr title="{translation of description}">{translation of text}</abbr>
```
Examples:
Source (English):
```
<abbr title="also known as: endpoints, routes">path</abbr>
<abbr title="a program that checks for code errors">linter</abbr>
<abbr title="converting the string that comes from an HTTP request into Python data">parsing</abbr>
<abbr title="before 2023-03">0.95.0</abbr>
<abbr title="2023-08-26">at the time of writing this</abbr>
```
Result (German):
```
<abbr title="auch bekannt als: Endpunkte, Routen">Pfad</abbr>
<abbr title="Programm das auf Fehler im Code prüft">Linter</abbr>
<abbr title="Konvertieren des Strings eines HTTP-Requests in Python-Daten">Parsen</abbr>
<abbr title="vor 2023-03">0.95.0</abbr>
<abbr title="2023-08-26">zum Zeitpunkt als das hier geschrieben wurde</abbr>
```
- If the text surrounded by the abbr element is an abbreviation and the description contains both the full phrase for that abbreviation, and other information, separated by a colon (`:`), then append a dash (`-`) and the translation of the full phrase to the original full phrase and translate the other information.
Conversion scheme: Conversion scheme:
@ -526,3 +490,23 @@ Result (German):
- If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements. - If there is an existing translation, and it has ADDITIONAL abbr elements in a sentence, and these additional abbr elements do not exist in the related sentence in the English text, then KEEP those additional abbr elements in the translation. Do not remove them. Except when you remove the whole sentence from the translation, because the whole sentence was removed from the English text, then also remove the abbr element. The reasoning for this rule is, that such additional abbr elements are manually added by the human editor of the translation, in order to translate or explain an English word to the human readers of the translation. These additional abbr elements would not make sense in the English text, but they do make sense in the translation. So keep them in the translation, even though they are not part of the English text. This rule only applies to abbr elements.
- Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's described above alone. Do not change their formatting or content. - Apply above rules also when there is an existing translation! Make sure that all title attributes in abbr elements get properly translated or updated, using the schemes given above. However, leave the ADDITIONAL abbr's described above alone. Do not change their formatting or content.
### HTML dfn elements
For HTML dfn elements (`<dfn>text</dfn>`), translate the text inside the dfn element and the title attribute. Do not include the original English text in the title attribute.
Examples:
Source (English):
```
<dfn title="also known as: endpoints, routes">path</dfn>
<dfn title="a program that checks for code errors">linter</dfn>
```
Result (German):
```
<dfn title="auch bekannt als: Endpunkte, Routen">Pfad</dfn>
<dfn title="Programm das auf Fehler im Code prüft">Linter</dfn>
```

8
tests/test_dependencies_utils.py

@ -0,0 +1,8 @@
from fastapi.dependencies.utils import get_typed_annotation
def test_get_typed_annotation():
# For coverage
annotation = "None"
typed_annotation = get_typed_annotation(annotation, globals())
assert typed_annotation is None

46
tests/test_list_bytes_file_order_preserved_issue_14811.py

@ -0,0 +1,46 @@
"""
Regression test: preserve order when using list[bytes] + File()
See https://github.com/fastapi/fastapi/discussions/14811
Fixed in PR: https://github.com/fastapi/fastapi/pull/14884
"""
from typing import Annotated
import anyio
import pytest
from fastapi import FastAPI, File
from fastapi.testclient import TestClient
from starlette.datastructures import UploadFile as StarletteUploadFile
def test_list_bytes_file_preserves_order(
monkeypatch: pytest.MonkeyPatch,
) -> None:
app = FastAPI()
@app.post("/upload")
async def upload(files: Annotated[list[bytes], File()]):
# return something that makes order obvious
return [b[0] for b in files]
original_read = StarletteUploadFile.read
async def patched_read(self: StarletteUploadFile, size: int = -1) -> bytes:
# Make the FIRST file slower *deterministically*
if self.filename == "slow.txt":
await anyio.sleep(0.05)
return await original_read(self, size)
monkeypatch.setattr(StarletteUploadFile, "read", patched_read)
client = TestClient(app)
files = [
("files", ("slow.txt", b"A" * 10, "text/plain")),
("files", ("fast.txt", b"B" * 10, "text/plain")),
]
r = client.post("/upload", files=files)
assert r.status_code == 200, r.text
# Must preserve request order: slow first, fast second
assert r.json() == [ord("A"), ord("B")]

6
tests/test_request_params/test_path/test_required_str.py

@ -3,7 +3,7 @@ from typing import Annotated
import pytest import pytest
from fastapi import FastAPI, Path from fastapi import FastAPI, Path
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import snapshot from inline_snapshot import Is, snapshot
app = FastAPI() app = FastAPI()
@ -58,8 +58,8 @@ def test_schema(path: str, expected_name: str, expected_title: str):
[ [
{ {
"required": True, "required": True,
"schema": {"title": expected_title, "type": "string"}, "schema": {"title": Is(expected_title), "type": "string"},
"name": expected_name, "name": Is(expected_name),
"in": "path", "in": "path",
} }
] ]

12
tests/test_router_circular_import.py

@ -0,0 +1,12 @@
import pytest
from fastapi import APIRouter
def test_router_circular_import():
router = APIRouter()
with pytest.raises(
AssertionError,
match="Cannot include the same APIRouter instance into itself. Did you mean to include a different router?",
):
router.include_router(router)

60
tests/test_router_events.py

@ -317,3 +317,63 @@ def test_router_async_generator_lifespan(state: State) -> None:
assert response.json() == {"message": "Hello World"} assert response.json() == {"message": "Hello World"}
assert state.app_startup is True assert state.app_startup is True
assert state.app_shutdown is True assert state.app_shutdown is True
def test_startup_shutdown_handlers_as_parameters(state: State) -> None:
"""Test that startup/shutdown handlers passed as parameters to FastAPI are called correctly."""
def app_startup() -> None:
state.app_startup = True
def app_shutdown() -> None:
state.app_shutdown = True
app = FastAPI(on_startup=[app_startup], on_shutdown=[app_shutdown])
@app.get("/")
def main() -> dict[str, str]:
return {"message": "Hello World"}
def router_startup() -> None:
state.router_startup = True
def router_shutdown() -> None:
state.router_shutdown = True
router = APIRouter(on_startup=[router_startup], on_shutdown=[router_shutdown])
def sub_router_startup() -> None:
state.sub_router_startup = True
def sub_router_shutdown() -> None:
state.sub_router_shutdown = True
sub_router = APIRouter(
on_startup=[sub_router_startup], on_shutdown=[sub_router_shutdown]
)
router.include_router(sub_router)
app.include_router(router)
assert state.app_startup is False
assert state.router_startup is False
assert state.sub_router_startup is False
assert state.app_shutdown is False
assert state.router_shutdown is False
assert state.sub_router_shutdown is False
with TestClient(app) as client:
assert state.app_startup is True
assert state.router_startup is True
assert state.sub_router_startup is True
assert state.app_shutdown is False
assert state.router_shutdown is False
assert state.sub_router_shutdown is False
response = client.get("/")
assert response.status_code == 200, response.text
assert response.json() == {"message": "Hello World"}
assert state.app_startup is True
assert state.router_startup is True
assert state.sub_router_startup is True
assert state.app_shutdown is True
assert state.router_shutdown is True
assert state.sub_router_shutdown is True

4
tests/test_tutorial/test_body_nested_models/test_tutorial001_tutorial002_tutorial003.py

@ -3,7 +3,7 @@ import importlib
import pytest import pytest
from dirty_equals import IsList from dirty_equals import IsList
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import snapshot from inline_snapshot import Is, snapshot
from ...utils import needs_py310 from ...utils import needs_py310
@ -212,7 +212,7 @@ def test_openapi_schema(client: TestClient, mod_name: str):
"title": "Tax", "title": "Tax",
"anyOf": [{"type": "number"}, {"type": "null"}], "anyOf": [{"type": "number"}, {"type": "null"}],
}, },
"tags": tags_schema, "tags": Is(tags_schema),
}, },
"required": [ "required": [
"name", "name",

4
tests/test_tutorial/test_custom_response/test_tutorial002_tutorial003_tutorial004.py

@ -2,7 +2,7 @@ import importlib
import pytest import pytest
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import snapshot from inline_snapshot import Is, snapshot
@pytest.fixture( @pytest.fixture(
@ -59,7 +59,7 @@ def test_openapi_schema(client: TestClient, mod_name: str):
"responses": { "responses": {
"200": { "200": {
"description": "Successful Response", "description": "Successful Response",
"content": response_content, "content": Is(response_content),
} }
}, },
"summary": "Read Items", "summary": "Read Items",

4
tests/test_tutorial/test_path_operation_configurations/test_tutorial003_tutorial004.py

@ -4,7 +4,7 @@ from textwrap import dedent
import pytest import pytest
from dirty_equals import IsList from dirty_equals import IsList
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import snapshot from inline_snapshot import Is, snapshot
from ...utils import needs_py310 from ...utils import needs_py310
@ -75,7 +75,7 @@ def test_openapi_schema(client: TestClient, mod_name: str):
"/items/": { "/items/": {
"post": { "post": {
"summary": "Create an item", "summary": "Create an item",
"description": DESCRIPTIONS[mod_name], "description": Is(DESCRIPTIONS[mod_name]),
"operationId": "create_item_items__post", "operationId": "create_item_items__post",
"requestBody": { "requestBody": {
"content": { "content": {

39
tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py

@ -3,7 +3,7 @@ import importlib
import pytest import pytest
from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import snapshot from inline_snapshot import Is, snapshot
from ...utils import needs_py310 from ...utils import needs_py310
@ -66,6 +66,23 @@ def test_query_params_str_validations_item_query_nonregexquery(client: TestClien
def test_openapi_schema(client: TestClient): def test_openapi_schema(client: TestClient):
response = client.get("/openapi.json") response = client.get("/openapi.json")
assert response.status_code == 200, response.text assert response.status_code == 200, response.text
parameters_schema = {
"anyOf": [
{
"type": "string",
"minLength": 3,
"maxLength": 50,
"pattern": "^fixedquery$",
},
{"type": "null"},
],
"title": "Query string",
"description": "Query string for the items to search in the database that have a good match",
# See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
**({"deprecated": True} if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10) else {}),
}
assert response.json() == snapshot( assert response.json() == snapshot(
{ {
"openapi": "3.1.0", "openapi": "3.1.0",
@ -96,25 +113,7 @@ def test_openapi_schema(client: TestClient):
"description": "Query string for the items to search in the database that have a good match", "description": "Query string for the items to search in the database that have a good match",
"required": False, "required": False,
"deprecated": True, "deprecated": True,
"schema": { "schema": Is(parameters_schema),
"anyOf": [
{
"type": "string",
"minLength": 3,
"maxLength": 50,
"pattern": "^fixedquery$",
},
{"type": "null"},
],
"title": "Query string",
"description": "Query string for the items to search in the database that have a good match",
# See https://github.com/pydantic/pydantic/blob/80353c29a824c55dea4667b328ba8f329879ac9f/tests/test_fastapi.sh#L25-L34.
**(
{"deprecated": True}
if PYDANTIC_VERSION_MINOR_TUPLE >= (2, 10)
else {}
),
},
"name": "item-query", "name": "item-query",
"in": "query", "in": "query",
} }

2300
uv.lock

File diff suppressed because it is too large
Loading…
Cancel
Save