Compare commits

..

11 Commits

Author SHA1 Message Date
github-actions[bot]
d06ab3f5c7 📝 Update release notes
[skip ci]
2026-02-11 18:41:46 +00:00
Sebastián Ramírez
3da206c06d 🎨 Update internal types for Python 3.10 (#14898) 2026-02-11 18:41:21 +00:00
github-actions[bot]
cc903bd440 📝 Update release notes
[skip ci]
2026-02-11 18:32:39 +00:00
Sebastián Ramírez
ad4e8e0060 Drop support for Python 3.9 (#14897)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-11 19:32:12 +01:00
Sebastián Ramírez
bdd20051c4 🔖 Release version 0.128.8 2026-02-11 16:16:34 +01:00
github-actions[bot]
1ed9bd4923 📝 Update release notes
[skip ci]
2026-02-11 13:37:37 +00:00
Sebastián Ramírez
aac30fd707 🔨 Tweak PDM hook script (#14895) 2026-02-11 13:37:09 +00:00
github-actions[bot]
417f1ee078 📝 Update release notes
[skip ci]
2026-02-11 12:34:12 +00:00
Sebastián Ramírez
ffb8965260 ♻️ Update build setup for fastapi-slim, deprecate it, and make it only depend on fastapi (#14894) 2026-02-11 12:33:49 +00:00
github-actions[bot]
93fa935fb8 📝 Update release notes
[skip ci]
2026-02-10 12:26:38 +00:00
Sanjana S
f0f3e7a113 📝 Fix grammar in docs/en/docs/tutorial/first-steps.md (#14708) 2026-02-10 13:26:10 +01:00
42 changed files with 1694 additions and 3355 deletions

View File

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

View File

@@ -56,14 +56,10 @@ jobs:
- starlette-pypi
- starlette-git
include:
- os: ubuntu-latest
python-version: "3.9"
coverage: coverage
uv-resolution: lowest-direct
- os: macos-latest
python-version: "3.10"
coverage: coverage
uv-resolution: highest
uv-resolution: lowest-direct
- os: windows-latest
python-version: "3.12"
coverage: coverage

View File

@@ -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`. 😎

View File

@@ -14,7 +14,7 @@ In a hurry and already know this stuff? Jump to the [`Dockerfile` below 👇](#b
<summary>Dockerfile Preview 👀</summary>
```Dockerfile
FROM python:3.9
FROM python:3.14
WORKDIR /code
@@ -166,7 +166,7 @@ Now in the same project directory create a file `Dockerfile` with:
```{ .dockerfile .annotate }
# (1)!
FROM python:3.9
FROM python:3.14
# (2)!
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`:
```{ .dockerfile .annotate hl_lines="10 13" }
FROM python:3.9
FROM python:3.14
WORKDIR /code
@@ -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:
```{ .dockerfile .annotate }
FROM python:3.9
FROM python:3.14
WORKDIR /code

View File

@@ -135,27 +135,30 @@ You can use, for example:
{* ../../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.
### Generic types { #generic-types }
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.
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]`.
If you can choose a more recent version of Python for your project, you will be able to take advantage of that extra simplicity.
These types that can take type parameters are called **Generic types** or **Generics**.
In all the docs there are examples compatible with each version of Python (when there's a difference).
You can use the same builtin types as generics (with square brackets and types inside):
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).
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 }
@@ -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`.
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 <dfn title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</dfn>.
//// 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"
{!> ../../docs_src/python_types/tutorial008b_py310.py!}
```
////
//// 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`.
This means that `item` could be an `int` or a `str`.
#### Possibly `None` { #possibly-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+
```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+
```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 <dfn title='also called "bitwise or operator", but that meaning is not relevant here'>vertical bar (`|`)</dfn> 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.
////
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.
### 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}.
/// 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 }
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] *}

View File

@@ -7,6 +7,25 @@ hide:
## 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

View File

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

View File

@@ -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`.
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.

View File

@@ -101,7 +101,7 @@ Now you can declare your dependency using this class.
Notice how we write `CommonQueryParams` twice in the above code:
//// tab | Python 3.9+
//// tab | Python 3.10+
```Python
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
@@ -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:
//// tab | Python 3.9+
//// tab | Python 3.10+
```Python
commons: Annotated[CommonQueryParams, ...
@@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ...
////
//// tab | Python 3.9+ non-Annotated
//// tab | Python 3.10+ non-Annotated
/// tip
@@ -163,7 +163,7 @@ commons: CommonQueryParams ...
You could actually write just:
//// tab | Python 3.9+
//// tab | Python 3.10+
```Python
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
@@ -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:
//// tab | Python 3.9+
//// tab | Python 3.10+
```Python
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
@@ -225,7 +225,7 @@ For those specific cases, you can do the following:
Instead of writing:
//// tab | Python 3.9+
//// tab | Python 3.10+
```Python
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
@@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams)
...you write:
//// tab | Python 3.9+
//// tab | Python 3.10+
```Python
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

View File

@@ -62,7 +62,7 @@ And it will save the returned value in a <dfn title="A utility/system to store c
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"
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

View File

@@ -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.
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] *}
@@ -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.
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] *}

View File

@@ -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)
```
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 }

View File

@@ -47,40 +47,16 @@ Now it's the time to use it with FastAPI. 🚀
We had this type annotation:
//// tab | Python 3.10+
```Python
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:
//// tab | Python 3.10+
```Python
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`.
Now let's jump to the fun stuff. 🎉

View File

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

54
fastapi-slim/README.md Normal file
View File

@@ -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.

View File

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

View File

@@ -1,4 +1,3 @@
import sys
import types
import typing
import warnings
@@ -8,27 +7,26 @@ from dataclasses import is_dataclass
from typing import (
Annotated,
Any,
TypeGuard,
TypeVar,
Union,
get_args,
get_origin,
)
from fastapi.types import UnionType
from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION
from starlette.datastructures import UploadFile
from typing_extensions import TypeGuard, get_args, get_origin
_T = TypeVar("_T")
# Copy from Pydantic: pydantic/_internal/_typing_extra.py
if sys.version_info < (3, 10):
WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined]
else:
WithArgsTypes: tuple[Any, ...] = (
typing._GenericAlias, # type: ignore[attr-defined]
types.GenericAlias,
types.UnionType,
) # pyright: ignore[reportAttributeAccessIssue]
WithArgsTypes: tuple[Any, ...] = (
typing._GenericAlias, # type: ignore[attr-defined]
types.GenericAlias,
types.UnionType,
) # pyright: ignore[reportAttributeAccessIssue]
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
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]]:
try:
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
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)):
return False
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)
if origin is Union or origin is UnionType:
for arg in get_args(annotation):
@@ -79,7 +77,7 @@ def value_is_sequence(value: Any) -> bool:
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 (
lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
or _annotation_is_sequence(annotation)
@@ -87,7 +85,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)
if origin is Union or origin is UnionType:
return any(field_annotation_is_complex(arg) for arg in get_args(annotation))
@@ -108,7 +106,7 @@ def field_annotation_is_scalar(annotation: Any) -> bool:
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)
if origin is Union or origin is UnionType:
at_least_one_scalar_sequence = False

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

@@ -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 starlette.background import BackgroundTasks as StarletteBackgroundTasks

View File

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

View File

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

View File

@@ -1,18 +1,19 @@
import dataclasses
import inspect
import sys
from collections.abc import Mapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from contextlib import AsyncExitStack, contextmanager
from copy import copy, deepcopy
from dataclasses import dataclass
from typing import (
Annotated,
Any,
Callable,
ForwardRef,
Optional,
Literal,
Union,
cast,
get_args,
get_origin,
)
from fastapi import params
@@ -63,7 +64,6 @@ from starlette.datastructures import (
from starlette.requests import HTTPConnection, Request
from starlette.responses import Response
from starlette.websockets import WebSocket
from typing_extensions import Literal, get_args, get_origin
from typing_inspection.typing_objects import is_typealiastype
multipart_not_installed_error = (
@@ -127,8 +127,8 @@ def get_flat_dependant(
dependant: Dependant,
*,
skip_repeats: bool = False,
visited: Optional[list[DependencyCacheKey]] = None,
parent_oauth_scopes: Optional[list[str]] = None,
visited: list[DependencyCacheKey] | None = None,
parent_oauth_scopes: list[str] | None = None,
) -> Dependant:
if visited is None:
visited = []
@@ -199,20 +199,17 @@ def get_flat_params(dependant: Dependant) -> list[ModelField]:
def _get_signature(call: Callable[..., Any]) -> inspect.Signature:
if sys.version_info >= (3, 10):
try:
signature = inspect.signature(call, eval_str=True)
except NameError:
# Handle type annotations with if TYPE_CHECKING, not used by FastAPI
# e.g. dependency return types
if sys.version_info >= (3, 14):
from annotationlib import Format
try:
signature = inspect.signature(call, eval_str=True)
except NameError:
# Handle type annotations with if TYPE_CHECKING, not used by FastAPI
# e.g. dependency return types
if sys.version_info >= (3, 14):
from annotationlib import Format
signature = inspect.signature(call, annotation_format=Format.FORWARDREF)
else:
signature = inspect.signature(call)
else:
signature = inspect.signature(call)
signature = inspect.signature(call, annotation_format=Format.FORWARDREF)
else:
signature = inspect.signature(call)
return signature
@@ -258,11 +255,11 @@ def get_dependant(
*,
path: str,
call: Callable[..., Any],
name: Optional[str] = None,
own_oauth_scopes: Optional[list[str]] = None,
parent_oauth_scopes: Optional[list[str]] = None,
name: str | None = None,
own_oauth_scopes: list[str] | None = None,
parent_oauth_scopes: list[str] | None = None,
use_cache: bool = True,
scope: Union[Literal["function", "request"], None] = None,
scope: Literal["function", "request"] | None = None,
) -> Dependant:
dependant = Dependant(
call=call,
@@ -331,7 +328,7 @@ def get_dependant(
def add_non_field_param_to_dependency(
*, param_name: str, type_annotation: Any, dependant: Dependant
) -> Optional[bool]:
) -> bool | None:
if lenient_issubclass(type_annotation, Request):
dependant.request_param_name = param_name
return True
@@ -356,8 +353,8 @@ def add_non_field_param_to_dependency(
@dataclass
class ParamDetails:
type_annotation: Any
depends: Optional[params.Depends]
field: Optional[ModelField]
depends: params.Depends | None
field: ModelField | None
def analyze_param(
@@ -399,7 +396,7 @@ def analyze_param(
)
]
if fastapi_specific_annotations:
fastapi_annotation: Union[FieldInfo, params.Depends, None] = (
fastapi_annotation: FieldInfo | params.Depends | None = (
fastapi_specific_annotations[-1]
)
else:
@@ -560,20 +557,20 @@ async def _solve_generator(
class SolvedDependency:
values: dict[str, Any]
errors: list[Any]
background_tasks: Optional[StarletteBackgroundTasks]
background_tasks: StarletteBackgroundTasks | None
response: Response
dependency_cache: dict[DependencyCacheKey, Any]
async def solve_dependencies(
*,
request: Union[Request, WebSocket],
request: Request | WebSocket,
dependant: Dependant,
body: Optional[Union[dict[str, Any], FormData]] = None,
background_tasks: Optional[StarletteBackgroundTasks] = None,
response: Optional[Response] = None,
dependency_overrides_provider: Optional[Any] = None,
dependency_cache: Optional[dict[DependencyCacheKey, Any]] = None,
body: dict[str, Any] | FormData | None = None,
background_tasks: StarletteBackgroundTasks | None = None,
response: Response | None = None,
dependency_overrides_provider: Any | None = None,
dependency_cache: dict[DependencyCacheKey, Any] | None = None,
# 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)
async_exit_stack: AsyncExitStack,
@@ -721,7 +718,7 @@ def _is_json_field(field: ModelField) -> bool:
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:
alias = alias or get_validation_alias(field)
if (
@@ -753,7 +750,7 @@ def _get_multidict_value(
def request_params_to_args(
fields: Sequence[ModelField],
received_params: Union[Mapping[str, Any], QueryParams, Headers],
received_params: Mapping[str, Any] | QueryParams | Headers,
) -> tuple[dict[str, Any], list[Any]]:
values: dict[str, Any] = {}
errors: list[dict[str, Any]] = []
@@ -901,7 +898,7 @@ async def _extract_form_body(
):
# For types
assert isinstance(value, sequence_types)
results: list[Union[bytes, str]] = []
results: list[bytes | str] = []
for sub_value in value:
results.append(await sub_value.read())
value = serialize_sequence_value(field=field, value=results)
@@ -920,7 +917,7 @@ async def _extract_form_body(
async def request_body_to_args(
body_fields: list[ModelField],
received_body: Optional[Union[dict[str, Any], FormData]],
received_body: dict[str, Any] | FormData | None,
embed_body_fields: bool,
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
values: dict[str, Any] = {}
@@ -950,7 +947,7 @@ async def request_body_to_args(
return {first_field.name: v_}, errors_
for field in body_fields:
loc = ("body", get_validation_alias(field))
value: Optional[Any] = None
value: Any | None = None
if body_to_process is not None:
try:
value = body_to_process.get(get_validation_alias(field))
@@ -970,7 +967,7 @@ async def request_body_to_args(
def get_body_field(
*, 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
all body parameters into a single field if necessary.

View File

@@ -1,6 +1,7 @@
import dataclasses
import datetime
from collections import defaultdict, deque
from collections.abc import Callable
from decimal import Decimal
from enum import Enum
from ipaddress import (
@@ -14,7 +15,7 @@ from ipaddress import (
from pathlib import Path, PurePath
from re import Pattern
from types import GeneratorType
from typing import Annotated, Any, Callable, Optional, Union
from typing import Annotated, Any
from uuid import UUID
from annotated_doc import Doc
@@ -33,13 +34,13 @@ from ._compat import (
# 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()
# Adapted from Pydantic v1
# 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
@@ -118,7 +119,7 @@ def jsonable_encoder(
),
],
include: Annotated[
Optional[IncEx],
IncEx | None,
Doc(
"""
Pydantic's `include` parameter, passed to Pydantic models to set the
@@ -127,7 +128,7 @@ def jsonable_encoder(
),
] = None,
exclude: Annotated[
Optional[IncEx],
IncEx | None,
Doc(
"""
Pydantic's `exclude` parameter, passed to Pydantic models to set the
@@ -177,7 +178,7 @@ def jsonable_encoder(
),
] = False,
custom_encoder: Annotated[
Optional[dict[Any, Callable[[Any], Any]]],
dict[Any, Callable[[Any], Any]] | None,
Doc(
"""
Pydantic's `custom_encoder` parameter, passed to Pydantic models to define

View File

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

View File

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

View File

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

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

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

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
from typing import Annotated, Optional, Union
from typing import Annotated
from annotated_doc import Doc
from fastapi.openapi.models import APIKey, APIKeyIn
@@ -13,8 +13,8 @@ class APIKeyBase(SecurityBase):
self,
location: APIKeyIn,
name: str,
description: Union[str, None],
scheme_name: Union[str, None],
description: str | None,
scheme_name: str | None,
auto_error: bool,
):
self.auto_error = auto_error
@@ -42,7 +42,7 @@ class APIKeyBase(SecurityBase):
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 self.auto_error:
raise self.make_not_authenticated_error()
@@ -90,7 +90,7 @@ class APIKeyQuery(APIKeyBase):
Doc("Query parameter name."),
],
scheme_name: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme name.
@@ -100,7 +100,7 @@ class APIKeyQuery(APIKeyBase):
),
] = None,
description: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme description.
@@ -137,7 +137,7 @@ class APIKeyQuery(APIKeyBase):
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)
return self.check_api_key(api_key)
@@ -179,7 +179,7 @@ class APIKeyHeader(APIKeyBase):
*,
name: Annotated[str, Doc("Header name.")],
scheme_name: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme name.
@@ -189,7 +189,7 @@ class APIKeyHeader(APIKeyBase):
),
] = None,
description: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme description.
@@ -225,7 +225,7 @@ class APIKeyHeader(APIKeyBase):
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)
return self.check_api_key(api_key)
@@ -267,7 +267,7 @@ class APIKeyCookie(APIKeyBase):
*,
name: Annotated[str, Doc("Cookie name.")],
scheme_name: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme name.
@@ -277,7 +277,7 @@ class APIKeyCookie(APIKeyBase):
),
] = None,
description: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme description.
@@ -313,6 +313,6 @@ class APIKeyCookie(APIKeyBase):
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)
return self.check_api_key(api_key)

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
from typing import Annotated, Optional
from typing import Annotated
from annotated_doc import Doc
from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel
@@ -31,7 +31,7 @@ class OpenIdConnect(SecurityBase):
),
],
scheme_name: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme name.
@@ -41,7 +41,7 @@ class OpenIdConnect(SecurityBase):
),
] = None,
description: Annotated[
Optional[str],
str | None,
Doc(
"""
Security scheme description.
@@ -84,7 +84,7 @@ class OpenIdConnect(SecurityBase):
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")
if not authorization:
if self.auto_error:

View File

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

View File

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

View File

@@ -3,8 +3,7 @@ import warnings
from typing import (
TYPE_CHECKING,
Any,
Optional,
Union,
Literal,
)
import fastapi
@@ -17,7 +16,6 @@ from fastapi._compat import (
from fastapi.datastructures import DefaultPlaceholder, DefaultType
from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError
from pydantic.fields import FieldInfo
from typing_extensions import Literal
from ._compat import v2
@@ -25,7 +23,7 @@ if TYPE_CHECKING: # pragma: nocover
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:
return True
# 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(
name: str,
type_: Any,
default: Optional[Any] = Undefined,
field_info: Optional[FieldInfo] = None,
alias: Optional[str] = None,
default: Any | None = Undefined,
field_info: FieldInfo | None = None,
alias: str | None = None,
mode: Literal["validation", "serialization"] = "validation",
) -> ModelField:
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(
first_item: Union[DefaultPlaceholder, DefaultType],
*extra_items: Union[DefaultPlaceholder, DefaultType],
) -> Union[DefaultPlaceholder, DefaultType]:
first_item: DefaultPlaceholder | DefaultType,
*extra_items: DefaultPlaceholder | DefaultType,
) -> DefaultPlaceholder | DefaultType:
"""
Pass items or `DefaultPlaceholder`s by descending priority.

View File

@@ -3,18 +3,38 @@ from typing import Any
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:
metadata = context.config.metadata
# Get main version
version = metadata["version"]
# 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"
]["packages"].get(TIANGOLO_BUILD_PACKAGE)
if not config:
]["packages"]
if TIANGOLO_BUILD_PACKAGE not in all_configs_config:
return
config = all_configs_config[TIANGOLO_BUILD_PACKAGE]
project_config: dict[str, Any] = config["project"]
# Override main [project] configs with custom configs for this package
for key, value in project_config.items():
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

View File

@@ -9,7 +9,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code,
readme = "README.md"
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.9"
requires-python = ">=3.10"
authors = [
{ name = "Sebastián Ramírez", email = "tiangolo@gmail.com" },
]
@@ -33,7 +33,6 @@ classifiers = [
"Framework :: Pydantic :: 2",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
@@ -202,6 +201,29 @@ source-includes = [
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.project]
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]
plugins = ["pydantic.mypy"]

View File

@@ -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

2300
uv.lock generated
View File

File diff suppressed because it is too large Load Diff