committed by
GitHub
68 changed files with 1868 additions and 3485 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 |
|||
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] |
|||
|
|||
@ -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 = "[email protected]" }, |
|||
] |
|||
@ -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"] |
|||
|
|||
@ -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")] |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue