75 changed files with 2010 additions and 3536 deletions
@ -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`. 😎 |
||||
@ -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. |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -1,11 +1,12 @@ |
|||||
import types |
import types |
||||
|
from collections.abc import Callable |
||||
from enum import Enum |
from enum import Enum |
||||
from typing import Any, Callable, Optional, TypeVar, Union |
from typing import Any, TypeVar, Union |
||||
|
|
||||
from pydantic import BaseModel |
from pydantic import BaseModel |
||||
from pydantic.main import IncEx as IncEx |
from pydantic.main import IncEx as IncEx |
||||
|
|
||||
DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) |
DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) |
||||
UnionType = getattr(types, "UnionType", Union) |
UnionType = getattr(types, "UnionType", Union) |
||||
ModelNameMap = dict[Union[type[BaseModel], type[Enum]], str] |
ModelNameMap = dict[type[BaseModel] | type[Enum], str] |
||||
DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str] |
DependencyCacheKey = tuple[Callable[..., Any] | None, tuple[str, ...], str] |
||||
|
|||||
@ -9,7 +9,7 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, |
|||||
readme = "README.md" |
readme = "README.md" |
||||
license = "MIT" |
license = "MIT" |
||||
license-files = ["LICENSE"] |
license-files = ["LICENSE"] |
||||
requires-python = ">=3.9" |
requires-python = ">=3.10" |
||||
authors = [ |
authors = [ |
||||
{ name = "Sebastián Ramírez", email = "[email protected]" }, |
{ name = "Sebastián Ramírez", email = "[email protected]" }, |
||||
] |
] |
||||
@ -33,7 +33,6 @@ classifiers = [ |
|||||
"Framework :: Pydantic :: 2", |
"Framework :: Pydantic :: 2", |
||||
"Intended Audience :: Developers", |
"Intended Audience :: Developers", |
||||
"Programming Language :: Python :: 3 :: Only", |
"Programming Language :: Python :: 3 :: Only", |
||||
"Programming Language :: Python :: 3.9", |
|
||||
"Programming Language :: Python :: 3.10", |
"Programming Language :: Python :: 3.10", |
||||
"Programming Language :: Python :: 3.11", |
"Programming Language :: Python :: 3.11", |
||||
"Programming Language :: Python :: 3.12", |
"Programming Language :: Python :: 3.12", |
||||
@ -202,6 +201,29 @@ source-includes = [ |
|||||
|
|
||||
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] |
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.project] |
||||
name = "fastapi-slim" |
name = "fastapi-slim" |
||||
|
readme = "fastapi-slim/README.md" |
||||
|
dependencies = [ |
||||
|
"fastapi", |
||||
|
] |
||||
|
optional-dependencies = {} |
||||
|
scripts = {} |
||||
|
|
||||
|
[tool.tiangolo._internal-slim-build.packages.fastapi-slim.tool.pdm.build] |
||||
|
# excludes needs to explicitly exclude the top level python packages, |
||||
|
# otherwise PDM includes them by default |
||||
|
# A "*" glob pattern can't be used here because in PDM internals, the patterns are put |
||||
|
# in a set (unordered, order varies) and each excluded file is assigned one of the |
||||
|
# glob patterns that matches, as the set is unordered, the matched pattern could be "*" |
||||
|
# independent of the order here. And then the internal code would give it a lower score |
||||
|
# than the one for a default included file. |
||||
|
# By not using "*" and explicitly excluding the top level packages, they get a higher |
||||
|
# score than the default inclusion |
||||
|
excludes = ["fastapi", "tests", "pdm_build.py"] |
||||
|
# source-includes needs to explicitly define some value because PDM will check the |
||||
|
# truthy value of the list, and if empty, will include some defaults, including "tests", |
||||
|
# an empty string doesn't match anything, but makes the list truthy, so that PDM |
||||
|
# doesn't override it during the build. |
||||
|
source-includes = [""] |
||||
|
|
||||
[tool.mypy] |
[tool.mypy] |
||||
plugins = ["pydantic.mypy"] |
plugins = ["pydantic.mypy"] |
||||
|
|||||
@ -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 |
||||
@ -0,0 +1,46 @@ |
|||||
|
""" |
||||
|
Regression test: preserve order when using list[bytes] + File() |
||||
|
See https://github.com/fastapi/fastapi/discussions/14811 |
||||
|
Fixed in PR: https://github.com/fastapi/fastapi/pull/14884 |
||||
|
""" |
||||
|
|
||||
|
from typing import Annotated |
||||
|
|
||||
|
import anyio |
||||
|
import pytest |
||||
|
from fastapi import FastAPI, File |
||||
|
from fastapi.testclient import TestClient |
||||
|
from starlette.datastructures import UploadFile as StarletteUploadFile |
||||
|
|
||||
|
|
||||
|
def test_list_bytes_file_preserves_order( |
||||
|
monkeypatch: pytest.MonkeyPatch, |
||||
|
) -> None: |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.post("/upload") |
||||
|
async def upload(files: Annotated[list[bytes], File()]): |
||||
|
# return something that makes order obvious |
||||
|
return [b[0] for b in files] |
||||
|
|
||||
|
original_read = StarletteUploadFile.read |
||||
|
|
||||
|
async def patched_read(self: StarletteUploadFile, size: int = -1) -> bytes: |
||||
|
# Make the FIRST file slower *deterministically* |
||||
|
if self.filename == "slow.txt": |
||||
|
await anyio.sleep(0.05) |
||||
|
return await original_read(self, size) |
||||
|
|
||||
|
monkeypatch.setattr(StarletteUploadFile, "read", patched_read) |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
files = [ |
||||
|
("files", ("slow.txt", b"A" * 10, "text/plain")), |
||||
|
("files", ("fast.txt", b"B" * 10, "text/plain")), |
||||
|
] |
||||
|
r = client.post("/upload", files=files) |
||||
|
assert r.status_code == 200, r.text |
||||
|
|
||||
|
# Must preserve request order: slow first, fast second |
||||
|
assert r.json() == [ord("A"), ord("B")] |
||||
@ -0,0 +1,12 @@ |
|||||
|
import pytest |
||||
|
from fastapi import APIRouter |
||||
|
|
||||
|
|
||||
|
def test_router_circular_import(): |
||||
|
router = APIRouter() |
||||
|
|
||||
|
with pytest.raises( |
||||
|
AssertionError, |
||||
|
match="Cannot include the same APIRouter instance into itself. Did you mean to include a different router?", |
||||
|
): |
||||
|
router.include_router(router) |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue