From 982448661630e2f412f18a192eed29d0da4a8fb1 Mon Sep 17 00:00:00 2001 From: Lie Ryan Date: Wed, 3 Dec 2025 07:58:30 +1100 Subject: [PATCH 001/140] =?UTF-8?q?=E2=9C=A8=20Allow=20using=20dependables?= =?UTF-8?q?=20with=20`functools.partial()`=20(#9753)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Co-authored-by: Yurii Motov Co-authored-by: Sebastián Ramírez --- fastapi/dependencies/models.py | 7 +- tests/test_dependency_partial.py | 251 +++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 tests/test_dependency_partial.py diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 13486dd189..2a4d9a0102 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -1,7 +1,7 @@ import inspect import sys from dataclasses import dataclass, field -from functools import cached_property +from functools import cached_property, partial from typing import Any, Callable, List, Optional, Sequence, Union from fastapi._compat import ModelField @@ -79,7 +79,10 @@ class Dependant: def _unwrapped_call(self) -> Any: if self.call is None: return self.call # pragma: no cover - return inspect.unwrap(self.call) + unwrapped = inspect.unwrap(self.call) + if isinstance(unwrapped, partial): + unwrapped = unwrapped.func + return unwrapped @cached_property def is_gen_callable(self) -> bool: diff --git a/tests/test_dependency_partial.py b/tests/test_dependency_partial.py new file mode 100644 index 0000000000..61a76236f8 --- /dev/null +++ b/tests/test_dependency_partial.py @@ -0,0 +1,251 @@ +from functools import partial +from typing import AsyncGenerator, Generator + +import pytest +from fastapi import Depends, FastAPI +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +app = FastAPI() + + +def function_dependency(value: str) -> str: + return value + + +async def async_function_dependency(value: str) -> str: + return value + + +def gen_dependency(value: str) -> Generator[str, None, None]: + yield value + + +async def async_gen_dependency(value: str) -> AsyncGenerator[str, None]: + yield value + + +class CallableDependency: + def __call__(self, value: str) -> str: + return value + + +class CallableGenDependency: + def __call__(self, value: str) -> Generator[str, None, None]: + yield value + + +class AsyncCallableDependency: + async def __call__(self, value: str) -> str: + return value + + +class AsyncCallableGenDependency: + async def __call__(self, value: str) -> AsyncGenerator[str, None]: + yield value + + +class MethodsDependency: + def synchronous(self, value: str) -> str: + return value + + async def asynchronous(self, value: str) -> str: + return value + + def synchronous_gen(self, value: str) -> Generator[str, None, None]: + yield value + + async def asynchronous_gen(self, value: str) -> AsyncGenerator[str, None]: + yield value + + +callable_dependency = CallableDependency() +callable_gen_dependency = CallableGenDependency() +async_callable_dependency = AsyncCallableDependency() +async_callable_gen_dependency = AsyncCallableGenDependency() +methods_dependency = MethodsDependency() + + +@app.get("/partial-function-dependency") +async def get_partial_function_dependency( + value: Annotated[ + str, Depends(partial(function_dependency, "partial-function-dependency")) + ], +) -> str: + return value + + +@app.get("/partial-async-function-dependency") +async def get_partial_async_function_dependency( + value: Annotated[ + str, + Depends( + partial(async_function_dependency, "partial-async-function-dependency") + ), + ], +) -> str: + return value + + +@app.get("/partial-gen-dependency") +async def get_partial_gen_dependency( + value: Annotated[str, Depends(partial(gen_dependency, "partial-gen-dependency"))], +) -> str: + return value + + +@app.get("/partial-async-gen-dependency") +async def get_partial_async_gen_dependency( + value: Annotated[ + str, Depends(partial(async_gen_dependency, "partial-async-gen-dependency")) + ], +) -> str: + return value + + +@app.get("/partial-callable-dependency") +async def get_partial_callable_dependency( + value: Annotated[ + str, Depends(partial(callable_dependency, "partial-callable-dependency")) + ], +) -> str: + return value + + +@app.get("/partial-callable-gen-dependency") +async def get_partial_callable_gen_dependency( + value: Annotated[ + str, + Depends(partial(callable_gen_dependency, "partial-callable-gen-dependency")), + ], +) -> str: + return value + + +@app.get("/partial-async-callable-dependency") +async def get_partial_async_callable_dependency( + value: Annotated[ + str, + Depends( + partial(async_callable_dependency, "partial-async-callable-dependency") + ), + ], +) -> str: + return value + + +@app.get("/partial-async-callable-gen-dependency") +async def get_partial_async_callable_gen_dependency( + value: Annotated[ + str, + Depends( + partial( + async_callable_gen_dependency, "partial-async-callable-gen-dependency" + ) + ), + ], +) -> str: + return value + + +@app.get("/partial-synchronous-method-dependency") +async def get_partial_synchronous_method_dependency( + value: Annotated[ + str, + Depends( + partial( + methods_dependency.synchronous, "partial-synchronous-method-dependency" + ) + ), + ], +) -> str: + return value + + +@app.get("/partial-synchronous-method-gen-dependency") +async def get_partial_synchronous_method_gen_dependency( + value: Annotated[ + str, + Depends( + partial( + methods_dependency.synchronous_gen, + "partial-synchronous-method-gen-dependency", + ) + ), + ], +) -> str: + return value + + +@app.get("/partial-asynchronous-method-dependency") +async def get_partial_asynchronous_method_dependency( + value: Annotated[ + str, + Depends( + partial( + methods_dependency.asynchronous, + "partial-asynchronous-method-dependency", + ) + ), + ], +) -> str: + return value + + +@app.get("/partial-asynchronous-method-gen-dependency") +async def get_partial_asynchronous_method_gen_dependency( + value: Annotated[ + str, + Depends( + partial( + methods_dependency.asynchronous_gen, + "partial-asynchronous-method-gen-dependency", + ) + ), + ], +) -> str: + return value + + +client = TestClient(app) + + +@pytest.mark.parametrize( + "route,value", + [ + ("/partial-function-dependency", "partial-function-dependency"), + ( + "/partial-async-function-dependency", + "partial-async-function-dependency", + ), + ("/partial-gen-dependency", "partial-gen-dependency"), + ("/partial-async-gen-dependency", "partial-async-gen-dependency"), + ("/partial-callable-dependency", "partial-callable-dependency"), + ("/partial-callable-gen-dependency", "partial-callable-gen-dependency"), + ("/partial-async-callable-dependency", "partial-async-callable-dependency"), + ( + "/partial-async-callable-gen-dependency", + "partial-async-callable-gen-dependency", + ), + ( + "/partial-synchronous-method-dependency", + "partial-synchronous-method-dependency", + ), + ( + "/partial-synchronous-method-gen-dependency", + "partial-synchronous-method-gen-dependency", + ), + ( + "/partial-asynchronous-method-dependency", + "partial-asynchronous-method-dependency", + ), + ( + "/partial-asynchronous-method-gen-dependency", + "partial-asynchronous-method-gen-dependency", + ), + ], +) +def test_dependency_types_with_partial(route: str, value: str) -> None: + response = client.get(route) + assert response.status_code == 200, response.text + assert response.json() == value From 3c440c762a8b49d13f54fb586467e7870f9604b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 2 Dec 2025 20:58:53 +0000 Subject: [PATCH 002/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 98ebbcf9b2..529c801604 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Features +* ✨ Allow using dependables with `functools.partial()`. PR [#9753](https://github.com/fastapi/fastapi/pull/9753) by [@lieryan](https://github.com/lieryan). * ✨ Add support for wrapped functions (e.g. `@functools.wraps()`) used with forward references. PR [#5077](https://github.com/fastapi/fastapi/pull/5077) by [@lucaswiman](https://github.com/lucaswiman). * ✨ Handle wrapped dependencies. PR [#9555](https://github.com/fastapi/fastapi/pull/9555) by [@phy1729](https://github.com/phy1729). From c57ac7bdf3613798c94caceff562a41fa16d4a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 2 Dec 2025 22:06:25 +0100 Subject: [PATCH 003/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?3.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 529c801604..c397528aa7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.123.5 + ### Features * ✨ Allow using dependables with `functools.partial()`. PR [#9753](https://github.com/fastapi/fastapi/pull/9753) by [@lieryan](https://github.com/lieryan). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index b1d2dcecc8..80b2a99c17 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.4" +__version__ = "0.123.5" from starlette import status as status From bba4d4c95e0de8fc9c99f88c269909d37d64d494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 3 Dec 2025 23:29:28 -0800 Subject: [PATCH 004/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20support=20for=20fu?= =?UTF-8?q?nctools=20wraps=20and=20partial=20combined,=20for=20async=20and?= =?UTF-8?q?=20regular=20functions=20and=20classes=20in=20path=20operations?= =?UTF-8?q?=20and=20dependencies=20(#14448)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yurii Motov --- fastapi/dependencies/models.py | 100 ++++++-- fastapi/dependencies/utils.py | 6 +- tests/test_dependency_wrapped.py | 380 ++++++++++++++++++++++++++++++- 3 files changed, 458 insertions(+), 28 deletions(-) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 2a4d9a0102..9b545e4e5c 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -15,6 +15,19 @@ else: # pragma: no cover from asyncio import iscoroutinefunction +def _unwrapped_call(call: Optional[Callable[..., Any]]) -> Any: + if call is None: + return call # pragma: no cover + unwrapped = inspect.unwrap(_impartial(call)) + return unwrapped + + +def _impartial(func: Callable[..., Any]) -> Callable[..., Any]: + while isinstance(func, partial): + func = func.func + return func + + @dataclass class SecurityRequirement: security_scheme: SecurityBase @@ -75,37 +88,82 @@ class Dependant: return True return False - @cached_property - def _unwrapped_call(self) -> Any: - if self.call is None: - return self.call # pragma: no cover - unwrapped = inspect.unwrap(self.call) - if isinstance(unwrapped, partial): - unwrapped = unwrapped.func - return unwrapped - @cached_property def is_gen_callable(self) -> bool: - if inspect.isgeneratorfunction(self._unwrapped_call): + if self.call is None: + return False # pragma: no cover + if inspect.isgeneratorfunction( + _impartial(self.call) + ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)): return True - dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004 - return inspect.isgeneratorfunction(dunder_call) + dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 + if dunder_call is None: + return False # pragma: no cover + if inspect.isgeneratorfunction( + _impartial(dunder_call) + ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)): + return True + dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004 + if dunder_unwrapped_call is None: + return False # pragma: no cover + if inspect.isgeneratorfunction( + _impartial(dunder_unwrapped_call) + ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)): + return True + return False @cached_property def is_async_gen_callable(self) -> bool: - if inspect.isasyncgenfunction(self._unwrapped_call): + if self.call is None: + return False # pragma: no cover + if inspect.isasyncgenfunction( + _impartial(self.call) + ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)): return True - dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004 - return inspect.isasyncgenfunction(dunder_call) + dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 + if dunder_call is None: + return False # pragma: no cover + if inspect.isasyncgenfunction( + _impartial(dunder_call) + ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)): + return True + dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004 + if dunder_unwrapped_call is None: + return False # pragma: no cover + if inspect.isasyncgenfunction( + _impartial(dunder_unwrapped_call) + ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)): + return True + return False @cached_property def is_coroutine_callable(self) -> bool: - if inspect.isroutine(self._unwrapped_call): - return iscoroutinefunction(self._unwrapped_call) - if inspect.isclass(self._unwrapped_call): - return False - dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004 - return iscoroutinefunction(dunder_call) + if self.call is None: + return False # pragma: no cover + if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction( + _impartial(self.call) + ): + return True + if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction( + _unwrapped_call(self.call) + ): + return True + dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 + if dunder_call is None: + return False # pragma: no cover + if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction( + _unwrapped_call(dunder_call) + ): + return True + dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004 + if dunder_unwrapped_call is None: + return False # pragma: no cover + if iscoroutinefunction( + _impartial(dunder_unwrapped_call) + ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)): + return True + # if inspect.isclass(self.call): False, covered by default return + return False @cached_property def computed_scope(self) -> Union[str, None]: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 1a493a9fd0..91348c8ea1 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -548,10 +548,10 @@ async def _solve_generator( *, dependant: Dependant, stack: AsyncExitStack, sub_values: Dict[str, Any] ) -> Any: assert dependant.call - if dependant.is_gen_callable: - cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values)) - elif dependant.is_async_gen_callable: + if dependant.is_async_gen_callable: cm = asynccontextmanager(dependant.call)(**sub_values) + elif dependant.is_gen_callable: + cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values)) return await stack.enter_async_context(cm) diff --git a/tests/test_dependency_wrapped.py b/tests/test_dependency_wrapped.py index f581ccba4c..08356712d6 100644 --- a/tests/test_dependency_wrapped.py +++ b/tests/test_dependency_wrapped.py @@ -1,10 +1,18 @@ +import inspect +import sys from functools import wraps from typing import AsyncGenerator, Generator import pytest from fastapi import Depends, FastAPI +from fastapi.concurrency import iterate_in_threadpool, run_in_threadpool from fastapi.testclient import TestClient +if sys.version_info >= (3, 13): # pragma: no cover + from inspect import iscoroutinefunction +else: # pragma: no cover + from asyncio import iscoroutinefunction + def noop_wrap(func): @wraps(func) @@ -14,8 +22,163 @@ def noop_wrap(func): return wrapper +def noop_wrap_async(func): + if inspect.isgeneratorfunction(func): + + @wraps(func) + async def gen_wrapper(*args, **kwargs): + async for item in iterate_in_threadpool(func(*args, **kwargs)): + yield item + + return gen_wrapper + + elif inspect.isasyncgenfunction(func): + + @wraps(func) + async def async_gen_wrapper(*args, **kwargs): + async for item in func(*args, **kwargs): + yield item + + return async_gen_wrapper + + @wraps(func) + async def wrapper(*args, **kwargs): + if inspect.isroutine(func) and iscoroutinefunction(func): + return await func(*args, **kwargs) + if inspect.isclass(func): + return await run_in_threadpool(func, *args, **kwargs) + dunder_call = getattr(func, "__call__", None) # noqa: B004 + if iscoroutinefunction(dunder_call): + return await dunder_call(*args, **kwargs) + return await run_in_threadpool(func, *args, **kwargs) + + return wrapper + + +class ClassInstanceDep: + def __call__(self): + return True + + +class_instance_dep = ClassInstanceDep() +wrapped_class_instance_dep = noop_wrap(class_instance_dep) +wrapped_class_instance_dep_async_wrapper = noop_wrap_async(class_instance_dep) + + +class ClassInstanceGenDep: + def __call__(self): + yield True + + +class_instance_gen_dep = ClassInstanceGenDep() +wrapped_class_instance_gen_dep = noop_wrap(class_instance_gen_dep) + + +class ClassInstanceWrappedDep: + @noop_wrap + def __call__(self): + return True + + +class_instance_wrapped_dep = ClassInstanceWrappedDep() + + +class ClassInstanceWrappedAsyncDep: + @noop_wrap_async + def __call__(self): + return True + + +class_instance_wrapped_async_dep = ClassInstanceWrappedAsyncDep() + + +class ClassInstanceWrappedGenDep: + @noop_wrap + def __call__(self): + yield True + + +class_instance_wrapped_gen_dep = ClassInstanceWrappedGenDep() + + +class ClassInstanceWrappedAsyncGenDep: + @noop_wrap_async + def __call__(self): + yield True + + +class_instance_wrapped_async_gen_dep = ClassInstanceWrappedAsyncGenDep() + + +class ClassDep: + def __init__(self): + self.value = True + + +wrapped_class_dep = noop_wrap(ClassDep) +wrapped_class_dep_async_wrapper = noop_wrap_async(ClassDep) + + +class ClassInstanceAsyncDep: + async def __call__(self): + return True + + +class_instance_async_dep = ClassInstanceAsyncDep() +wrapped_class_instance_async_dep = noop_wrap(class_instance_async_dep) +wrapped_class_instance_async_dep_async_wrapper = noop_wrap_async( + class_instance_async_dep +) + + +class ClassInstanceAsyncGenDep: + async def __call__(self): + yield True + + +class_instance_async_gen_dep = ClassInstanceAsyncGenDep() +wrapped_class_instance_async_gen_dep = noop_wrap(class_instance_async_gen_dep) + + +class ClassInstanceAsyncWrappedDep: + @noop_wrap + async def __call__(self): + return True + + +class_instance_async_wrapped_dep = ClassInstanceAsyncWrappedDep() + + +class ClassInstanceAsyncWrappedAsyncDep: + @noop_wrap_async + async def __call__(self): + return True + + +class_instance_async_wrapped_async_dep = ClassInstanceAsyncWrappedAsyncDep() + + +class ClassInstanceAsyncWrappedGenDep: + @noop_wrap + async def __call__(self): + yield True + + +class_instance_async_wrapped_gen_dep = ClassInstanceAsyncWrappedGenDep() + + +class ClassInstanceAsyncWrappedGenAsyncDep: + @noop_wrap_async + async def __call__(self): + yield True + + +class_instance_async_wrapped_gen_async_dep = ClassInstanceAsyncWrappedGenAsyncDep() + app = FastAPI() +# Sync wrapper + @noop_wrap def wrapped_dependency() -> bool: @@ -59,16 +222,225 @@ async def get_async_wrapped_gen_dependency( return value +@app.get("/wrapped-class-instance-dependency/") +async def get_wrapped_class_instance_dependency( + value: bool = Depends(wrapped_class_instance_dep), +): + return value + + +@app.get("/wrapped-class-instance-async-dependency/") +async def get_wrapped_class_instance_async_dependency( + value: bool = Depends(wrapped_class_instance_async_dep), +): + return value + + +@app.get("/wrapped-class-instance-gen-dependency/") +async def get_wrapped_class_instance_gen_dependency( + value: bool = Depends(wrapped_class_instance_gen_dep), +): + return value + + +@app.get("/wrapped-class-instance-async-gen-dependency/") +async def get_wrapped_class_instance_async_gen_dependency( + value: bool = Depends(wrapped_class_instance_async_gen_dep), +): + return value + + +@app.get("/class-instance-wrapped-dependency/") +async def get_class_instance_wrapped_dependency( + value: bool = Depends(class_instance_wrapped_dep), +): + return value + + +@app.get("/class-instance-wrapped-async-dependency/") +async def get_class_instance_wrapped_async_dependency( + value: bool = Depends(class_instance_wrapped_async_dep), +): + return value + + +@app.get("/class-instance-async-wrapped-dependency/") +async def get_class_instance_async_wrapped_dependency( + value: bool = Depends(class_instance_async_wrapped_dep), +): + return value + + +@app.get("/class-instance-async-wrapped-async-dependency/") +async def get_class_instance_async_wrapped_async_dependency( + value: bool = Depends(class_instance_async_wrapped_async_dep), +): + return value + + +@app.get("/class-instance-wrapped-gen-dependency/") +async def get_class_instance_wrapped_gen_dependency( + value: bool = Depends(class_instance_wrapped_gen_dep), +): + return value + + +@app.get("/class-instance-wrapped-async-gen-dependency/") +async def get_class_instance_wrapped_async_gen_dependency( + value: bool = Depends(class_instance_wrapped_async_gen_dep), +): + return value + + +@app.get("/class-instance-async-wrapped-gen-dependency/") +async def get_class_instance_async_wrapped_gen_dependency( + value: bool = Depends(class_instance_async_wrapped_gen_dep), +): + return value + + +@app.get("/class-instance-async-wrapped-gen-async-dependency/") +async def get_class_instance_async_wrapped_gen_async_dependency( + value: bool = Depends(class_instance_async_wrapped_gen_async_dep), +): + return value + + +@app.get("/wrapped-class-dependency/") +async def get_wrapped_class_dependency(value: ClassDep = Depends(wrapped_class_dep)): + return value.value + + +@app.get("/wrapped-endpoint/") +@noop_wrap +def get_wrapped_endpoint(): + return True + + +@app.get("/async-wrapped-endpoint/") +@noop_wrap +async def get_async_wrapped_endpoint(): + return True + + +# Async wrapper + + +@noop_wrap_async +def wrapped_dependency_async_wrapper() -> bool: + return True + + +@noop_wrap_async +def wrapped_gen_dependency_async_wrapper() -> Generator[bool, None, None]: + yield True + + +@noop_wrap_async +async def async_wrapped_dependency_async_wrapper() -> bool: + return True + + +@noop_wrap_async +async def async_wrapped_gen_dependency_async_wrapper() -> AsyncGenerator[bool, None]: + yield True + + +@app.get("/wrapped-dependency-async-wrapper/") +async def get_wrapped_dependency_async_wrapper( + value: bool = Depends(wrapped_dependency_async_wrapper), +): + return value + + +@app.get("/wrapped-gen-dependency-async-wrapper/") +async def get_wrapped_gen_dependency_async_wrapper( + value: bool = Depends(wrapped_gen_dependency_async_wrapper), +): + return value + + +@app.get("/async-wrapped-dependency-async-wrapper/") +async def get_async_wrapped_dependency_async_wrapper( + value: bool = Depends(async_wrapped_dependency_async_wrapper), +): + return value + + +@app.get("/async-wrapped-gen-dependency-async-wrapper/") +async def get_async_wrapped_gen_dependency_async_wrapper( + value: bool = Depends(async_wrapped_gen_dependency_async_wrapper), +): + return value + + +@app.get("/wrapped-class-instance-dependency-async-wrapper/") +async def get_wrapped_class_instance_dependency_async_wrapper( + value: bool = Depends(wrapped_class_instance_dep_async_wrapper), +): + return value + + +@app.get("/wrapped-class-instance-async-dependency-async-wrapper/") +async def get_wrapped_class_instance_async_dependency_async_wrapper( + value: bool = Depends(wrapped_class_instance_async_dep_async_wrapper), +): + return value + + +@app.get("/wrapped-class-dependency-async-wrapper/") +async def get_wrapped_class_dependency_async_wrapper( + value: ClassDep = Depends(wrapped_class_dep_async_wrapper), +): + return value.value + + +@app.get("/wrapped-endpoint-async-wrapper/") +@noop_wrap_async +def get_wrapped_endpoint_async_wrapper(): + return True + + +@app.get("/async-wrapped-endpoint-async-wrapper/") +@noop_wrap_async +async def get_async_wrapped_endpoint_async_wrapper(): + return True + + client = TestClient(app) @pytest.mark.parametrize( "route", [ - "/wrapped-dependency", - "/wrapped-gen-dependency", - "/async-wrapped-dependency", - "/async-wrapped-gen-dependency", + "/wrapped-dependency/", + "/wrapped-gen-dependency/", + "/async-wrapped-dependency/", + "/async-wrapped-gen-dependency/", + "/wrapped-class-instance-dependency/", + "/wrapped-class-instance-async-dependency/", + "/wrapped-class-instance-gen-dependency/", + "/wrapped-class-instance-async-gen-dependency/", + "/class-instance-wrapped-dependency/", + "/class-instance-wrapped-async-dependency/", + "/class-instance-async-wrapped-dependency/", + "/class-instance-async-wrapped-async-dependency/", + "/class-instance-wrapped-gen-dependency/", + "/class-instance-wrapped-async-gen-dependency/", + "/class-instance-async-wrapped-gen-dependency/", + "/class-instance-async-wrapped-gen-async-dependency/", + "/wrapped-class-dependency/", + "/wrapped-endpoint/", + "/async-wrapped-endpoint/", + "/wrapped-dependency-async-wrapper/", + "/wrapped-gen-dependency-async-wrapper/", + "/async-wrapped-dependency-async-wrapper/", + "/async-wrapped-gen-dependency-async-wrapper/", + "/wrapped-class-instance-dependency-async-wrapper/", + "/wrapped-class-instance-async-dependency-async-wrapper/", + "/wrapped-class-dependency-async-wrapper/", + "/wrapped-endpoint-async-wrapper/", + "/async-wrapped-endpoint-async-wrapper/", ], ) def test_class_dependency(route): From 6c6b9d7a2b1df17fc24c8d2e4c5303fc334e75b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Dec 2025 07:29:53 +0000 Subject: [PATCH 005/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index c397528aa7..a687e89ab7 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies. PR [#14448](https://github.com/fastapi/fastapi/pull/14448) by [@tiangolo](https://github.com/tiangolo). + ## 0.123.5 ### Features From 811fa898752e7f5697733a2958ff79ad8dfb5276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 4 Dec 2025 08:33:11 +0100 Subject: [PATCH 006/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?3.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a687e89ab7..8c80851b47 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.123.6 + ### Fixes * 🐛 Fix support for functools wraps and partial combined, for async and regular functions and classes in path operations and dependencies. PR [#14448](https://github.com/fastapi/fastapi/pull/14448) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 80b2a99c17..32223231e5 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.5" +__version__ = "0.123.6" from starlette import status as status From 861598b4e30a7a7297aee803097f77431f799ea5 Mon Sep 17 00:00:00 2001 From: chaen Date: Thu, 4 Dec 2025 09:18:32 +0100 Subject: [PATCH 007/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20evaluating=20strin?= =?UTF-8?q?gified=20annotations=20in=20Python=203.10=20(#11355)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sofie Van Landeghem Co-authored-by: svlandeg Co-authored-by: Sebastián Ramírez --- fastapi/dependencies/utils.py | 11 +++++++-- tests/test_stringified_annotations_simple.py | 26 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/test_stringified_annotations_simple.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 91348c8ea1..1ff35f6483 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,5 +1,6 @@ import dataclasses import inspect +import sys from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass @@ -191,7 +192,10 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]: def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: - signature = inspect.signature(call) + if sys.version_info >= (3, 10): + signature = inspect.signature(call, eval_str=True) + else: + signature = inspect.signature(call) unwrapped = inspect.unwrap(call) globalns = getattr(unwrapped, "__globals__", {}) typed_params = [ @@ -217,7 +221,10 @@ def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: def get_typed_return_annotation(call: Callable[..., Any]) -> Any: - signature = inspect.signature(call) + if sys.version_info >= (3, 10): + signature = inspect.signature(call, eval_str=True) + else: + signature = inspect.signature(call) unwrapped = inspect.unwrap(call) annotation = signature.return_annotation diff --git a/tests/test_stringified_annotations_simple.py b/tests/test_stringified_annotations_simple.py new file mode 100644 index 0000000000..9bd6d09d63 --- /dev/null +++ b/tests/test_stringified_annotations_simple.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from fastapi import Depends, FastAPI, Request +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +from .utils import needs_py310 + + +class Dep: + def __call__(self, request: Request): + return "test" + + +@needs_py310 +def test_stringified_annotations(): + app = FastAPI() + + client = TestClient(app) + + @app.get("/test/") + def call(test: Annotated[str, Depends(Dep())]): + return {"test": test} + + response = client.get("/test") + assert response.status_code == 200 From 6c565482cfc729e2f93d6917731156c244b484a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Dec 2025 08:18:55 +0000 Subject: [PATCH 008/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 8c80851b47..592fa50891 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix evaluating stringified annotations in Python 3.10. PR [#11355](https://github.com/fastapi/fastapi/pull/11355) by [@chaen](https://github.com/chaen). + ## 0.123.6 ### Fixes From 603df6e36f59cc4a3e189f506b8f36d1e816c3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 4 Dec 2025 09:27:38 +0100 Subject: [PATCH 009/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?3.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 592fa50891..4747d5729e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.123.7 + ### Fixes * 🐛 Fix evaluating stringified annotations in Python 3.10. PR [#11355](https://github.com/fastapi/fastapi/pull/11355) by [@chaen](https://github.com/chaen). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 32223231e5..61d751e587 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.6" +__version__ = "0.123.7" from starlette import status as status From 0ec4bafca204c92dca903437e78514246fe14eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 4 Dec 2025 04:59:24 -0800 Subject: [PATCH 010/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20OpenAPI=20security?= =?UTF-8?q?=20scheme=20OAuth2=20scopes=20declaration,=20deduplicate=20secu?= =?UTF-8?q?rity=20schemes=20with=20different=20scopes=20(#14455)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/openapi/utils.py | 13 +- ...uthorization_code_bearer_scopes_openapi.py | 131 ++++++++++++++++++ 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index dbc93d2892..e7e6da2f76 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -79,7 +79,8 @@ def get_openapi_security_definitions( flat_dependant: Dependant, ) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: security_definitions = {} - operation_security = [] + # Use a dict to merge scopes for same security scheme + operation_security_dict: Dict[str, List[str]] = {} for security_requirement in flat_dependant.security_requirements: security_definition = jsonable_encoder( security_requirement.security_scheme.model, @@ -88,7 +89,15 @@ def get_openapi_security_definitions( ) security_name = security_requirement.security_scheme.scheme_name security_definitions[security_name] = security_definition - operation_security.append({security_name: security_requirement.scopes}) + # Merge scopes for the same security scheme + if security_name not in operation_security_dict: + operation_security_dict[security_name] = [] + for scope in security_requirement.scopes or []: + if scope not in operation_security_dict[security_name]: + operation_security_dict[security_name].append(scope) + operation_security = [ + {name: scopes} for name, scopes in operation_security_dict.items() + ] return security_definitions, operation_security diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py new file mode 100644 index 0000000000..644df8de6c --- /dev/null +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py @@ -0,0 +1,131 @@ +# Ref: https://github.com/fastapi/fastapi/issues/14454 + +from typing import Optional + +from fastapi import APIRouter, FastAPI, Security +from fastapi.security import OAuth2AuthorizationCodeBearer +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + +oauth2_scheme = OAuth2AuthorizationCodeBearer( + authorizationUrl="authorize", + tokenUrl="token", + auto_error=True, + scopes={"read": "Read access", "write": "Write access"}, +) + +app = FastAPI(dependencies=[Security(oauth2_scheme)]) + + +@app.get("/") +async def root(): + return {"message": "Hello World"} + + +router = APIRouter(dependencies=[Security(oauth2_scheme, scopes=["read"])]) + + +@router.get("/items/") +async def read_items(token: Optional[str] = Security(oauth2_scheme)): + return {"token": token} + + +@router.post("/items/") +async def create_item( + token: Optional[str] = Security(oauth2_scheme, scopes=["read", "write"]), +): + return {"token": token} + + +app.include_router(router) + +client = TestClient(app) + + +def test_root(): + response = client.get("/", headers={"Authorization": "Bearer testtoken"}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Hello World"} + + +def test_read_token(): + response = client.get("/items/", headers={"Authorization": "Bearer testtoken"}) + assert response.status_code == 200, response.text + assert response.json() == {"token": "testtoken"} + + +def test_create_token(): + response = client.post("/items/", headers={"Authorization": "Bearer testtoken"}) + assert response.status_code == 200, response.text + assert response.json() == {"token": "testtoken"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Root", + "operationId": "root__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [{"OAuth2AuthorizationCodeBearer": []}], + } + }, + "/items/": { + "get": { + "summary": "Read Items", + "operationId": "read_items_items__get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [ + {"OAuth2AuthorizationCodeBearer": ["read"]}, + ], + }, + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [ + {"OAuth2AuthorizationCodeBearer": ["read", "write"]}, + ], + }, + }, + }, + "components": { + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "scopes": { + "read": "Read access", + "write": "Write access", + }, + "authorizationUrl": "authorize", + "tokenUrl": "token", + } + }, + } + } + }, + } + ) From e248a4d22b6de0630771895cee309e32e64bfbd4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Dec 2025 12:59:45 +0000 Subject: [PATCH 011/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4747d5729e..86b21e8f12 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix OpenAPI security scheme OAuth2 scopes declaration, deduplicate security schemes with different scopes. PR [#14455](https://github.com/fastapi/fastapi/pull/14455) by [@tiangolo](https://github.com/tiangolo). + ## 0.123.7 ### Fixes From eb1d50479ba0ac873e4ffa08a824b1d822ca3a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 4 Dec 2025 14:01:00 +0100 Subject: [PATCH 012/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?3.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 86b21e8f12..50eaef5145 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.123.8 + ### Fixes * 🐛 Fix OpenAPI security scheme OAuth2 scopes declaration, deduplicate security schemes with different scopes. PR [#14455](https://github.com/fastapi/fastapi/pull/14455) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 61d751e587..b5f5300f0d 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.7" +__version__ = "0.123.8" from starlette import status as status From 0b5fa563cdfb887e4145d8419ae91b6a40905349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 4 Dec 2025 14:22:01 -0800 Subject: [PATCH 013/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20OAuth2=20scopes=20?= =?UTF-8?q?in=20OpenAPI=20in=20extra=20corner=20cases,=20parent=20dependen?= =?UTF-8?q?cy=20with=20scopes,=20sub-dependency=20security=20scheme=20with?= =?UTF-8?q?out=20scopes=20(#14459)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/models.py | 30 +++++-- fastapi/dependencies/utils.py | 33 +++++--- fastapi/openapi/utils.py | 8 +- ...uthorization_code_bearer_scopes_openapi.py | 73 ++++++++++++++++- ...ation_code_bearer_scopes_openapi_simple.py | 79 +++++++++++++++++++ 5 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 9b545e4e5c..af168a177a 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -2,7 +2,7 @@ import inspect import sys from dataclasses import dataclass, field from functools import cached_property, partial -from typing import Any, Callable, List, Optional, Sequence, Union +from typing import Any, Callable, List, Optional, Union from fastapi._compat import ModelField from fastapi.security.base import SecurityBase @@ -28,12 +28,6 @@ def _impartial(func: Callable[..., Any]) -> Callable[..., Any]: return func -@dataclass -class SecurityRequirement: - security_scheme: SecurityBase - scopes: Optional[Sequence[str]] = None - - @dataclass class Dependant: path_params: List[ModelField] = field(default_factory=list) @@ -42,7 +36,6 @@ 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) - security_requirements: List[SecurityRequirement] = field(default_factory=list) name: Optional[str] = None call: Optional[Callable[..., Any]] = None request_param_name: Optional[str] = None @@ -83,11 +76,32 @@ class Dependant: return True if self.security_scopes_param_name is not None: return True + if self._is_security_scheme: + return True for sub_dep in self.dependencies: if sub_dep._uses_scopes: return True return False + @cached_property + def _is_security_scheme(self) -> bool: + if self.call is None: + return False # pragma: no cover + unwrapped = _unwrapped_call(self.call) + return isinstance(unwrapped, SecurityBase) + + # Mainly to get the type of SecurityBase, but it's the same self.call + @cached_property + def _security_scheme(self) -> SecurityBase: + unwrapped = _unwrapped_call(self.call) + assert isinstance(unwrapped, SecurityBase) + return unwrapped + + @cached_property + def _security_dependencies(self) -> List["Dependant"]: + security_deps = [dep for dep in self.dependencies if dep._is_security_scheme] + return security_deps + @cached_property def is_gen_callable(self) -> bool: if self.call is None: diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 1ff35f6483..23bca6f2a1 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -55,10 +55,9 @@ from fastapi.concurrency import ( asynccontextmanager, contextmanager_in_threadpool, ) -from fastapi.dependencies.models import Dependant, SecurityRequirement +from fastapi.dependencies.models import Dependant from fastapi.exceptions import DependencyScopeError from fastapi.logger import logger -from fastapi.security.base import SecurityBase from fastapi.security.oauth2 import SecurityScopes from fastapi.types import DependencyCacheKey from fastapi.utils import create_model_field, get_path_param_names @@ -142,10 +141,14 @@ def get_flat_dependant( *, skip_repeats: bool = False, visited: Optional[List[DependencyCacheKey]] = None, + parent_oauth_scopes: Optional[List[str]] = None, ) -> Dependant: if visited is None: visited = [] visited.append(dependant.cache_key) + use_parent_oauth_scopes = (parent_oauth_scopes or []) + ( + dependant.oauth_scopes or [] + ) flat_dependant = Dependant( path_params=dependant.path_params.copy(), @@ -153,22 +156,37 @@ def get_flat_dependant( header_params=dependant.header_params.copy(), cookie_params=dependant.cookie_params.copy(), body_params=dependant.body_params.copy(), - security_requirements=dependant.security_requirements.copy(), + name=dependant.name, + call=dependant.call, + request_param_name=dependant.request_param_name, + websocket_param_name=dependant.websocket_param_name, + http_connection_param_name=dependant.http_connection_param_name, + response_param_name=dependant.response_param_name, + background_tasks_param_name=dependant.background_tasks_param_name, + security_scopes_param_name=dependant.security_scopes_param_name, + own_oauth_scopes=dependant.own_oauth_scopes, + parent_oauth_scopes=use_parent_oauth_scopes, use_cache=dependant.use_cache, path=dependant.path, + scope=dependant.scope, ) for sub_dependant in dependant.dependencies: if skip_repeats and sub_dependant.cache_key in visited: continue flat_sub = get_flat_dependant( - sub_dependant, skip_repeats=skip_repeats, visited=visited + sub_dependant, + skip_repeats=skip_repeats, + visited=visited, + parent_oauth_scopes=flat_dependant.oauth_scopes, ) + flat_dependant.dependencies.append(flat_sub) flat_dependant.path_params.extend(flat_sub.path_params) flat_dependant.query_params.extend(flat_sub.query_params) flat_dependant.header_params.extend(flat_sub.header_params) flat_dependant.cookie_params.extend(flat_sub.cookie_params) flat_dependant.body_params.extend(flat_sub.body_params) - flat_dependant.security_requirements.extend(flat_sub.security_requirements) + flat_dependant.dependencies.extend(flat_sub.dependencies) + return flat_dependant @@ -258,11 +276,6 @@ def get_dependant( path_param_names = get_path_param_names(path) endpoint_signature = get_typed_signature(call) signature_params = endpoint_signature.parameters - if isinstance(call, SecurityBase): - security_requirement = SecurityRequirement( - security_scheme=call, scopes=current_scopes - ) - dependant.security_requirements.append(security_requirement) for param_name, param in signature_params.items(): is_path_param = param_name in path_param_names param_details = analyze_param( diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index e7e6da2f76..06c14861a3 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -81,18 +81,18 @@ def get_openapi_security_definitions( security_definitions = {} # Use a dict to merge scopes for same security scheme operation_security_dict: Dict[str, List[str]] = {} - for security_requirement in flat_dependant.security_requirements: + for security_dependency in flat_dependant._security_dependencies: security_definition = jsonable_encoder( - security_requirement.security_scheme.model, + security_dependency._security_scheme.model, by_alias=True, exclude_none=True, ) - security_name = security_requirement.security_scheme.scheme_name + security_name = security_dependency._security_scheme.scheme_name security_definitions[security_name] = security_definition # Merge scopes for the same security scheme if security_name not in operation_security_dict: operation_security_dict[security_name] = [] - for scope in security_requirement.scopes or []: + for scope in security_dependency.oauth_scopes or []: if scope not in operation_security_dict[security_name]: operation_security_dict[security_name].append(scope) operation_security = [ diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py index 644df8de6c..d41f1dc1f0 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py @@ -2,10 +2,11 @@ from typing import Optional -from fastapi import APIRouter, FastAPI, Security +from fastapi import APIRouter, Depends, FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient from inline_snapshot import snapshot +from typing_extensions import Annotated oauth2_scheme = OAuth2AuthorizationCodeBearer( authorizationUrl="authorize", @@ -14,7 +15,12 @@ oauth2_scheme = OAuth2AuthorizationCodeBearer( scopes={"read": "Read access", "write": "Write access"}, ) -app = FastAPI(dependencies=[Security(oauth2_scheme)]) + +async def get_token(token: Annotated[str, Depends(oauth2_scheme)]) -> str: + return token + + +app = FastAPI(dependencies=[Depends(get_token)]) @app.get("/") @@ -22,11 +28,26 @@ async def root(): return {"message": "Hello World"} +@app.get( + "/with-oauth2-scheme", + dependencies=[Security(oauth2_scheme, scopes=["read", "write"])], +) +async def read_with_oauth2_scheme(): + return {"message": "Admin Access"} + + +@app.get( + "/with-get-token", dependencies=[Security(get_token, scopes=["read", "write"])] +) +async def read_with_get_token(): + return {"message": "Admin Access"} + + router = APIRouter(dependencies=[Security(oauth2_scheme, scopes=["read"])]) @router.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: Optional[str] = Depends(oauth2_scheme)): return {"token": token} @@ -48,6 +69,22 @@ def test_root(): assert response.json() == {"message": "Hello World"} +def test_read_with_oauth2_scheme(): + response = client.get( + "/with-oauth2-scheme", headers={"Authorization": "Bearer testtoken"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Admin Access"} + + +def test_read_with_get_token(): + response = client.get( + "/with-get-token", headers={"Authorization": "Bearer testtoken"} + ) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Admin Access"} + + def test_read_token(): response = client.get("/items/", headers={"Authorization": "Bearer testtoken"}) assert response.status_code == 200, response.text @@ -81,6 +118,36 @@ def test_openapi_schema(): "security": [{"OAuth2AuthorizationCodeBearer": []}], } }, + "/with-oauth2-scheme": { + "get": { + "summary": "Read With Oauth2 Scheme", + "operationId": "read_with_oauth2_scheme_with_oauth2_scheme_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [ + {"OAuth2AuthorizationCodeBearer": ["read", "write"]} + ], + } + }, + "/with-get-token": { + "get": { + "summary": "Read With Get Token", + "operationId": "read_with_get_token_with_get_token_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [ + {"OAuth2AuthorizationCodeBearer": ["read", "write"]} + ], + } + }, "/items/": { "get": { "summary": "Read Items", diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py new file mode 100644 index 0000000000..ff866d4fc9 --- /dev/null +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py @@ -0,0 +1,79 @@ +# Ref: https://github.com/fastapi/fastapi/issues/14454 + +from fastapi import Depends, FastAPI, Security +from fastapi.security import OAuth2AuthorizationCodeBearer +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from typing_extensions import Annotated + +oauth2_scheme = OAuth2AuthorizationCodeBearer( + authorizationUrl="api/oauth/authorize", + tokenUrl="/api/oauth/token", + scopes={"read": "Read access", "write": "Write access"}, +) + + +async def get_token(token: Annotated[str, Depends(oauth2_scheme)]) -> str: + return token + + +app = FastAPI(dependencies=[Depends(get_token)]) + + +@app.get("/admin", dependencies=[Security(get_token, scopes=["read", "write"])]) +async def read_admin(): + return {"message": "Admin Access"} + + +client = TestClient(app) + + +def test_read_admin(): + response = client.get("/admin", headers={"Authorization": "Bearer faketoken"}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "Admin Access"} + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/admin": { + "get": { + "summary": "Read Admin", + "operationId": "read_admin_admin_get", + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + } + }, + "security": [ + {"OAuth2AuthorizationCodeBearer": ["read", "write"]} + ], + } + } + }, + "components": { + "securitySchemes": { + "OAuth2AuthorizationCodeBearer": { + "type": "oauth2", + "flows": { + "authorizationCode": { + "scopes": { + "read": "Read access", + "write": "Write access", + }, + "authorizationUrl": "api/oauth/authorize", + "tokenUrl": "/api/oauth/token", + } + }, + } + } + }, + } + ) From 188d63101115ca40f274ed1e0b7093edf4ce696d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 4 Dec 2025 22:22:25 +0000 Subject: [PATCH 014/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 50eaef5145..9323eb7586 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix OAuth2 scopes in OpenAPI in extra corner cases, parent dependency with scopes, sub-dependency security scheme without scopes. PR [#14459](https://github.com/fastapi/fastapi/pull/14459) by [@tiangolo](https://github.com/tiangolo). + ## 0.123.8 ### Fixes From f0dd1046a688935ffd23666b3d4164b838a4d8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 4 Dec 2025 23:23:21 +0100 Subject: [PATCH 015/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?3.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9323eb7586..ed39da1116 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.123.9 + ### Fixes * 🐛 Fix OAuth2 scopes in OpenAPI in extra corner cases, parent dependency with scopes, sub-dependency security scheme without scopes. PR [#14459](https://github.com/fastapi/fastapi/pull/14459) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index b5f5300f0d..dc5467b0f6 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.8" +__version__ = "0.123.9" from starlette import status as status From 812a1926f06391b22b081fdb11fe7528e3b91293 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 5 Dec 2025 21:19:30 +0100 Subject: [PATCH 016/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20`separate=5Finput?= =?UTF-8?q?=5Foutput=5Fschemas=3DFalse`=20with=20`computed=5Ffield`=20(#14?= =?UTF-8?q?453)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/v2.py | 31 ++-- ...t_openapi_separate_input_output_schemas.py | 151 ++++++++++++++++++ 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 0faa7d5a8d..acd23d8465 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -171,6 +171,13 @@ def _get_model_config(model: BaseModel) -> Any: return model.model_config +def _has_computed_fields(field: ModelField) -> bool: + computed_fields = field._type_adapter.core_schema.get("schema", {}).get( + "computed_fields", [] + ) + return len(computed_fields) > 0 + + def get_schema_from_model_field( *, field: ModelField, @@ -180,12 +187,9 @@ def get_schema_from_model_field( ], separate_input_output_schemas: bool = True, ) -> Dict[str, Any]: - computed_fields = field._type_adapter.core_schema.get("schema", {}).get( - "computed_fields", [] - ) override_mode: Union[Literal["validation"], None] = ( None - if (separate_input_output_schemas or len(computed_fields) > 0) + if (separate_input_output_schemas or _has_computed_fields(field)) else "validation" ) # This expects that GenerateJsonSchema was already used to generate the definitions @@ -208,15 +212,7 @@ def get_definitions( Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], Dict[str, Dict[str, Any]], ]: - has_computed_fields: bool = any( - field._type_adapter.core_schema.get("schema", {}).get("computed_fields", []) - for field in fields - ) - schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) - override_mode: Union[Literal["validation"], None] = ( - None if (separate_input_output_schemas or has_computed_fields) else "validation" - ) validation_fields = [field for field in fields if field.mode == "validation"] serialization_fields = [field for field in fields if field.mode == "serialization"] flat_validation_models = get_flat_models_from_fields( @@ -246,9 +242,16 @@ def get_definitions( unique_flat_model_fields = { f for f in flat_model_fields if f.type_ not in input_types } - inputs = [ - (field, override_mode or field.mode, field._type_adapter.core_schema) + ( + field, + ( + field.mode + if (separate_input_output_schemas or _has_computed_fields(field)) + else "validation" + ), + field._type_adapter.core_schema, + ) for field in list(fields) + list(unique_flat_model_fields) ] field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs) diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index fa73620eae..c9a05418bf 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -24,6 +24,18 @@ class Item(BaseModel): model_config = {"json_schema_serialization_defaults_required": True} +if PYDANTIC_V2: + from pydantic import computed_field + + class WithComputedField(BaseModel): + name: str + + @computed_field + @property + def computed_field(self) -> str: + return f"computed {self.name}" + + def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) @@ -46,6 +58,14 @@ def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: Item(name="Plumbus"), ] + if PYDANTIC_V2: + + @app.post("/with-computed-field/") + def create_with_computed_field( + with_computed_field: WithComputedField, + ) -> WithComputedField: + return with_computed_field + client = TestClient(app) return client @@ -131,6 +151,23 @@ def test_read_items(): ) +@needs_pydanticv2 +def test_with_computed_field(): + client = get_app_client() + client_no = get_app_client(separate_input_output_schemas=False) + response = client.post("/with-computed-field/", json={"name": "example"}) + response2 = client_no.post("/with-computed-field/", json={"name": "example"}) + assert response.status_code == response2.status_code == 200, response.text + assert ( + response.json() + == response2.json() + == { + "name": "example", + "computed_field": "computed example", + } + ) + + @needs_pydanticv2 def test_openapi_schema(): client = get_app_client() @@ -245,6 +282,44 @@ def test_openapi_schema(): }, } }, + "/with-computed-field/": { + "post": { + "summary": "Create With Computed Field", + "operationId": "create_with_computed_field_with_computed_field__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithComputedField-Input" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithComputedField-Output" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, }, "components": { "schemas": { @@ -333,6 +408,25 @@ def test_openapi_schema(): "required": ["subname", "sub_description", "tags"], "title": "SubItem", }, + "WithComputedField-Input": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "WithComputedField", + }, + "WithComputedField-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "computed_field": { + "type": "string", + "title": "Computed Field", + "readOnly": True, + }, + }, + "type": "object", + "required": ["name", "computed_field"], + "title": "WithComputedField", + }, "ValidationError": { "properties": { "loc": { @@ -458,6 +552,44 @@ def test_openapi_schema_no_separate(): }, } }, + "/with-computed-field/": { + "post": { + "summary": "Create With Computed Field", + "operationId": "create_with_computed_field_with_computed_field__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithComputedField-Input" + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WithComputedField-Output" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + }, + }, }, "components": { "schemas": { @@ -508,6 +640,25 @@ def test_openapi_schema_no_separate(): "required": ["subname"], "title": "SubItem", }, + "WithComputedField-Input": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "WithComputedField", + }, + "WithComputedField-Output": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "computed_field": { + "type": "string", + "title": "Computed Field", + "readOnly": True, + }, + }, + "type": "object", + "required": ["name", "computed_field"], + "title": "WithComputedField", + }, "ValidationError": { "properties": { "loc": { From 516169428d2fa189d34318ebc469a082c49c1189 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Dec 2025 20:19:54 +0000 Subject: [PATCH 017/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ed39da1116..aa8a858433 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix `separate_input_output_schemas=False` with `computed_field`. PR [#14453](https://github.com/fastapi/fastapi/pull/14453) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.123.9 ### Fixes From da0ffab0b260475499294d3dc767409d7bca5c34 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 5 Dec 2025 22:21:05 +0100 Subject: [PATCH 018/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20using=20class=20(n?= =?UTF-8?q?ot=20instance)=20dependency=20that=20has=20`=5F=5Fcall=5F=5F`?= =?UTF-8?q?=20method=20(#14458)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- fastapi/dependencies/models.py | 7 ++++++- tests/test_dependency_class.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index af168a177a..6c4bf18b37 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -110,6 +110,8 @@ class Dependant: _impartial(self.call) ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)): return True + if inspect.isclass(_unwrapped_call(self.call)): + return False dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 if dunder_call is None: return False # pragma: no cover @@ -134,6 +136,8 @@ class Dependant: _impartial(self.call) ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)): return True + if inspect.isclass(_unwrapped_call(self.call)): + return False dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 if dunder_call is None: return False # pragma: no cover @@ -162,6 +166,8 @@ class Dependant: _unwrapped_call(self.call) ): return True + if inspect.isclass(_unwrapped_call(self.call)): + return False dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 if dunder_call is None: return False # pragma: no cover @@ -176,7 +182,6 @@ class Dependant: _impartial(dunder_unwrapped_call) ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)): return True - # if inspect.isclass(self.call): False, covered by default return return False @cached_property diff --git a/tests/test_dependency_class.py b/tests/test_dependency_class.py index 0233492e6c..75241b467a 100644 --- a/tests/test_dependency_class.py +++ b/tests/test_dependency_class.py @@ -48,6 +48,34 @@ async_callable_gen_dependency = AsyncCallableGenDependency() methods_dependency = MethodsDependency() +@app.get("/callable-dependency-class") +async def get_callable_dependency_class( + value: str, instance: CallableDependency = Depends() +): + return instance(value) + + +@app.get("/callable-gen-dependency-class") +async def get_callable_gen_dependency_class( + value: str, instance: CallableGenDependency = Depends() +): + return next(instance(value)) + + +@app.get("/async-callable-dependency-class") +async def get_async_callable_dependency_class( + value: str, instance: AsyncCallableDependency = Depends() +): + return await instance(value) + + +@app.get("/async-callable-gen-dependency-class") +async def get_async_callable_gen_dependency_class( + value: str, instance: AsyncCallableGenDependency = Depends() +): + return await instance(value).__anext__() + + @app.get("/callable-dependency") async def get_callable_dependency(value: str = Depends(callable_dependency)): return value @@ -114,6 +142,10 @@ client = TestClient(app) ("/synchronous-method-gen-dependency", "synchronous-method-gen-dependency"), ("/asynchronous-method-dependency", "asynchronous-method-dependency"), ("/asynchronous-method-gen-dependency", "asynchronous-method-gen-dependency"), + ("/callable-dependency-class", "callable-dependency-class"), + ("/callable-gen-dependency-class", "callable-gen-dependency-class"), + ("/async-callable-dependency-class", "async-callable-dependency-class"), + ("/async-callable-gen-dependency-class", "async-callable-gen-dependency-class"), ], ) def test_class_dependency(route, value): From e7d7038dfa35fc923f20fd11a969d2e65e1b9df1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Dec 2025 21:21:29 +0000 Subject: [PATCH 019/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index aa8a858433..ce620b1328 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Fixes +* 🐛 Fix using class (not instance) dependency that has `__call__` method. PR [#14458](https://github.com/fastapi/fastapi/pull/14458) by [@YuriiMotov](https://github.com/YuriiMotov). * 🐛 Fix `separate_input_output_schemas=False` with `computed_field`. PR [#14453](https://github.com/fastapi/fastapi/pull/14453) by [@YuriiMotov](https://github.com/YuriiMotov). ## 0.123.9 From 08b09e5236e315b6f10265ed229f130d4befb4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 5 Dec 2025 22:26:36 +0100 Subject: [PATCH 020/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?3.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ce620b1328..d27c473831 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.123.10 + ### Fixes * 🐛 Fix using class (not instance) dependency that has `__call__` method. PR [#14458](https://github.com/fastapi/fastapi/pull/14458) by [@YuriiMotov](https://github.com/YuriiMotov). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index dc5467b0f6..2396c501d6 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.9" +__version__ = "0.123.10" from starlette import status as status From e1117f75505bbdb2d42321a009dbf26c9c2b8b6d Mon Sep 17 00:00:00 2001 From: Savannah Ostrowski Date: Sat, 6 Dec 2025 04:21:57 -0800 Subject: [PATCH 021/140] =?UTF-8?q?=F0=9F=9A=B8=20=20Improve=20tracebacks?= =?UTF-8?q?=20by=20adding=20endpoint=20metadata=20(#14306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Sebastián Ramírez --- fastapi/exceptions.py | 75 +++++++++-- fastapi/routing.py | 62 ++++++++- tests/test_validation_error_context.py | 168 +++++++++++++++++++++++++ 3 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 tests/test_validation_error_context.py diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index 0620428be7..a46e823506 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Sequence, Type, Union +from typing import Any, Dict, Optional, Sequence, Type, TypedDict, Union from annotated_doc import Doc from pydantic import BaseModel, create_model @@ -7,6 +7,13 @@ from starlette.exceptions import WebSocketException as StarletteWebSocketExcepti from typing_extensions import Annotated +class EndpointContext(TypedDict, total=False): + function: str + path: str + file: str + line: int + + class HTTPException(StarletteHTTPException): """ An HTTP exception you can raise in your own code to show errors to the client. @@ -155,30 +162,72 @@ class DependencyScopeError(FastAPIError): class ValidationException(Exception): - def __init__(self, errors: Sequence[Any]) -> None: + def __init__( + self, + errors: Sequence[Any], + *, + endpoint_ctx: Optional[EndpointContext] = None, + ) -> None: self._errors = errors + self.endpoint_ctx = endpoint_ctx + + ctx = endpoint_ctx or {} + self.endpoint_function = ctx.get("function") + self.endpoint_path = ctx.get("path") + self.endpoint_file = ctx.get("file") + self.endpoint_line = ctx.get("line") def errors(self) -> Sequence[Any]: return self._errors + def _format_endpoint_context(self) -> str: + if not (self.endpoint_file and self.endpoint_line and self.endpoint_function): + if self.endpoint_path: + return f"\n Endpoint: {self.endpoint_path}" + return "" + + context = f'\n File "{self.endpoint_file}", line {self.endpoint_line}, in {self.endpoint_function}' + if self.endpoint_path: + context += f"\n {self.endpoint_path}" + return context + + def __str__(self) -> str: + message = f"{len(self._errors)} validation error{'s' if len(self._errors) != 1 else ''}:\n" + for err in self._errors: + message += f" {err}\n" + message += self._format_endpoint_context() + return message.rstrip() + class RequestValidationError(ValidationException): - def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None: - super().__init__(errors) + def __init__( + self, + errors: Sequence[Any], + *, + body: Any = None, + endpoint_ctx: Optional[EndpointContext] = None, + ) -> None: + super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body class WebSocketRequestValidationError(ValidationException): - pass + def __init__( + self, + errors: Sequence[Any], + *, + endpoint_ctx: Optional[EndpointContext] = None, + ) -> None: + super().__init__(errors, endpoint_ctx=endpoint_ctx) class ResponseValidationError(ValidationException): - def __init__(self, errors: Sequence[Any], *, body: Any = None) -> None: - super().__init__(errors) + def __init__( + self, + errors: Sequence[Any], + *, + body: Any = None, + endpoint_ctx: Optional[EndpointContext] = None, + ) -> None: + super().__init__(errors, endpoint_ctx=endpoint_ctx) self.body = body - - def __str__(self) -> str: - message = f"{len(self._errors)} validation errors:\n" - for err in self._errors: - message += f" {err}\n" - return message diff --git a/fastapi/routing.py b/fastapi/routing.py index c10175b161..9be2b44bc1 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -46,6 +46,7 @@ from fastapi.dependencies.utils import ( ) from fastapi.encoders import jsonable_encoder from fastapi.exceptions import ( + EndpointContext, FastAPIError, RequestValidationError, ResponseValidationError, @@ -212,6 +213,33 @@ def _merge_lifespan_context( return merged_lifespan # type: ignore[return-value] +# Cache for endpoint context to avoid re-extracting on every request +_endpoint_context_cache: Dict[int, EndpointContext] = {} + + +def _extract_endpoint_context(func: Any) -> EndpointContext: + """Extract endpoint context with caching to avoid repeated file I/O.""" + func_id = id(func) + + if func_id in _endpoint_context_cache: + return _endpoint_context_cache[func_id] + + try: + ctx: EndpointContext = {} + + if (source_file := inspect.getsourcefile(func)) is not None: + ctx["file"] = source_file + if (line_number := inspect.getsourcelines(func)[1]) is not None: + ctx["line"] = line_number + if (func_name := getattr(func, "__name__", None)) is not None: + ctx["function"] = func_name + except Exception: + ctx = EndpointContext() + + _endpoint_context_cache[func_id] = ctx + return ctx + + async def serialize_response( *, field: Optional[ModelField] = None, @@ -223,6 +251,7 @@ async def serialize_response( exclude_defaults: bool = False, exclude_none: bool = False, is_coroutine: bool = True, + endpoint_ctx: Optional[EndpointContext] = None, ) -> Any: if field: errors = [] @@ -245,8 +274,11 @@ async def serialize_response( elif errors_: errors.append(errors_) if errors: + ctx = endpoint_ctx or EndpointContext() raise ResponseValidationError( - errors=_normalize_errors(errors), body=response_content + errors=_normalize_errors(errors), + body=response_content, + endpoint_ctx=ctx, ) if hasattr(field, "serialize"): @@ -318,6 +350,18 @@ def get_request_handler( "fastapi_middleware_astack not found in request scope" ) + # Extract endpoint context for error messages + endpoint_ctx = ( + _extract_endpoint_context(dependant.call) + if dependant.call + else EndpointContext() + ) + + if dependant.path: + # For mounted sub-apps, include the mount path prefix + mount_path = request.scope.get("root_path", "").rstrip("/") + endpoint_ctx["path"] = f"{request.method} {mount_path}{dependant.path}" + # Read body and auto-close files try: body: Any = None @@ -355,6 +399,7 @@ def get_request_handler( } ], body=e.doc, + endpoint_ctx=endpoint_ctx, ) raise validation_error from e except HTTPException: @@ -414,6 +459,7 @@ def get_request_handler( exclude_defaults=response_model_exclude_defaults, exclude_none=response_model_exclude_none, is_coroutine=is_coroutine, + endpoint_ctx=endpoint_ctx, ) response = actual_response_class(content, **response_args) if not is_body_allowed_for_status_code(response.status_code): @@ -421,7 +467,7 @@ def get_request_handler( response.headers.raw.extend(solved_result.response.headers.raw) if errors: validation_error = RequestValidationError( - _normalize_errors(errors), body=body + _normalize_errors(errors), body=body, endpoint_ctx=endpoint_ctx ) raise validation_error @@ -438,6 +484,15 @@ def get_websocket_app( embed_body_fields: bool = False, ) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: async def app(websocket: WebSocket) -> None: + endpoint_ctx = ( + _extract_endpoint_context(dependant.call) + if dependant.call + else EndpointContext() + ) + if dependant.path: + # For mounted sub-apps, include the mount path prefix + mount_path = websocket.scope.get("root_path", "").rstrip("/") + endpoint_ctx["path"] = f"WS {mount_path}{dependant.path}" async_exit_stack = websocket.scope.get("fastapi_inner_astack") assert isinstance(async_exit_stack, AsyncExitStack), ( "fastapi_inner_astack not found in request scope" @@ -451,7 +506,8 @@ def get_websocket_app( ) if solved_result.errors: raise WebSocketRequestValidationError( - _normalize_errors(solved_result.errors) + _normalize_errors(solved_result.errors), + endpoint_ctx=endpoint_ctx, ) assert dependant.call is not None, "dependant.call must be a function" await dependant.call(**solved_result.values) diff --git a/tests/test_validation_error_context.py b/tests/test_validation_error_context.py new file mode 100644 index 0000000000..844b8a64f9 --- /dev/null +++ b/tests/test_validation_error_context.py @@ -0,0 +1,168 @@ +from fastapi import FastAPI, Request, WebSocket +from fastapi.exceptions import ( + RequestValidationError, + ResponseValidationError, + WebSocketRequestValidationError, +) +from fastapi.testclient import TestClient +from pydantic import BaseModel + + +class Item(BaseModel): + id: int + name: str + + +class ExceptionCapture: + def __init__(self): + self.exception = None + + def capture(self, exc): + self.exception = exc + return exc + + +app = FastAPI() +sub_app = FastAPI() +captured_exception = ExceptionCapture() + +app.mount(path="/sub", app=sub_app) + + +@app.exception_handler(RequestValidationError) +@sub_app.exception_handler(RequestValidationError) +async def request_validation_handler(request: Request, exc: RequestValidationError): + captured_exception.capture(exc) + raise exc + + +@app.exception_handler(ResponseValidationError) +@sub_app.exception_handler(ResponseValidationError) +async def response_validation_handler(_: Request, exc: ResponseValidationError): + captured_exception.capture(exc) + raise exc + + +@app.exception_handler(WebSocketRequestValidationError) +@sub_app.exception_handler(WebSocketRequestValidationError) +async def websocket_validation_handler( + websocket: WebSocket, exc: WebSocketRequestValidationError +): + captured_exception.capture(exc) + raise exc + + +@app.get("/users/{user_id}") +def get_user(user_id: int): + return {"user_id": user_id} # pragma: no cover + + +@app.get("/items/", response_model=Item) +def get_item(): + return {"name": "Widget"} + + +@sub_app.get("/items/", response_model=Item) +def get_sub_item(): + return {"name": "Widget"} # pragma: no cover + + +@app.websocket("/ws/{item_id}") +async def websocket_endpoint(websocket: WebSocket, item_id: int): + await websocket.accept() # pragma: no cover + await websocket.send_text(f"Item: {item_id}") # pragma: no cover + await websocket.close() # pragma: no cover + + +@sub_app.websocket("/ws/{item_id}") +async def subapp_websocket_endpoint(websocket: WebSocket, item_id: int): + await websocket.accept() # pragma: no cover + await websocket.send_text(f"Item: {item_id}") # pragma: no cover + await websocket.close() # pragma: no cover + + +client = TestClient(app) + + +def test_request_validation_error_includes_endpoint_context(): + captured_exception.exception = None + try: + client.get("/users/invalid") + except Exception: + pass + + assert captured_exception.exception is not None + error_str = str(captured_exception.exception) + assert "get_user" in error_str + assert "/users/" in error_str + + +def test_response_validation_error_includes_endpoint_context(): + captured_exception.exception = None + try: + client.get("/items/") + except Exception: + pass + + assert captured_exception.exception is not None + error_str = str(captured_exception.exception) + assert "get_item" in error_str + assert "/items/" in error_str + + +def test_websocket_validation_error_includes_endpoint_context(): + captured_exception.exception = None + try: + with client.websocket_connect("/ws/invalid"): + pass # pragma: no cover + except Exception: + pass + + assert captured_exception.exception is not None + error_str = str(captured_exception.exception) + assert "websocket_endpoint" in error_str + assert "/ws/" in error_str + + +def test_subapp_request_validation_error_includes_endpoint_context(): + captured_exception.exception = None + try: + client.get("/sub/items/") + except Exception: + pass + + assert captured_exception.exception is not None + error_str = str(captured_exception.exception) + assert "get_sub_item" in error_str + assert "/sub/items/" in error_str + + +def test_subapp_websocket_validation_error_includes_endpoint_context(): + captured_exception.exception = None + try: + with client.websocket_connect("/sub/ws/invalid"): + pass # pragma: no cover + except Exception: + pass + + assert captured_exception.exception is not None + error_str = str(captured_exception.exception) + assert "subapp_websocket_endpoint" in error_str + assert "/sub/ws/" in error_str + + +def test_validation_error_with_only_path(): + errors = [{"type": "missing", "loc": ("body", "name"), "msg": "Field required"}] + exc = RequestValidationError(errors, endpoint_ctx={"path": "GET /api/test"}) + error_str = str(exc) + assert "Endpoint: GET /api/test" in error_str + assert 'File "' not in error_str + + +def test_validation_error_with_no_context(): + errors = [{"type": "missing", "loc": ("body", "name"), "msg": "Field required"}] + exc = RequestValidationError(errors, endpoint_ctx={}) + error_str = str(exc) + assert "1 validation error:" in error_str + assert "Endpoint" not in error_str + assert 'File "' not in error_str From dbd34f15789f4afa851e339cea4fcd49f421039d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Dec 2025 12:22:24 +0000 Subject: [PATCH 022/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d27c473831..b294de9061 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Features + +* 🚸 Improve tracebacks by adding endpoint metadata. PR [#14306](https://github.com/fastapi/fastapi/pull/14306) by [@savannahostrowski](https://github.com/savannahostrowski). + ## 0.123.10 ### Fixes From 5b6245666b8a499d2551caff7567056ef7f881b2 Mon Sep 17 00:00:00 2001 From: Yuji Teshima <36704166+yujiteshima@users.noreply.github.com> Date: Sat, 6 Dec 2025 21:23:01 +0900 Subject: [PATCH 023/140] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typo=20in=20?= =?UTF-8?q?`scripts/mkdocs=5Fhooks.py`=20(#14457)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/mkdocs_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index b9e4ff59ed..09cfa99e38 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -132,7 +132,7 @@ def on_pre_page(page: Page, *, config: MkDocsConfig, files: Files) -> Page: def on_page_markdown( markdown: str, *, page: Page, config: MkDocsConfig, files: Files ) -> str: - # Set matadata["social"]["cards_layout_options"]["title"] to clean title (without + # Set metadata["social"]["cards_layout_options"]["title"] to clean title (without # permalink) title = page.title clean_title = title.split("{ #")[0] From a2cef707e30fb6eb14812e4e273e34079d30ae6b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 6 Dec 2025 12:23:23 +0000 Subject: [PATCH 024/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b294de9061..f49d46a8f3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * 🚸 Improve tracebacks by adding endpoint metadata. PR [#14306](https://github.com/fastapi/fastapi/pull/14306) by [@savannahostrowski](https://github.com/savannahostrowski). +### Internal + +* ✏️ Fix typo in `scripts/mkdocs_hooks.py`. PR [#14457](https://github.com/fastapi/fastapi/pull/14457) by [@yujiteshima](https://github.com/yujiteshima). + ## 0.123.10 ### Fixes From b5ca13249e3f2002c70c3f2de528a128af2008f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 6 Dec 2025 14:09:51 +0100 Subject: [PATCH 025/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?4.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f49d46a8f3..f40223338a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.124.0 + ### Features * 🚸 Improve tracebacks by adding endpoint metadata. PR [#14306](https://github.com/fastapi/fastapi/pull/14306) by [@savannahostrowski](https://github.com/savannahostrowski). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 2396c501d6..7009a7777b 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.123.10" +__version__ = "0.124.0" from starlette import status as status From 81517f66ccb906afef898570c823053afba994d8 Mon Sep 17 00:00:00 2001 From: Alejandra <90076947+alejsdev@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:04:54 +0100 Subject: [PATCH 026/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20tech=20stack=20?= =?UTF-8?q?in=20project=20generation=20docs=20(#14472)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/de/docs/project-generation.md | 2 +- docs/en/docs/project-generation.md | 18 +++++++++--------- docs/pt/docs/project-generation.md | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/de/docs/project-generation.md b/docs/de/docs/project-generation.md index f830f0f4d6..dd3c3b4276 100644 --- a/docs/de/docs/project-generation.md +++ b/docs/de/docs/project-generation.md @@ -14,7 +14,7 @@ GitHub-Repository: Date: Mon, 8 Dec 2025 13:05:20 +0000 Subject: [PATCH 027/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f40223338a..0af51573f8 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 📝 Update tech stack in project generation docs. PR [#14472](https://github.com/fastapi/fastapi/pull/14472) by [@alejsdev](https://github.com/alejsdev). + ## 0.124.0 ### Features From 8cedb742cb59f72a752bb0a6b4f73c02aeb15bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 9 Dec 2025 03:12:24 -0800 Subject: [PATCH 028/140] =?UTF-8?q?=E2=9C=85=20Add=20test=20for=20Pydantic?= =?UTF-8?q?=20v2,=20dataclasses,=20UUID,=20and=20`=5F=5Fannotations=5F=5F`?= =?UTF-8?q?=20(#14477)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ataclasses_uuid_stringified_annotations.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py diff --git a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py new file mode 100644 index 0000000000..c9f94563bb --- /dev/null +++ b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +import uuid +from dataclasses import dataclass, field +from typing import List, Union + +from dirty_equals import IsUUID +from fastapi import FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot + + +@dataclass +class Item: + id: uuid.UUID + name: str + price: float + tags: List[str] = field(default_factory=list) + description: Union[str, None] = None + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.get("/item", response_model=Item) +async def read_item(): + return { + "id": uuid.uuid4(), + "name": "Island In The Moon", + "price": 12.99, + "description": "A place to be be playin' and havin' fun", + "tags": ["breater"], + } + + +client = TestClient(app) + + +def test_annotations(): + response = client.get("/item") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "id": IsUUID(), + "name": "Island In The Moon", + "price": 12.99, + "tags": ["breater"], + "description": "A place to be be playin' and havin' fun", + "tax": None, + } + ) From 5b28a04d550d95ef0061f41f5a5dcfe36045e8c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 9 Dec 2025 11:12:49 +0000 Subject: [PATCH 029/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0af51573f8..60bc1cc6f0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * 📝 Update tech stack in project generation docs. PR [#14472](https://github.com/fastapi/fastapi/pull/14472) by [@alejsdev](https://github.com/alejsdev). +### Internal + +* ✅ Add test for Pydantic v2, dataclasses, UUID, and `__annotations__`. PR [#14477](https://github.com/fastapi/fastapi/pull/14477) by [@tiangolo](https://github.com/tiangolo). + ## 0.124.0 ### Features From 9475024640f2e204944c5aa2cd9c67a8826189d9 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:55:32 +0100 Subject: [PATCH 030/140] =?UTF-8?q?=F0=9F=93=9D=20Add=20variants=20for=20c?= =?UTF-8?q?ode=20examples=20in=20"Advanced=20User=20Guide"=20(#14413)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/advanced/additional-responses.md | 4 +- docs/en/docs/advanced/dataclasses.md | 6 +- docs/en/docs/advanced/openapi-callbacks.md | 8 +- .../path-operation-advanced-configuration.md | 10 +- docs/en/docs/advanced/response-directly.md | 2 +- docs/en/docs/advanced/settings.md | 8 +- docs/en/docs/how-to/configure-swagger-ui.md | 2 +- .../docs/how-to/custom-request-and-route.md | 12 +- docs/en/docs/tutorial/bigger-applications.md | 78 +---- docs/en/docs/tutorial/cookie-param-models.md | 2 +- docs/en/docs/tutorial/testing.md | 54 +--- .../additional_responses/tutorial002_py310.py | 28 ++ .../additional_responses/tutorial004_py310.py | 30 ++ .../tutorial001_an.py | 36 +++ .../tutorial001_an_py310.py | 36 +++ .../tutorial001_an_py39.py | 35 +++ .../tutorial001_py310.py | 35 +++ .../tutorial001_py39.py | 35 +++ .../tutorial002_an.py | 30 ++ .../tutorial002_an_py310.py | 30 ++ .../tutorial002_an_py39.py | 29 ++ .../tutorial002_py310.py | 29 ++ .../tutorial002_py39.py | 29 ++ .../tutorial003_py310.py | 39 +++ docs_src/dataclasses/tutorial001_py310.py | 19 ++ docs_src/dataclasses/tutorial002_py310.py | 25 ++ docs_src/dataclasses/tutorial002_py39.py | 26 ++ docs_src/dataclasses/tutorial003_py310.py | 54 ++++ docs_src/dataclasses/tutorial003_py39.py | 55 ++++ .../openapi_callbacks/tutorial001_py310.py | 51 ++++ .../tutorial004_py310.py | 28 ++ .../tutorial004_py39.py | 30 ++ .../tutorial007_pv1_py39.py | 32 ++ .../tutorial007_py39.py | 32 ++ .../response_directly/tutorial001_py310.py | 21 ++ docs_src/settings/app03/config.py | 5 +- docs_src/settings/app03/config_pv1.py | 10 + docs_src/settings/app03_an/main.py | 2 +- docs_src/settings/app03_an_py39/config.py | 5 +- docs_src/settings/app03_an_py39/config_pv1.py | 10 + docs_src/settings/app03_an_py39/main.py | 2 +- pyproject.toml | 7 + .../test_tutorial002.py | 27 +- .../test_tutorial004.py | 27 +- .../test_tutorial001.py | 29 +- .../test_tutorial002.py | 29 +- .../test_tutorial003.py | 25 +- .../test_dataclasses/test_tutorial001.py | 28 +- .../test_dataclasses/test_tutorial002.py | 27 +- .../test_dataclasses/test_tutorial003.py | 33 +- .../test_tutorial001.py | 35 ++- .../test_tutorial004.py | 33 +- .../test_tutorial007.py | 20 +- .../test_tutorial007_pv1.py | 20 +- .../test_response_directly/__init__.py | 0 .../test_tutorial001.py | 288 ++++++++++++++++++ .../test_tutorial/test_settings/test_app02.py | 43 ++- .../test_tutorial/test_settings/test_app03.py | 59 ++++ .../test_using_request_directly/__init__.py | 0 .../test_tutorial001.py | 112 +++++++ .../test_websockets/test_tutorial003.py | 39 ++- .../test_websockets/test_tutorial003_py39.py | 50 --- 62 files changed, 1655 insertions(+), 290 deletions(-) create mode 100644 docs_src/additional_responses/tutorial002_py310.py create mode 100644 docs_src/additional_responses/tutorial004_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial001_an.py create mode 100644 docs_src/custom_request_and_route/tutorial001_an_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial001_an_py39.py create mode 100644 docs_src/custom_request_and_route/tutorial001_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial001_py39.py create mode 100644 docs_src/custom_request_and_route/tutorial002_an.py create mode 100644 docs_src/custom_request_and_route/tutorial002_an_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial002_an_py39.py create mode 100644 docs_src/custom_request_and_route/tutorial002_py310.py create mode 100644 docs_src/custom_request_and_route/tutorial002_py39.py create mode 100644 docs_src/custom_request_and_route/tutorial003_py310.py create mode 100644 docs_src/dataclasses/tutorial001_py310.py create mode 100644 docs_src/dataclasses/tutorial002_py310.py create mode 100644 docs_src/dataclasses/tutorial002_py39.py create mode 100644 docs_src/dataclasses/tutorial003_py310.py create mode 100644 docs_src/dataclasses/tutorial003_py39.py create mode 100644 docs_src/openapi_callbacks/tutorial001_py310.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial004_py310.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial004_py39.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py create mode 100644 docs_src/path_operation_advanced_configuration/tutorial007_py39.py create mode 100644 docs_src/response_directly/tutorial001_py310.py create mode 100644 docs_src/settings/app03/config_pv1.py create mode 100644 docs_src/settings/app03_an_py39/config_pv1.py create mode 100644 tests/test_tutorial/test_response_directly/__init__.py create mode 100644 tests/test_tutorial/test_response_directly/test_tutorial001.py create mode 100644 tests/test_tutorial/test_settings/test_app03.py create mode 100644 tests/test_tutorial/test_using_request_directly/__init__.py create mode 100644 tests/test_tutorial/test_using_request_directly/test_tutorial001.py delete mode 100644 tests/test_tutorial/test_websockets/test_tutorial003_py39.py diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md index 799532c5b2..cb3a40d13e 100644 --- a/docs/en/docs/advanced/additional-responses.md +++ b/docs/en/docs/advanced/additional-responses.md @@ -175,7 +175,7 @@ You can use this same `responses` parameter to add different media types for the For example, you can add an additional media type of `image/png`, declaring that your *path operation* can return a JSON object (with media type `application/json`) or a PNG image: -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} /// note @@ -237,7 +237,7 @@ You can use that technique to reuse some predefined responses in your *path oper For example: -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} ## More information about OpenAPI responses { #more-information-about-openapi-responses } diff --git a/docs/en/docs/advanced/dataclasses.md b/docs/en/docs/advanced/dataclasses.md index b7b9b65c52..574beb65f4 100644 --- a/docs/en/docs/advanced/dataclasses.md +++ b/docs/en/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI is built on top of **Pydantic**, and I have been showing you how to use But FastAPI also supports using `dataclasses` the same way: -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} This is still supported thanks to **Pydantic**, as it has internal support for `dataclasses`. @@ -32,7 +32,7 @@ But if you have a bunch of dataclasses laying around, this is a nice trick to us You can also use `dataclasses` in the `response_model` parameter: -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} The dataclass will be automatically converted to a Pydantic dataclass. @@ -48,7 +48,7 @@ In some cases, you might still have to use Pydantic's version of `dataclasses`. In that case, you can simply swap the standard `dataclasses` with `pydantic.dataclasses`, which is a drop-in replacement: -{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} +{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. We still import `field` from standard `dataclasses`. diff --git a/docs/en/docs/advanced/openapi-callbacks.md b/docs/en/docs/advanced/openapi-callbacks.md index 059d893c26..5bd7c2cfd4 100644 --- a/docs/en/docs/advanced/openapi-callbacks.md +++ b/docs/en/docs/advanced/openapi-callbacks.md @@ -31,7 +31,7 @@ It will have a *path operation* that will receive an `Invoice` body, and a query This part is pretty normal, most of the code is probably already familiar to you: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} /// tip @@ -90,7 +90,7 @@ Temporarily adopting this point of view (of the *external developer*) can help y First create a new `APIRouter` that will contain one or more callbacks. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} ### Create the callback *path operation* { #create-the-callback-path-operation } @@ -101,7 +101,7 @@ It should look just like a normal FastAPI *path operation*: * It should probably have a declaration of the body it should receive, e.g. `body: InvoiceEvent`. * And it could also have a declaration of the response it should return, e.g. `response_model=InvoiceEventReceived`. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} There are 2 main differences from a normal *path operation*: @@ -169,7 +169,7 @@ At this point you have the *callback path operation(s)* needed (the one(s) that Now use the parameter `callbacks` in *your API's path operation decorator* to pass the attribute `.routes` (that's actually just a `list` of routes/*path operations*) from that callback router: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} /// tip diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index b9961f9f38..5879bc5c71 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -50,7 +50,7 @@ Adding an `\f` (an escaped "form feed" character) causes **FastAPI** to truncate It won't show up in the documentation, but other tools (such as Sphinx) will be able to use the rest. -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} ## Additional Responses { #additional-responses } @@ -155,13 +155,13 @@ For example, in this application we don't use FastAPI's integrated functionality //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} //// @@ -179,13 +179,13 @@ And then in our code, we parse that YAML content directly, and then we are again //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} //// diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 3197e1bd46..156b4dac7e 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/docs/en/docs/advanced/response-directly.md @@ -34,7 +34,7 @@ For example, you cannot put a Pydantic model in a `JSONResponse` without first c For those cases, you can use the `jsonable_encoder` to convert your data before passing it to a response: -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} /// note | Technical Details diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md index a218c3d016..0220c52ce1 100644 --- a/docs/en/docs/advanced/settings.md +++ b/docs/en/docs/advanced/settings.md @@ -148,7 +148,7 @@ This could be especially useful during testing, as it's very easy to override a Coming from the previous example, your `config.py` file could look like: -{* ../../docs_src/settings/app02/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} Notice that now we don't create a default instance `settings = Settings()`. @@ -174,7 +174,7 @@ And then we can require it from the *path operation function* as a dependency an Then it would be very easy to provide a different settings object during testing by creating a dependency override for `get_settings`: -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} In the dependency override we set a new value for the `admin_email` when creating the new `Settings` object, and then we return that new object. @@ -217,7 +217,7 @@ And then update your `config.py` with: //// tab | Pydantic v2 -{* ../../docs_src/settings/app03_an/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip @@ -229,7 +229,7 @@ The `model_config` attribute is used just for Pydantic configuration. You can re //// tab | Pydantic v1 -{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} +{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} /// tip diff --git a/docs/en/docs/how-to/configure-swagger-ui.md b/docs/en/docs/how-to/configure-swagger-ui.md index 2d7b99f8fa..3dbfcffeca 100644 --- a/docs/en/docs/how-to/configure-swagger-ui.md +++ b/docs/en/docs/how-to/configure-swagger-ui.md @@ -40,7 +40,7 @@ FastAPI includes some default configuration parameters appropriate for most of t It includes these default configurations: -{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} You can override any of them by setting a different value in the argument `swagger_ui_parameters`. diff --git a/docs/en/docs/how-to/custom-request-and-route.md b/docs/en/docs/how-to/custom-request-and-route.md index 884c8ed04f..bfc60729f8 100644 --- a/docs/en/docs/how-to/custom-request-and-route.md +++ b/docs/en/docs/how-to/custom-request-and-route.md @@ -42,7 +42,7 @@ If there's no `gzip` in the header, it will not try to decompress the body. That way, the same route class can handle gzip compressed or uncompressed requests. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} ### Create a custom `GzipRoute` class { #create-a-custom-gziproute-class } @@ -54,7 +54,7 @@ This method returns a function. And that function is what will receive a request Here we use it to create a `GzipRequest` from the original request. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} /// note | Technical Details @@ -92,18 +92,18 @@ We can also use this same approach to access the request body in an exception ha All we need to do is handle the request inside a `try`/`except` block: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} If an exception occurs, the`Request` instance will still be in scope, so we can read and make use of the request body when handling the error: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} ## Custom `APIRoute` class in a router { #custom-apiroute-class-in-a-router } You can also set the `route_class` parameter of an `APIRouter`: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} In this example, the *path operations* under the `router` will use the custom `TimedRoute` class, and will have an extra `X-Response-Time` header in the response with the time it took to generate the response: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/en/docs/tutorial/bigger-applications.md b/docs/en/docs/tutorial/bigger-applications.md index 74daa54835..3cc9d7ecfb 100644 --- a/docs/en/docs/tutorial/bigger-applications.md +++ b/docs/en/docs/tutorial/bigger-applications.md @@ -85,9 +85,7 @@ You can create the *path operations* for that module using `APIRouter`. You import it and create an "instance" the same way you would with the class `FastAPI`: -```Python hl_lines="1 3" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### *Path operations* with `APIRouter` { #path-operations-with-apirouter } @@ -95,9 +93,7 @@ And then you use it to declare your *path operations*. Use it the same way you would use the `FastAPI` class: -```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} You can think of `APIRouter` as a "mini `FastAPI`" class. @@ -121,35 +117,7 @@ So we put them in their own `dependencies` module (`app/dependencies.py`). We will now use a simple dependency to read a custom `X-Token` header: -//// tab | Python 3.9+ - -```Python hl_lines="3 6-8" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip - -Prefer to use the `Annotated` version if possible. - -/// - -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app/dependencies.py!} -``` - -//// +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip @@ -181,9 +149,7 @@ We know all the *path operations* in this module have the same: So, instead of adding all that to each *path operation*, we can add it to the `APIRouter`. -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} As the path of each *path operation* has to start with `/`, like in: @@ -242,9 +208,7 @@ And we need to get the dependency function from the module `app.dependencies`, t So we use a relative import with `..` for the dependencies: -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} #### How relative imports work { #how-relative-imports-work } @@ -315,9 +279,7 @@ We are not adding the prefix `/items` nor the `tags=["items"]` to each *path ope But we can still add _more_ `tags` that will be applied to a specific *path operation*, and also some extra `responses` specific to that *path operation*: -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip @@ -343,17 +305,13 @@ You import and create a `FastAPI` class as normally. And we can even declare [global dependencies](dependencies/global-dependencies.md){.internal-link target=_blank} that will be combined with the dependencies for each `APIRouter`: -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} ### Import the `APIRouter` { #import-the-apirouter } Now we import the other submodules that have `APIRouter`s: -```Python hl_lines="4-5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} As the files `app/routers/users.py` and `app/routers/items.py` are submodules that are part of the same Python package `app`, we can use a single dot `.` to import them using "relative imports". @@ -416,17 +374,13 @@ the `router` from `users` would overwrite the one from `items` and we wouldn't b So, to be able to use both of them in the same file, we import the submodules directly: -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} ### Include the `APIRouter`s for `users` and `items` { #include-the-apirouters-for-users-and-items } Now, let's include the `router`s from the submodules `users` and `items`: -```Python hl_lines="10-11" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} /// info @@ -466,17 +420,13 @@ It contains an `APIRouter` with some admin *path operations* that your organizat For this example it will be super simple. But let's say that because it is shared with other projects in the organization, we cannot modify it and add a `prefix`, `dependencies`, `tags`, etc. directly to the `APIRouter`: -```Python hl_lines="3" title="app/internal/admin.py" -{!../../docs_src/bigger_applications/app/internal/admin.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} But we still want to set a custom `prefix` when including the `APIRouter` so that all its *path operations* start with `/admin`, we want to secure it with the `dependencies` we already have for this project, and we want to include `tags` and `responses`. We can declare all that without having to modify the original `APIRouter` by passing those parameters to `app.include_router()`: -```Python hl_lines="14-17" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} That way, the original `APIRouter` will stay unmodified, so we can still share that same `app/internal/admin.py` file with other projects in the organization. @@ -497,9 +447,7 @@ We can also add *path operations* directly to the `FastAPI` app. Here we do it... just to show that we can 🤷: -```Python hl_lines="21-23" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} and it will work correctly, together with all the other *path operations* added with `app.include_router()`. diff --git a/docs/en/docs/tutorial/cookie-param-models.md b/docs/en/docs/tutorial/cookie-param-models.md index 96dc5cf3d8..016a65d7f0 100644 --- a/docs/en/docs/tutorial/cookie-param-models.md +++ b/docs/en/docs/tutorial/cookie-param-models.md @@ -50,7 +50,7 @@ Your API now has the power to control its own bytes: + if not hasattr(self, "_body"): + body = await super().body() + if "gzip" in self.headers.getlist("Content-Encoding"): + body = gzip.decompress(body) + self._body = body + return self._body + + +class GzipRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + request = GzipRequest(request.scope, request.receive) + return await original_route_handler(request) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = GzipRoute + + +@app.post("/sum") +async def sum_numbers(numbers: Annotated[List[int], Body()]): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_an_py310.py b/docs_src/custom_request_and_route/tutorial001_an_py310.py new file mode 100644 index 0000000000..381bab6d84 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_an_py310.py @@ -0,0 +1,36 @@ +import gzip +from collections.abc import Callable +from typing import Annotated + +from fastapi import Body, FastAPI, Request, Response +from fastapi.routing import APIRoute + + +class GzipRequest(Request): + async def body(self) -> bytes: + if not hasattr(self, "_body"): + body = await super().body() + if "gzip" in self.headers.getlist("Content-Encoding"): + body = gzip.decompress(body) + self._body = body + return self._body + + +class GzipRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + request = GzipRequest(request.scope, request.receive) + return await original_route_handler(request) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = GzipRoute + + +@app.post("/sum") +async def sum_numbers(numbers: Annotated[list[int], Body()]): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_an_py39.py b/docs_src/custom_request_and_route/tutorial001_an_py39.py new file mode 100644 index 0000000000..076727e643 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_an_py39.py @@ -0,0 +1,35 @@ +import gzip +from typing import Annotated, Callable + +from fastapi import Body, FastAPI, Request, Response +from fastapi.routing import APIRoute + + +class GzipRequest(Request): + async def body(self) -> bytes: + if not hasattr(self, "_body"): + body = await super().body() + if "gzip" in self.headers.getlist("Content-Encoding"): + body = gzip.decompress(body) + self._body = body + return self._body + + +class GzipRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + request = GzipRequest(request.scope, request.receive) + return await original_route_handler(request) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = GzipRoute + + +@app.post("/sum") +async def sum_numbers(numbers: Annotated[list[int], Body()]): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_py310.py b/docs_src/custom_request_and_route/tutorial001_py310.py new file mode 100644 index 0000000000..c678088ce7 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_py310.py @@ -0,0 +1,35 @@ +import gzip +from collections.abc import Callable + +from fastapi import Body, FastAPI, Request, Response +from fastapi.routing import APIRoute + + +class GzipRequest(Request): + async def body(self) -> bytes: + if not hasattr(self, "_body"): + body = await super().body() + if "gzip" in self.headers.getlist("Content-Encoding"): + body = gzip.decompress(body) + self._body = body + return self._body + + +class GzipRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + request = GzipRequest(request.scope, request.receive) + return await original_route_handler(request) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = GzipRoute + + +@app.post("/sum") +async def sum_numbers(numbers: list[int] = Body()): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_py39.py b/docs_src/custom_request_and_route/tutorial001_py39.py new file mode 100644 index 0000000000..54b20b9425 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial001_py39.py @@ -0,0 +1,35 @@ +import gzip +from typing import Callable + +from fastapi import Body, FastAPI, Request, Response +from fastapi.routing import APIRoute + + +class GzipRequest(Request): + async def body(self) -> bytes: + if not hasattr(self, "_body"): + body = await super().body() + if "gzip" in self.headers.getlist("Content-Encoding"): + body = gzip.decompress(body) + self._body = body + return self._body + + +class GzipRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + request = GzipRequest(request.scope, request.receive) + return await original_route_handler(request) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = GzipRoute + + +@app.post("/sum") +async def sum_numbers(numbers: list[int] = Body()): + return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial002_an.py b/docs_src/custom_request_and_route/tutorial002_an.py new file mode 100644 index 0000000000..127f7a9ce0 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial002_an.py @@ -0,0 +1,30 @@ +from typing import Callable, List + +from fastapi import Body, FastAPI, HTTPException, Request, Response +from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute +from typing_extensions import Annotated + + +class ValidationErrorLoggingRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + try: + return await original_route_handler(request) + except RequestValidationError as exc: + body = await request.body() + detail = {"errors": exc.errors(), "body": body.decode()} + raise HTTPException(status_code=422, detail=detail) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = ValidationErrorLoggingRoute + + +@app.post("/") +async def sum_numbers(numbers: Annotated[List[int], Body()]): + return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial002_an_py310.py b/docs_src/custom_request_and_route/tutorial002_an_py310.py new file mode 100644 index 0000000000..69b7de4859 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial002_an_py310.py @@ -0,0 +1,30 @@ +from collections.abc import Callable +from typing import Annotated + +from fastapi import Body, FastAPI, HTTPException, Request, Response +from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute + + +class ValidationErrorLoggingRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + try: + return await original_route_handler(request) + except RequestValidationError as exc: + body = await request.body() + detail = {"errors": exc.errors(), "body": body.decode()} + raise HTTPException(status_code=422, detail=detail) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = ValidationErrorLoggingRoute + + +@app.post("/") +async def sum_numbers(numbers: Annotated[list[int], Body()]): + return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial002_an_py39.py b/docs_src/custom_request_and_route/tutorial002_an_py39.py new file mode 100644 index 0000000000..e7de09de44 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial002_an_py39.py @@ -0,0 +1,29 @@ +from typing import Annotated, Callable + +from fastapi import Body, FastAPI, HTTPException, Request, Response +from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute + + +class ValidationErrorLoggingRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + try: + return await original_route_handler(request) + except RequestValidationError as exc: + body = await request.body() + detail = {"errors": exc.errors(), "body": body.decode()} + raise HTTPException(status_code=422, detail=detail) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = ValidationErrorLoggingRoute + + +@app.post("/") +async def sum_numbers(numbers: Annotated[list[int], Body()]): + return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial002_py310.py b/docs_src/custom_request_and_route/tutorial002_py310.py new file mode 100644 index 0000000000..13a5ca5426 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial002_py310.py @@ -0,0 +1,29 @@ +from collections.abc import Callable + +from fastapi import Body, FastAPI, HTTPException, Request, Response +from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute + + +class ValidationErrorLoggingRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + try: + return await original_route_handler(request) + except RequestValidationError as exc: + body = await request.body() + detail = {"errors": exc.errors(), "body": body.decode()} + raise HTTPException(status_code=422, detail=detail) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = ValidationErrorLoggingRoute + + +@app.post("/") +async def sum_numbers(numbers: list[int] = Body()): + return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial002_py39.py b/docs_src/custom_request_and_route/tutorial002_py39.py new file mode 100644 index 0000000000..c4e4748281 --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial002_py39.py @@ -0,0 +1,29 @@ +from typing import Callable + +from fastapi import Body, FastAPI, HTTPException, Request, Response +from fastapi.exceptions import RequestValidationError +from fastapi.routing import APIRoute + + +class ValidationErrorLoggingRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + try: + return await original_route_handler(request) + except RequestValidationError as exc: + body = await request.body() + detail = {"errors": exc.errors(), "body": body.decode()} + raise HTTPException(status_code=422, detail=detail) + + return custom_route_handler + + +app = FastAPI() +app.router.route_class = ValidationErrorLoggingRoute + + +@app.post("/") +async def sum_numbers(numbers: list[int] = Body()): + return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial003_py310.py b/docs_src/custom_request_and_route/tutorial003_py310.py new file mode 100644 index 0000000000..f4e60be61b --- /dev/null +++ b/docs_src/custom_request_and_route/tutorial003_py310.py @@ -0,0 +1,39 @@ +import time +from collections.abc import Callable + +from fastapi import APIRouter, FastAPI, Request, Response +from fastapi.routing import APIRoute + + +class TimedRoute(APIRoute): + def get_route_handler(self) -> Callable: + original_route_handler = super().get_route_handler() + + async def custom_route_handler(request: Request) -> Response: + before = time.time() + response: Response = await original_route_handler(request) + duration = time.time() - before + response.headers["X-Response-Time"] = str(duration) + print(f"route duration: {duration}") + print(f"route response: {response}") + print(f"route response headers: {response.headers}") + return response + + return custom_route_handler + + +app = FastAPI() +router = APIRouter(route_class=TimedRoute) + + +@app.get("/") +async def not_timed(): + return {"message": "Not timed"} + + +@router.get("/timed") +async def timed(): + return {"message": "It's the time of my life"} + + +app.include_router(router) diff --git a/docs_src/dataclasses/tutorial001_py310.py b/docs_src/dataclasses/tutorial001_py310.py new file mode 100644 index 0000000000..ab709a7c85 --- /dev/null +++ b/docs_src/dataclasses/tutorial001_py310.py @@ -0,0 +1,19 @@ +from dataclasses import dataclass + +from fastapi import FastAPI + + +@dataclass +class Item: + name: str + price: float + description: str | None = None + tax: float | None = None + + +app = FastAPI() + + +@app.post("/items/") +async def create_item(item: Item): + return item diff --git a/docs_src/dataclasses/tutorial002_py310.py b/docs_src/dataclasses/tutorial002_py310.py new file mode 100644 index 0000000000..e16249f1e1 --- /dev/null +++ b/docs_src/dataclasses/tutorial002_py310.py @@ -0,0 +1,25 @@ +from dataclasses import dataclass, field + +from fastapi import FastAPI + + +@dataclass +class Item: + name: str + price: float + tags: list[str] = field(default_factory=list) + description: str | None = None + tax: float | None = None + + +app = FastAPI() + + +@app.get("/items/next", response_model=Item) +async def read_next_item(): + return { + "name": "Island In The Moon", + "price": 12.99, + "description": "A place to be playin' and havin' fun", + "tags": ["breater"], + } diff --git a/docs_src/dataclasses/tutorial002_py39.py b/docs_src/dataclasses/tutorial002_py39.py new file mode 100644 index 0000000000..0c23765d84 --- /dev/null +++ b/docs_src/dataclasses/tutorial002_py39.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass, field +from typing import Union + +from fastapi import FastAPI + + +@dataclass +class Item: + name: str + price: float + tags: list[str] = field(default_factory=list) + description: Union[str, None] = None + tax: Union[float, None] = None + + +app = FastAPI() + + +@app.get("/items/next", response_model=Item) +async def read_next_item(): + return { + "name": "Island In The Moon", + "price": 12.99, + "description": "A place to be playin' and havin' fun", + "tags": ["breater"], + } diff --git a/docs_src/dataclasses/tutorial003_py310.py b/docs_src/dataclasses/tutorial003_py310.py new file mode 100644 index 0000000000..9b9a3fd635 --- /dev/null +++ b/docs_src/dataclasses/tutorial003_py310.py @@ -0,0 +1,54 @@ +from dataclasses import field # (1) + +from fastapi import FastAPI +from pydantic.dataclasses import dataclass # (2) + + +@dataclass +class Item: + name: str + description: str | None = None + + +@dataclass +class Author: + name: str + items: list[Item] = field(default_factory=list) # (3) + + +app = FastAPI() + + +@app.post("/authors/{author_id}/items/", response_model=Author) # (4) +async def create_author_items(author_id: str, items: list[Item]): # (5) + return {"name": author_id, "items": items} # (6) + + +@app.get("/authors/", response_model=list[Author]) # (7) +def get_authors(): # (8) + return [ # (9) + { + "name": "Breaters", + "items": [ + { + "name": "Island In The Moon", + "description": "A place to be playin' and havin' fun", + }, + {"name": "Holy Buddies"}, + ], + }, + { + "name": "System of an Up", + "items": [ + { + "name": "Salt", + "description": "The kombucha mushroom people's favorite", + }, + {"name": "Pad Thai"}, + { + "name": "Lonely Night", + "description": "The mostests lonliest nightiest of allest", + }, + ], + }, + ] diff --git a/docs_src/dataclasses/tutorial003_py39.py b/docs_src/dataclasses/tutorial003_py39.py new file mode 100644 index 0000000000..991708c009 --- /dev/null +++ b/docs_src/dataclasses/tutorial003_py39.py @@ -0,0 +1,55 @@ +from dataclasses import field # (1) +from typing import Union + +from fastapi import FastAPI +from pydantic.dataclasses import dataclass # (2) + + +@dataclass +class Item: + name: str + description: Union[str, None] = None + + +@dataclass +class Author: + name: str + items: list[Item] = field(default_factory=list) # (3) + + +app = FastAPI() + + +@app.post("/authors/{author_id}/items/", response_model=Author) # (4) +async def create_author_items(author_id: str, items: list[Item]): # (5) + return {"name": author_id, "items": items} # (6) + + +@app.get("/authors/", response_model=list[Author]) # (7) +def get_authors(): # (8) + return [ # (9) + { + "name": "Breaters", + "items": [ + { + "name": "Island In The Moon", + "description": "A place to be playin' and havin' fun", + }, + {"name": "Holy Buddies"}, + ], + }, + { + "name": "System of an Up", + "items": [ + { + "name": "Salt", + "description": "The kombucha mushroom people's favorite", + }, + {"name": "Pad Thai"}, + { + "name": "Lonely Night", + "description": "The mostests lonliest nightiest of allest", + }, + ], + }, + ] diff --git a/docs_src/openapi_callbacks/tutorial001_py310.py b/docs_src/openapi_callbacks/tutorial001_py310.py new file mode 100644 index 0000000000..3efe0ee25f --- /dev/null +++ b/docs_src/openapi_callbacks/tutorial001_py310.py @@ -0,0 +1,51 @@ +from fastapi import APIRouter, FastAPI +from pydantic import BaseModel, HttpUrl + +app = FastAPI() + + +class Invoice(BaseModel): + id: str + title: str | None = None + customer: str + total: float + + +class InvoiceEvent(BaseModel): + description: str + paid: bool + + +class InvoiceEventReceived(BaseModel): + ok: bool + + +invoices_callback_router = APIRouter() + + +@invoices_callback_router.post( + "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived +) +def invoice_notification(body: InvoiceEvent): + pass + + +@app.post("/invoices/", callbacks=invoices_callback_router.routes) +def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None): + """ + Create an invoice. + + This will (let's imagine) let the API user (some external developer) create an + invoice. + + And this path operation will: + + * Send the invoice to the client. + * Collect the money from the client. + * Send a notification back to the API user (the external developer), as a callback. + * At this point is that the API will somehow send a POST request to the + external API with the notification of the invoice event + (e.g. "payment successful"). + """ + # Send the invoice, collect the money, send the notification (the callback) + return {"msg": "Invoice received"} diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py310.py b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py new file mode 100644 index 0000000000..a815a564b7 --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial004_py310.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + tags: set[str] = set() + + +@app.post("/items/", response_model=Item, summary="Create an item") +async def create_item(item: Item): + """ + Create an item with all the information: + + - **name**: each item must have a name + - **description**: a long description + - **price**: required + - **tax**: if the item doesn't have tax, you can omit this + - **tags**: a set of unique tag strings for this item + \f + :param item: User input. + """ + return item diff --git a/docs_src/path_operation_advanced_configuration/tutorial004_py39.py b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py new file mode 100644 index 0000000000..d5fe6705ca --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial004_py39.py @@ -0,0 +1,30 @@ +from typing import Union + +from fastapi import FastAPI +from pydantic import BaseModel + +app = FastAPI() + + +class Item(BaseModel): + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + tags: set[str] = set() + + +@app.post("/items/", response_model=Item, summary="Create an item") +async def create_item(item: Item): + """ + Create an item with all the information: + + - **name**: each item must have a name + - **description**: a long description + - **price**: required + - **tax**: if the item doesn't have tax, you can omit this + - **tags**: a set of unique tag strings for this item + \f + :param item: User input. + """ + return item diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py new file mode 100644 index 0000000000..831966553f --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py @@ -0,0 +1,32 @@ +import yaml +from fastapi import FastAPI, HTTPException, Request +from pydantic import BaseModel, ValidationError + +app = FastAPI() + + +class Item(BaseModel): + name: str + tags: list[str] + + +@app.post( + "/items/", + openapi_extra={ + "requestBody": { + "content": {"application/x-yaml": {"schema": Item.schema()}}, + "required": True, + }, + }, +) +async def create_item(request: Request): + raw_body = await request.body() + try: + data = yaml.safe_load(raw_body) + except yaml.YAMLError: + raise HTTPException(status_code=422, detail="Invalid YAML") + try: + item = Item.parse_obj(data) + except ValidationError as e: + raise HTTPException(status_code=422, detail=e.errors()) + return item diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py new file mode 100644 index 0000000000..ff64ef7923 --- /dev/null +++ b/docs_src/path_operation_advanced_configuration/tutorial007_py39.py @@ -0,0 +1,32 @@ +import yaml +from fastapi import FastAPI, HTTPException, Request +from pydantic import BaseModel, ValidationError + +app = FastAPI() + + +class Item(BaseModel): + name: str + tags: list[str] + + +@app.post( + "/items/", + openapi_extra={ + "requestBody": { + "content": {"application/x-yaml": {"schema": Item.model_json_schema()}}, + "required": True, + }, + }, +) +async def create_item(request: Request): + raw_body = await request.body() + try: + data = yaml.safe_load(raw_body) + except yaml.YAMLError: + raise HTTPException(status_code=422, detail="Invalid YAML") + try: + item = Item.model_validate(data) + except ValidationError as e: + raise HTTPException(status_code=422, detail=e.errors(include_url=False)) + return item diff --git a/docs_src/response_directly/tutorial001_py310.py b/docs_src/response_directly/tutorial001_py310.py new file mode 100644 index 0000000000..81e094dc69 --- /dev/null +++ b/docs_src/response_directly/tutorial001_py310.py @@ -0,0 +1,21 @@ +from datetime import datetime + +from fastapi import FastAPI +from fastapi.encoders import jsonable_encoder +from fastapi.responses import JSONResponse +from pydantic import BaseModel + + +class Item(BaseModel): + title: str + timestamp: datetime + description: str | None = None + + +app = FastAPI() + + +@app.put("/items/{id}") +def update_item(id: str, item: Item): + json_compatible_item_data = jsonable_encoder(item) + return JSONResponse(content=json_compatible_item_data) diff --git a/docs_src/settings/app03/config.py b/docs_src/settings/app03/config.py index 942aea3e58..08f8f88c28 100644 --- a/docs_src/settings/app03/config.py +++ b/docs_src/settings/app03/config.py @@ -1,4 +1,4 @@ -from pydantic_settings import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): @@ -6,5 +6,4 @@ class Settings(BaseSettings): admin_email: str items_per_user: int = 50 - class Config: - env_file = ".env" + model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03/config_pv1.py b/docs_src/settings/app03/config_pv1.py new file mode 100644 index 0000000000..e1c3ee3006 --- /dev/null +++ b/docs_src/settings/app03/config_pv1.py @@ -0,0 +1,10 @@ +from pydantic import BaseSettings + + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 + + class Config: + env_file = ".env" diff --git a/docs_src/settings/app03_an/main.py b/docs_src/settings/app03_an/main.py index 2f64b9cd17..62f3476396 100644 --- a/docs_src/settings/app03_an/main.py +++ b/docs_src/settings/app03_an/main.py @@ -1,7 +1,7 @@ from functools import lru_cache -from typing import Annotated from fastapi import Depends, FastAPI +from typing_extensions import Annotated from . import config diff --git a/docs_src/settings/app03_an_py39/config.py b/docs_src/settings/app03_an_py39/config.py index 942aea3e58..08f8f88c28 100644 --- a/docs_src/settings/app03_an_py39/config.py +++ b/docs_src/settings/app03_an_py39/config.py @@ -1,4 +1,4 @@ -from pydantic_settings import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): @@ -6,5 +6,4 @@ class Settings(BaseSettings): admin_email: str items_per_user: int = 50 - class Config: - env_file = ".env" + model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03_an_py39/config_pv1.py b/docs_src/settings/app03_an_py39/config_pv1.py new file mode 100644 index 0000000000..e1c3ee3006 --- /dev/null +++ b/docs_src/settings/app03_an_py39/config_pv1.py @@ -0,0 +1,10 @@ +from pydantic import BaseSettings + + +class Settings(BaseSettings): + app_name: str = "Awesome API" + admin_email: str + items_per_user: int = 50 + + class Config: + env_file = ".env" diff --git a/docs_src/settings/app03_an_py39/main.py b/docs_src/settings/app03_an_py39/main.py index 62f3476396..2f64b9cd17 100644 --- a/docs_src/settings/app03_an_py39/main.py +++ b/docs_src/settings/app03_an_py39/main.py @@ -1,7 +1,7 @@ from functools import lru_cache +from typing import Annotated from fastapi import Depends, FastAPI -from typing_extensions import Annotated from . import config diff --git a/pyproject.toml b/pyproject.toml index cafcf65c63..f8d5fa7c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -236,8 +236,15 @@ ignore = [ "docs_src/custom_response/tutorial007.py" = ["B007"] "docs_src/dataclasses/tutorial003.py" = ["I001"] "docs_src/path_operation_advanced_configuration/tutorial007.py" = ["B904"] +"docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"] "docs_src/path_operation_advanced_configuration/tutorial007_pv1.py" = ["B904"] +"docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"] "docs_src/custom_request_and_route/tutorial002.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_an.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"] +"docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"] "docs_src/dependencies/tutorial008_an.py" = ["F821"] "docs_src/dependencies/tutorial008_an_py39.py" = ["F821"] "docs_src/query_params_str_validations/tutorial012_an.py" = ["B006"] diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 588a3160a9..91d6ff101f 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -1,21 +1,36 @@ +import importlib import os import shutil +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.additional_responses.tutorial002 import app - -client = TestClient(app) +from tests.utils import needs_py310 -def test_path_operation(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002"), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.additional_responses.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_path_operation(client: TestClient): response = client.get("/items/foo") assert response.status_code == 200, response.text assert response.json() == {"id": "foo", "value": "there goes my hero"} -def test_path_operation_img(): +def test_path_operation_img(client: TestClient): shutil.copy("./docs/en/docs/img/favicon.png", "./image.png") response = client.get("/items/foo?img=1") assert response.status_code == 200, response.text @@ -24,7 +39,7 @@ def test_path_operation_img(): os.remove("./image.png") -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index 55b556d8e1..2d9491467e 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -1,21 +1,36 @@ +import importlib import os import shutil +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.additional_responses.tutorial004 import app - -client = TestClient(app) +from tests.utils import needs_py310 -def test_path_operation(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial004"), + pytest.param("tutorial004_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.additional_responses.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_path_operation(client: TestClient): response = client.get("/items/foo") assert response.status_code == 200, response.text assert response.json() == {"id": "foo", "value": "there goes my hero"} -def test_path_operation_img(): +def test_path_operation_img(client: TestClient): shutil.copy("./docs/en/docs/img/favicon.png", "./image.png") response = client.get("/items/foo?img=1") assert response.status_code == 200, response.text @@ -24,7 +39,7 @@ def test_path_operation_img(): os.remove("./image.png") -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py index e6da630e88..f9fd0d1af8 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py @@ -1,23 +1,38 @@ import gzip +import importlib import json import pytest from fastapi import Request from fastapi.testclient import TestClient -from docs_src.custom_request_and_route.tutorial001 import app +from tests.utils import needs_py39, needs_py310 -@app.get("/check-class") -async def check_gzip_request(request: Request): - return {"request_class": type(request).__name__} +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001"), + pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py310", marks=needs_py310), + pytest.param("tutorial001_an"), + pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}") + @mod.app.get("/check-class") + async def check_gzip_request(request: Request): + return {"request_class": type(request).__name__} -client = TestClient(app) + client = TestClient(mod.app) + return client @pytest.mark.parametrize("compress", [True, False]) -def test_gzip_request(compress): +def test_gzip_request(client: TestClient, compress): n = 1000 headers = {} body = [1] * n @@ -30,6 +45,6 @@ def test_gzip_request(compress): assert response.json() == {"sum": n} -def test_request_class(): +def test_request_class(client: TestClient): response = client.get("/check-class") assert response.json() == {"request_class": "GzipRequest"} diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py index 647f1c5ddf..c35752ed13 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py @@ -1,17 +1,36 @@ +import importlib + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.custom_request_and_route.tutorial002 import app - -client = TestClient(app) +from tests.utils import needs_py39, needs_py310 -def test_endpoint_works(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002"), + pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py310", marks=needs_py310), + pytest.param("tutorial002_an"), + pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_endpoint_works(client: TestClient): response = client.post("/", json=[1, 2, 3]) assert response.json() == 6 -def test_exception_handler_body_access(): +def test_exception_handler_body_access(client: TestClient): response = client.post("/", json={"numbers": [1, 2, 3]}) assert response.json() == IsDict( { diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py index db5dad7cf6..9e895b2daf 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py @@ -1,17 +1,32 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.custom_request_and_route.tutorial003 import app - -client = TestClient(app) +from tests.utils import needs_py310 -def test_get(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial003"), + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.custom_request_and_route.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_get(client: TestClient): response = client.get("/") assert response.json() == {"message": "Not timed"} assert "X-Response-Time" not in response.headers -def test_get_timed(): +def test_get_timed(client: TestClient): response = client.get("/timed") assert response.json() == {"message": "It's the time of my life"} assert "X-Response-Time" in response.headers diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index 762654d29d..b36dee7684 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -1,12 +1,28 @@ +import importlib + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.dataclasses.tutorial001 import app - -client = TestClient(app) +from tests.utils import needs_py310 -def test_post_item(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001"), + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_post_item(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": 3}) assert response.status_code == 200 assert response.json() == { @@ -17,7 +33,7 @@ def test_post_item(): } -def test_post_invalid_item(): +def test_post_invalid_item(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": "invalid price"}) assert response.status_code == 422 assert response.json() == IsDict( @@ -45,7 +61,7 @@ def test_post_invalid_item(): ) -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index e6d303cfc1..baaea45d8f 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -1,12 +1,29 @@ +import importlib + +import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from docs_src.dataclasses.tutorial002 import app - -client = TestClient(app) +from tests.utils import needs_py39, needs_py310 -def test_get_item(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial002"), + pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_get_item(client: TestClient): response = client.get("/items/next") assert response.status_code == 200 assert response.json() == { @@ -18,7 +35,7 @@ def test_get_item(): } -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index e1fa45201f..5728d2b6b3 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -1,13 +1,28 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.dataclasses.tutorial003 import app - -from ...utils import needs_pydanticv1, needs_pydanticv2 - -client = TestClient(app) +from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 -def test_post_authors_item(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial003"), + pytest.param("tutorial003_py39", marks=needs_py39), + pytest.param("tutorial003_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.dataclasses.{request.param}") + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_post_authors_item(client: TestClient): response = client.post( "/authors/foo/items/", json=[{"name": "Bar"}, {"name": "Baz", "description": "Drop the Baz"}], @@ -22,7 +37,7 @@ def test_post_authors_item(): } -def test_get_authors(): +def test_get_authors(client: TestClient): response = client.get("/authors/") assert response.status_code == 200 assert response.json() == [ @@ -54,7 +69,7 @@ def test_get_authors(): @needs_pydanticv2 -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { @@ -191,7 +206,7 @@ def test_openapi_schema(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 -def test_openapi_schema_pv1(): +def test_openapi_schema_pv1(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 assert response.json() == { diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index 73af420ae1..2df2b98893 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -1,12 +1,33 @@ +import importlib +from types import ModuleType + +import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.openapi_callbacks.tutorial001 import app, invoice_notification - -client = TestClient(app) +from tests.utils import needs_py310 -def test_get(): +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial001"), + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.openapi_callbacks.{request.param}") + return mod + + +@pytest.fixture(name="client") +def get_client(mod: ModuleType): + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_get(client: TestClient): response = client.post( "/invoices/", json={"id": "fooinvoice", "customer": "John", "total": 5.3} ) @@ -14,12 +35,12 @@ def test_get(): assert response.json() == {"msg": "Invoice received"} -def test_dummy_callback(): +def test_dummy_callback(mod: ModuleType): # Just for coverage - invoice_notification({}) + mod.invoice_notification({}) -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index 4f69e4646c..da5782d189 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -1,13 +1,30 @@ +import importlib + +import pytest from fastapi.testclient import TestClient -from docs_src.path_operation_advanced_configuration.tutorial004 import app - -from ...utils import needs_pydanticv1, needs_pydanticv2 - -client = TestClient(app) +from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 -def test_query_params_str_validations(): +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial004"), + pytest.param("tutorial004_py39", marks=needs_py39), + pytest.param("tutorial004_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.path_operation_advanced_configuration.{request.param}" + ) + + client = TestClient(mod.app) + client.headers.clear() + return client + + +def test_query_params_str_validations(client: TestClient): response = client.post("/items/", json={"name": "Foo", "price": 42}) assert response.status_code == 200, response.text assert response.json() == { @@ -20,7 +37,7 @@ def test_query_params_str_validations(): @needs_pydanticv2 -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { @@ -123,7 +140,7 @@ def test_openapi_schema(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 -def test_openapi_schema_pv1(): +def test_openapi_schema_pv1(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text assert response.json() == { diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index 8240b60a62..a90337a63d 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -1,14 +1,24 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 +from ...utils import needs_py39, needs_pydanticv2 -@pytest.fixture(name="client") -def get_client(): - from docs_src.path_operation_advanced_configuration.tutorial007 import app +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial007"), + pytest.param("tutorial007_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.path_operation_advanced_configuration.{request.param}" + ) - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py index ef012f8a6c..b38e4947cc 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py @@ -1,14 +1,24 @@ +import importlib + import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv1 +from ...utils import needs_py39, needs_pydanticv1 -@pytest.fixture(name="client") -def get_client(): - from docs_src.path_operation_advanced_configuration.tutorial007_pv1 import app +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial007_pv1"), + pytest.param("tutorial007_pv1_py39", marks=needs_py39), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module( + f"docs_src.path_operation_advanced_configuration.{request.param}" + ) - client = TestClient(app) + client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_response_directly/__init__.py b/tests/test_tutorial/test_response_directly/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py new file mode 100644 index 0000000000..2cc4f3b0c1 --- /dev/null +++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py @@ -0,0 +1,288 @@ +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="client", + params=[ + pytest.param("tutorial001"), + pytest.param("tutorial001_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.response_directly.{request.param}") + + client = TestClient(mod.app) + return client + + +def test_path_operation(client: TestClient): + response = client.put( + "/items/1", + json={ + "title": "Foo", + "timestamp": "2023-01-01T12:00:00", + "description": "A test item", + }, + ) + assert response.status_code == 200, response.text + assert response.json() == { + "description": "A test item", + "timestamp": "2023-01-01T12:00:00", + "title": "Foo", + } + + +@needs_pydanticv2 +def test_openapi_schema_pv2(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{id}": { + "put": { + "operationId": "update_item_items__id__put", + "parameters": [ + { + "in": "path", + "name": "id", + "required": True, + "schema": {"title": "Id", "type": "string"}, + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item", + }, + }, + }, + "required": True, + }, + "responses": { + "200": { + "content": { + "application/json": {"schema": {}}, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Update Item", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "Item": { + "properties": { + "description": { + "anyOf": [ + {"type": "string"}, + {"type": "null"}, + ], + "title": "Description", + }, + "timestamp": { + "format": "date-time", + "title": "Timestamp", + "type": "string", + }, + "title": {"title": "Title", "type": "string"}, + }, + "required": [ + "title", + "timestamp", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", + }, + }, + }, + } + + +@needs_pydanticv1 +def test_openapi_schema_pv1(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{id}": { + "put": { + "operationId": "update_item_items__id__put", + "parameters": [ + { + "in": "path", + "name": "id", + "required": True, + "schema": { + "title": "Id", + "type": "string", + }, + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Item", + }, + }, + }, + "required": True, + }, + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Update Item", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "Item": { + "properties": { + "description": { + "title": "Description", + "type": "string", + }, + "timestamp": { + "format": "date-time", + "title": "Timestamp", + "type": "string", + }, + "title": { + "title": "Title", + "type": "string", + }, + }, + "required": [ + "title", + "timestamp", + ], + "title": "Item", + "type": "object", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", + }, + }, + }, + } diff --git a/tests/test_tutorial/test_settings/test_app02.py b/tests/test_tutorial/test_settings/test_app02.py index eced88c044..5e1232ea01 100644 --- a/tests/test_tutorial/test_settings/test_app02.py +++ b/tests/test_tutorial/test_settings/test_app02.py @@ -1,20 +1,45 @@ +import importlib +from types import ModuleType + +import pytest from pytest import MonkeyPatch -from ...utils import needs_pydanticv2 +from ...utils import needs_py39, needs_pydanticv2 + + +@pytest.fixture( + name="mod_path", + params=[ + pytest.param("app02"), + pytest.param("app02_an"), + pytest.param("app02_an_py39", marks=needs_py39), + ], +) +def get_mod_path(request: pytest.FixtureRequest): + mod_path = f"docs_src.settings.{request.param}" + return mod_path + + +@pytest.fixture(name="main_mod") +def get_main_mod(mod_path: str) -> ModuleType: + main_mod = importlib.import_module(f"{mod_path}.main") + return main_mod + + +@pytest.fixture(name="test_main_mod") +def get_test_main_mod(mod_path: str) -> ModuleType: + test_main_mod = importlib.import_module(f"{mod_path}.test_main") + return test_main_mod @needs_pydanticv2 -def test_settings(monkeypatch: MonkeyPatch): - from docs_src.settings.app02 import main - +def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") - settings = main.get_settings() + settings = main_mod.get_settings() assert settings.app_name == "Awesome API" assert settings.items_per_user == 50 @needs_pydanticv2 -def test_override_settings(): - from docs_src.settings.app02 import test_main - - test_main.test_app() +def test_override_settings(test_main_mod: ModuleType): + test_main_mod.test_app() diff --git a/tests/test_tutorial/test_settings/test_app03.py b/tests/test_tutorial/test_settings/test_app03.py new file mode 100644 index 0000000000..d9872c15f2 --- /dev/null +++ b/tests/test_tutorial/test_settings/test_app03.py @@ -0,0 +1,59 @@ +import importlib +from types import ModuleType + +import pytest +from fastapi.testclient import TestClient +from pytest import MonkeyPatch + +from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2 + + +@pytest.fixture( + name="mod_path", + params=[ + pytest.param("app03"), + pytest.param("app03_an"), + pytest.param("app03_an_py39", marks=needs_py39), + ], +) +def get_mod_path(request: pytest.FixtureRequest): + mod_path = f"docs_src.settings.{request.param}" + return mod_path + + +@pytest.fixture(name="main_mod") +def get_main_mod(mod_path: str) -> ModuleType: + main_mod = importlib.import_module(f"{mod_path}.main") + return main_mod + + +@needs_pydanticv2 +def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): + monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") + settings = main_mod.get_settings() + assert settings.app_name == "Awesome API" + assert settings.admin_email == "admin@example.com" + assert settings.items_per_user == 50 + + +@needs_pydanticv1 +def test_settings_pv1(mod_path: str, monkeypatch: MonkeyPatch): + monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") + config_mod = importlib.import_module(f"{mod_path}.config_pv1") + settings = config_mod.Settings() + assert settings.app_name == "Awesome API" + assert settings.admin_email == "admin@example.com" + assert settings.items_per_user == 50 + + +@needs_pydanticv2 +def test_endpoint(main_mod: ModuleType, monkeypatch: MonkeyPatch): + monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") + client = TestClient(main_mod.app) + response = client.get("/info") + assert response.status_code == 200 + assert response.json() == { + "app_name": "Awesome API", + "admin_email": "admin@example.com", + "items_per_user": 50, + } diff --git a/tests/test_tutorial/test_using_request_directly/__init__.py b/tests/test_tutorial/test_using_request_directly/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py new file mode 100644 index 0000000000..54c53ae1e3 --- /dev/null +++ b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py @@ -0,0 +1,112 @@ +from fastapi.testclient import TestClient + +from docs_src.using_request_directly.tutorial001 import app + +client = TestClient(app) + + +def test_path_operation(): + response = client.get("/items/foo") + assert response.status_code == 200 + assert response.json() == {"client_host": "testclient", "item_id": "foo"} + + +def test_openapi(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "info": { + "title": "FastAPI", + "version": "0.1.0", + }, + "openapi": "3.1.0", + "paths": { + "/items/{item_id}": { + "get": { + "operationId": "read_root_items__item_id__get", + "parameters": [ + { + "in": "path", + "name": "item_id", + "required": True, + "schema": { + "title": "Item Id", + "type": "string", + }, + }, + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": {}, + }, + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError", + }, + }, + }, + "description": "Validation Error", + }, + }, + "summary": "Read Root", + }, + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError", + }, + "title": "Detail", + "type": "array", + }, + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string", + }, + { + "type": "integer", + }, + ], + }, + "title": "Location", + "type": "array", + }, + "msg": { + "title": "Message", + "type": "string", + }, + "type": { + "title": "Error Type", + "type": "string", + }, + }, + "required": [ + "loc", + "msg", + "type", + ], + "title": "ValidationError", + "type": "object", + }, + }, + }, + } diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py index dbcad3b02c..85efc18590 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial003.py +++ b/tests/test_tutorial/test_websockets/test_tutorial003.py @@ -1,16 +1,45 @@ +import importlib +from types import ModuleType + +import pytest from fastapi.testclient import TestClient -from docs_src.websockets.tutorial003 import app, html - -client = TestClient(app) +from ...utils import needs_py39 -def test_get(): +@pytest.fixture( + name="mod", + params=[ + pytest.param("tutorial003"), + pytest.param("tutorial003_py39", marks=needs_py39), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.websockets.{request.param}") + + return mod + + +@pytest.fixture(name="html") +def get_html(mod: ModuleType): + return mod.html + + +@pytest.fixture(name="client") +def get_client(mod: ModuleType): + client = TestClient(mod.app) + + return client + + +@needs_py39 +def test_get(client: TestClient, html: str): response = client.get("/") assert response.text == html -def test_websocket_handle_disconnection(): +@needs_py39 +def test_websocket_handle_disconnection(client: TestClient): with client.websocket_connect("/ws/1234") as connection, client.websocket_connect( "/ws/5678" ) as connection_two: diff --git a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py b/tests/test_tutorial/test_websockets/test_tutorial003_py39.py deleted file mode 100644 index 06c4a92796..0000000000 --- a/tests/test_tutorial/test_websockets/test_tutorial003_py39.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from ...utils import needs_py39 - - -@pytest.fixture(name="app") -def get_app(): - from docs_src.websockets.tutorial003_py39 import app - - return app - - -@pytest.fixture(name="html") -def get_html(): - from docs_src.websockets.tutorial003_py39 import html - - return html - - -@pytest.fixture(name="client") -def get_client(app: FastAPI): - client = TestClient(app) - - return client - - -@needs_py39 -def test_get(client: TestClient, html: str): - response = client.get("/") - assert response.text == html - - -@needs_py39 -def test_websocket_handle_disconnection(client: TestClient): - with client.websocket_connect("/ws/1234") as connection, client.websocket_connect( - "/ws/5678" - ) as connection_two: - connection.send_text("Hello from 1234") - data1 = connection.receive_text() - assert data1 == "You wrote: Hello from 1234" - data2 = connection_two.receive_text() - client1_says = "Client #1234 says: Hello from 1234" - assert data2 == client1_says - data1 = connection.receive_text() - assert data1 == client1_says - connection_two.close() - data1 = connection.receive_text() - assert data1 == "Client #5678 left the chat" From 71a17b5932e38bcf798fcd05c5bb384e6d4db5bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Dec 2025 08:55:57 +0000 Subject: [PATCH 031/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 60bc1cc6f0..2b8c727673 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 📝 Add variants for code examples in "Advanced User Guide". PR [#14413](https://github.com/fastapi/fastapi/pull/14413) by [@YuriiMotov](https://github.com/YuriiMotov). * 📝 Update tech stack in project generation docs. PR [#14472](https://github.com/fastapi/fastapi/pull/14472) by [@alejsdev](https://github.com/alejsdev). ### Internal From 42b250d14dd42d3c0c24dd085fa53878172a985f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 10 Dec 2025 02:36:29 -0800 Subject: [PATCH 032/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20handling=20arbitra?= =?UTF-8?q?ry=20types=20when=20using=20`arbitrary=5Ftypes=5Fallowed=3DTrue?= =?UTF-8?q?`=20(#14482)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/v2.py | 32 ++++++-- tests/test_arbitrary_types.py | 141 ++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 tests/test_arbitrary_types.py diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index acd23d8465..46a30b3ee8 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -1,7 +1,7 @@ import re import warnings from copy import copy, deepcopy -from dataclasses import dataclass +from dataclasses import dataclass, is_dataclass from enum import Enum from typing import ( Any, @@ -18,7 +18,7 @@ from typing import ( from fastapi._compat import may_v1, shared from fastapi.openapi.constants import REF_TEMPLATE from fastapi.types import IncEx, ModelNameMap, UnionType -from pydantic import BaseModel, TypeAdapter, create_model +from pydantic import BaseModel, ConfigDict, TypeAdapter, create_model from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation from pydantic import ValidationError as ValidationError @@ -64,6 +64,7 @@ class ModelField: field_info: FieldInfo name: str mode: Literal["validation", "serialization"] = "validation" + config: Union[ConfigDict, None] = None @property def alias(self) -> str: @@ -94,8 +95,14 @@ class ModelField: warnings.simplefilter( "ignore", category=UnsupportedFieldAttributeWarning ) + annotated_args = ( + self.field_info.annotation, + *self.field_info.metadata, + self.field_info, + ) self._type_adapter: TypeAdapter[Any] = TypeAdapter( - Annotated[self.field_info.annotation, self.field_info] + Annotated[annotated_args], + config=self.config, ) def get_default(self) -> Any: @@ -412,10 +419,21 @@ def create_body_model( def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: - return [ - ModelField(field_info=field_info, name=name) - for name, field_info in model.model_fields.items() - ] + model_fields: List[ModelField] = [] + for name, field_info in model.model_fields.items(): + type_ = field_info.annotation + if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_): + model_config = None + else: + model_config = model.model_config + model_fields.append( + ModelField( + field_info=field_info, + name=name, + config=model_config, + ) + ) + return model_fields # Duplicate of several schema functions from Pydantic v1 to make them compatible with diff --git a/tests/test_arbitrary_types.py b/tests/test_arbitrary_types.py new file mode 100644 index 0000000000..e5fa95ef22 --- /dev/null +++ b/tests/test_arbitrary_types.py @@ -0,0 +1,141 @@ +from typing import List + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from typing_extensions import Annotated + +from .utils import needs_pydanticv2 + + +@pytest.fixture(name="client") +def get_client(): + from pydantic import ( + BaseModel, + ConfigDict, + PlainSerializer, + TypeAdapter, + WithJsonSchema, + ) + + class FakeNumpyArray: + def __init__(self): + self.data = [1.0, 2.0, 3.0] + + FakeNumpyArrayPydantic = Annotated[ + FakeNumpyArray, + WithJsonSchema(TypeAdapter(List[float]).json_schema()), + PlainSerializer(lambda v: v.data), + ] + + class MyModel(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + custom_field: FakeNumpyArrayPydantic + + app = FastAPI() + + @app.get("/") + def test() -> MyModel: + return MyModel(custom_field=FakeNumpyArray()) + + client = TestClient(app) + return client + + +@needs_pydanticv2 +def test_get(client: TestClient): + response = client.get("/") + assert response.json() == {"custom_field": [1.0, 2.0, 3.0]} + + +@needs_pydanticv2 +def test_typeadapter(): + # This test is only to confirm that Pydantic alone is working as expected + from pydantic import ( + BaseModel, + ConfigDict, + PlainSerializer, + TypeAdapter, + WithJsonSchema, + ) + + class FakeNumpyArray: + def __init__(self): + self.data = [1.0, 2.0, 3.0] + + FakeNumpyArrayPydantic = Annotated[ + FakeNumpyArray, + WithJsonSchema(TypeAdapter(List[float]).json_schema()), + PlainSerializer(lambda v: v.data), + ] + + class MyModel(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) + custom_field: FakeNumpyArrayPydantic + + ta = TypeAdapter(MyModel) + assert ta.dump_python(MyModel(custom_field=FakeNumpyArray())) == { + "custom_field": [1.0, 2.0, 3.0] + } + assert ta.json_schema() == snapshot( + { + "properties": { + "custom_field": { + "items": {"type": "number"}, + "title": "Custom Field", + "type": "array", + } + }, + "required": ["custom_field"], + "title": "MyModel", + "type": "object", + } + ) + + +@needs_pydanticv2 +def test_openapi_schema(client: TestClient): + response = client.get("openapi.json") + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Test", + "operationId": "test__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyModel" + } + } + }, + } + }, + } + } + }, + "components": { + "schemas": { + "MyModel": { + "properties": { + "custom_field": { + "items": {"type": "number"}, + "type": "array", + "title": "Custom Field", + } + }, + "type": "object", + "required": ["custom_field"], + "title": "MyModel", + } + } + }, + } + ) From ae7af59c6d5921a3c700a9595788a444975a9d8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Dec 2025 10:36:56 +0000 Subject: [PATCH 033/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2b8c727673..e7dbadff7f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix handling arbitrary types when using `arbitrary_types_allowed=True`. PR [#14482](https://github.com/fastapi/fastapi/pull/14482) by [@tiangolo](https://github.com/tiangolo). + ### Docs * 📝 Add variants for code examples in "Advanced User Guide". PR [#14413](https://github.com/fastapi/fastapi/pull/14413) by [@YuriiMotov](https://github.com/YuriiMotov). From 60699f306b67dc1f4918ebea17f04d7a48cda645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 10 Dec 2025 11:38:41 +0100 Subject: [PATCH 034/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e7dbadff7f..08db56c8ec 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.124.1 + ### Fixes * 🐛 Fix handling arbitrary types when using `arbitrary_types_allowed=True`. PR [#14482](https://github.com/fastapi/fastapi/pull/14482) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 7009a7777b..2e7dba8a1b 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.124.0" +__version__ = "0.124.1" from starlette import status as status From 7ba042e069ad424a584a37f1db03887798d9af80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 10 Dec 2025 04:06:05 -0800 Subject: [PATCH 035/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20support=20for=20`i?= =?UTF-8?q?f=20TYPE=5FCHECKING`,=20=20non-evaluated=20stringified=20annota?= =?UTF-8?q?tions=20(#14485)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/utils.py | 19 +++-- .../test_stringified_annotation_dependency.py | 80 +++++++++++++++++++ 2 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 tests/test_stringified_annotation_dependency.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 23bca6f2a1..262dba6fdd 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -209,11 +209,21 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]: return path_params + query_params + header_params + cookie_params -def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: +def _get_signature(call: Callable[..., Any]) -> inspect.Signature: if sys.version_info >= (3, 10): - signature = inspect.signature(call, eval_str=True) + 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 + signature = inspect.signature(call) else: signature = inspect.signature(call) + return signature + + +def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: + signature = _get_signature(call) unwrapped = inspect.unwrap(call) globalns = getattr(unwrapped, "__globals__", {}) typed_params = [ @@ -239,10 +249,7 @@ def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: def get_typed_return_annotation(call: Callable[..., Any]) -> Any: - if sys.version_info >= (3, 10): - signature = inspect.signature(call, eval_str=True) - else: - signature = inspect.signature(call) + signature = _get_signature(call) unwrapped = inspect.unwrap(call) annotation = signature.return_annotation diff --git a/tests/test_stringified_annotation_dependency.py b/tests/test_stringified_annotation_dependency.py new file mode 100644 index 0000000000..89bb884b5c --- /dev/null +++ b/tests/test_stringified_annotation_dependency.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest +from fastapi import Depends, FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from typing_extensions import Annotated + +if TYPE_CHECKING: # pragma: no cover + from collections.abc import AsyncGenerator + + +class DummyClient: + async def get_people(self) -> list: + return ["John Doe", "Jane Doe"] + + async def close(self) -> None: + pass + + +async def get_client() -> AsyncGenerator[DummyClient, None]: + client = DummyClient() + yield client + await client.close() + + +Client = Annotated[DummyClient, Depends(get_client)] + + +@pytest.fixture(name="client") +def client_fixture() -> TestClient: + app = FastAPI() + + @app.get("/") + async def get_people(client: Client) -> list: + return await client.get_people() + + client = TestClient(app) + return client + + +def test_get(client: TestClient): + response = client.get("/") + assert response.status_code == 200, response.text + assert response.json() == ["John Doe", "Jane Doe"] + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/": { + "get": { + "summary": "Get People", + "operationId": "get_people__get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": {}, + "type": "array", + "title": "Response Get People Get", + } + } + }, + } + }, + } + } + }, + } + ) From 96bdde376f900d3c32c9e57f7cc16930fb29aa5e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Dec 2025 12:06:32 +0000 Subject: [PATCH 036/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 08db56c8ec..8e69b52f74 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix support for `if TYPE_CHECKING`, non-evaluated stringified annotations. PR [#14485](https://github.com/fastapi/fastapi/pull/14485) by [@tiangolo](https://github.com/tiangolo). + ## 0.124.1 ### Fixes From 7b0b915749582206025f306924e6a7bf86041a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 10 Dec 2025 13:07:53 +0100 Subject: [PATCH 037/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?4.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 8e69b52f74..a0d0a40379 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.124.2 + ### Fixes * 🐛 Fix support for `if TYPE_CHECKING`, non-evaluated stringified annotations. PR [#14485](https://github.com/fastapi/fastapi/pull/14485) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 2e7dba8a1b..8de426ad42 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.124.1" +__version__ = "0.124.2" from starlette import status as status From 442cb306f6bcffa3e3d7446a67800ad637829d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 10 Dec 2025 04:28:40 -0800 Subject: [PATCH 038/140] =?UTF-8?q?=F0=9F=94=A5=20Remove=20external=20link?= =?UTF-8?q?s=20section=20(#14486)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/data/external_links.yml | 430 -------------------------------- docs/en/docs/external-links.md | 28 +-- docs/en/docs/resources/index.md | 2 +- docs/en/mkdocs.yml | 1 - 4 files changed, 8 insertions(+), 453 deletions(-) delete mode 100644 docs/en/data/external_links.yml diff --git a/docs/en/data/external_links.yml b/docs/en/data/external_links.yml deleted file mode 100644 index 6e71ab9eb4..0000000000 --- a/docs/en/data/external_links.yml +++ /dev/null @@ -1,430 +0,0 @@ -Articles: - English: - - author: Apitally - author_link: https://apitally.io - link: https://apitally.io/blog/getting-started-with-logging-in-fastapi - title: Getting started with logging in FastAPI - - author: Balthazar Rouberol - author_link: https://balthazar-rouberol.com - link: https://blog.balthazar-rouberol.com/how-to-profile-a-fastapi-asynchronous-request - title: How to profile a FastAPI asynchronous request - - author: Stephen Siegert - Neon - link: https://neon.tech/blog/deploy-a-serverless-fastapi-app-with-neon-postgres-and-aws-app-runner-at-any-scale - title: Deploy a Serverless FastAPI App with Neon Postgres and AWS App Runner at any scale - - author: Kurtis Pykes - NVIDIA - link: https://developer.nvidia.com/blog/building-a-machine-learning-microservice-with-fastapi/ - title: Building a Machine Learning Microservice with FastAPI - - author: Ravgeet Dhillon - Twilio - link: https://www.twilio.com/en-us/blog/booking-appointments-twilio-notion-fastapi - title: Booking Appointments with Twilio, Notion, and FastAPI - - author: Abhinav Tripathi - Microsoft Blogs - link: https://devblogs.microsoft.com/cosmosdb/azure-cosmos-db-python-and-fastapi/ - title: Write a Python data layer with Azure Cosmos DB and FastAPI - - author: Donny Peeters - author_link: https://github.com/Donnype - link: https://bitestreams.com/blog/fastapi-sqlalchemy/ - title: 10 Tips for adding SQLAlchemy to FastAPI - - author: Jessica Temporal - author_link: https://jtemporal.com/socials - link: https://jtemporal.com/tips-on-migrating-from-flask-to-fastapi-and-vice-versa/ - title: Tips on migrating from Flask to FastAPI and vice-versa - - author: Ankit Anchlia - author_link: https://linkedin.com/in/aanchlia21 - link: https://hackernoon.com/explore-how-to-effectively-use-jwt-with-fastapi - title: Explore How to Effectively Use JWT With FastAPI - - author: Nicoló Lino - author_link: https://www.nlino.com - link: https://github.com/softwarebloat/python-tracing-demo - title: Instrument FastAPI with OpenTelemetry tracing and visualize traces in Grafana Tempo. - - author: Mikhail Rozhkov, Elena Samuylova - author_link: https://www.linkedin.com/in/mnrozhkov/ - link: https://www.evidentlyai.com/blog/fastapi-tutorial - title: ML serving and monitoring with FastAPI and Evidently - - author: Visual Studio Code Team - author_link: https://code.visualstudio.com/ - link: https://code.visualstudio.com/docs/python/tutorial-fastapi - title: FastAPI Tutorial in Visual Studio Code - - author: Apitally - author_link: https://apitally.io - link: https://blog.apitally.io/fastapi-application-monitoring-made-easy - title: FastAPI application monitoring made easy - - author: John Philip - author_link: https://medium.com/@amjohnphilip - link: https://python.plainenglish.io/building-a-restful-api-with-fastapi-secure-signup-and-login-functionality-included-45cdbcb36106 - title: "Building a RESTful API with FastAPI: Secure Signup and Login Functionality Included" - - author: Keshav Malik - author_link: https://theinfosecguy.xyz/ - link: https://blog.theinfosecguy.xyz/building-a-crud-api-with-fastapi-and-supabase-a-step-by-step-guide - title: Building a CRUD API with FastAPI and Supabase - - author: Adejumo Ridwan Suleiman - author_link: https://www.linkedin.com/in/adejumoridwan/ - link: https://medium.com/python-in-plain-english/build-an-sms-spam-classifier-serverless-database-with-faunadb-and-fastapi-23dbb275bc5b - title: Build an SMS Spam Classifier Serverless Database with FaunaDB and FastAPI - - author: Raf Rasenberg - author_link: https://rafrasenberg.com/about/ - link: https://rafrasenberg.com/fastapi-lambda/ - title: 'FastAPI lambda container: serverless simplified' - - author: Teresa N. Fontanella De Santis - author_link: https://dev.to/ - link: https://dev.to/teresafds/authorization-on-fastapi-with-casbin-41og - title: Authorization on FastAPI with Casbin - - author: New Relic - author_link: https://newrelic.com - link: https://newrelic.com/instant-observability/fastapi/e559ec64-f765-4470-a15f-1901fcebb468 - title: How to monitor FastAPI application performance using Python agent - - author: Jean-Baptiste Rocher - author_link: https://hashnode.com/@jibrocher - link: https://dev.indooroutdoor.io/series/fastapi-react-poll-app - title: Building the Poll App From Django Tutorial With FastAPI And React - - author: Silvan Melchior - author_link: https://github.com/silvanmelchior - link: https://blog.devgenius.io/seamless-fastapi-configuration-with-confz-90949c14ea12 - title: Seamless FastAPI Configuration with ConfZ - - author: Kaustubh Gupta - author_link: https://medium.com/@kaustubhgupta1828/ - link: https://levelup.gitconnected.com/5-advance-features-of-fastapi-you-should-try-7c0ac7eebb3e - title: 5 Advanced Features of FastAPI You Should Try - - author: Kaustubh Gupta - author_link: https://medium.com/@kaustubhgupta1828/ - link: https://www.analyticsvidhya.com/blog/2021/06/deploying-ml-models-as-api-using-fastapi-and-heroku/ - title: Deploying ML Models as API Using FastAPI and Heroku - - link: https://jarmos.netlify.app/posts/using-github-actions-to-deploy-a-fastapi-project-to-heroku/ - title: Using GitHub Actions to Deploy a FastAPI Project to Heroku - author_link: https://jarmos.netlify.app/ - author: Somraj Saha - - author: "@pystar" - author_link: https://pystar.substack.com/ - link: https://pystar.substack.com/p/how-to-create-a-fake-certificate - title: How to Create A Fake Certificate Authority And Generate TLS Certs for FastAPI - - author: Ben Gamble - author_link: https://uk.linkedin.com/in/bengamble7 - link: https://ably.com/blog/realtime-ticket-booking-solution-kafka-fastapi-ably - title: Building a realtime ticket booking solution with Kafka, FastAPI, and Ably - - author: Shahriyar(Shako) Rzayev - author_link: https://www.linkedin.com/in/shahriyar-rzayev/ - link: https://www.azepug.az/posts/fastapi/#building-simple-e-commerce-with-nuxtjs-and-fastapi-series - title: Building simple E-Commerce with NuxtJS and FastAPI - - author: Rodrigo Arenas - author_link: https://rodrigo-arenas.medium.com/ - link: https://medium.com/analytics-vidhya/serve-a-machine-learning-model-using-sklearn-fastapi-and-docker-85aabf96729b - title: "Serve a machine learning model using Sklearn, FastAPI and Docker" - - author: Yashasvi Singh - author_link: https://hashnode.com/@aUnicornDev - link: https://aunicorndev.hashnode.dev/series/supafast-api - title: "Building an API with FastAPI and Supabase and Deploying on Deta" - - author: Navule Pavan Kumar Rao - author_link: https://www.linkedin.com/in/navule/ - link: https://www.tutlinks.com/deploy-fastapi-on-ubuntu-gunicorn-caddy-2/ - title: Deploy FastAPI on Ubuntu and Serve using Caddy 2 Web Server - - author: Patrick Ladon - author_link: https://dev.to/factorlive - link: https://dev.to/factorlive/python-facebook-messenger-webhook-with-fastapi-on-glitch-4n90 - title: Python Facebook messenger webhook with FastAPI on Glitch - - author: Valon Januzaj - author_link: https://www.linkedin.com/in/valon-januzaj-b02692187/ - link: https://valonjanuzaj.medium.com/deploy-a-dockerized-fastapi-application-to-aws-cc757830ba1b - title: Deploy a dockerized FastAPI application to AWS - - author: Amit Chaudhary - author_link: https://x.com/amitness - link: https://amitness.com/2020/06/fastapi-vs-flask/ - title: FastAPI for Flask Users - - author: Louis Guitton - author_link: https://x.com/louis_guitton - link: https://guitton.co/posts/fastapi-monitoring/ - title: How to monitor your FastAPI service - - author: Precious Ndubueze - author_link: https://medium.com/@gabbyprecious2000 - link: https://medium.com/@gabbyprecious2000/creating-a-crud-app-with-fastapi-part-one-7c049292ad37 - title: Creating a CRUD App with FastAPI (Part one) - - author: Farhad Malik - author_link: https://medium.com/@farhadmalik - link: https://towardsdatascience.com/build-and-host-fast-data-science-applications-using-fastapi-823be8a1d6a0 - title: Build And Host Fast Data Science Applications Using FastAPI - - author: Navule Pavan Kumar Rao - author_link: https://www.linkedin.com/in/navule/ - link: https://www.tutlinks.com/deploy-fastapi-on-azure/ - title: Deploy FastAPI on Azure App Service - - author: Davide Fiocco - author_link: https://github.com/davidefiocco - link: https://davidefiocco.github.io/streamlit-fastapi-ml-serving/ - title: Machine learning model serving in Python using FastAPI and streamlit - - author: Netflix - author_link: https://netflixtechblog.com/ - link: https://netflixtechblog.com/introducing-dispatch-da4b8a2a8072 - title: Introducing Dispatch - - author: Stavros Korokithakis - author_link: https://x.com/Stavros - link: https://www.stavros.io/posts/fastapi-with-django/ - title: Using FastAPI with Django - - author: Twilio - author_link: https://www.twilio.com - link: https://www.twilio.com/blog/build-secure-twilio-webhook-python-fastapi - title: Build a Secure Twilio Webhook with Python and FastAPI - - author: Sebastián Ramírez (tiangolo) - author_link: https://x.com/tiangolo - link: https://dev.to/tiangolo/build-a-web-api-from-scratch-with-fastapi-the-workshop-2ehe - title: Build a web API from scratch with FastAPI - the workshop - - author: Paul Sec - author_link: https://x.com/PaulWebSec - link: https://paulsec.github.io/posts/fastapi_plus_zeit_serverless_fu/ - title: FastAPI + Zeit.co = 🚀 - - author: cuongld2 - author_link: https://dev.to/cuongld2 - link: https://dev.to/cuongld2/build-simple-api-service-with-python-fastapi-part-1-581o - title: Build simple API service with Python FastAPI — Part 1 - - author: Paurakh Sharma Humagain - author_link: https://x.com/PaurakhSharma - link: https://dev.to/paurakhsharma/microservice-in-python-using-fastapi-24cc - title: Microservice in Python using FastAPI - - author: Guillermo Cruz - author_link: https://wuilly.com/ - link: https://wuilly.com/2019/10/real-time-notifications-with-python-and-postgres/ - title: Real-time Notifications with Python and Postgres - - author: Navule Pavan Kumar Rao - author_link: https://www.linkedin.com/in/navule/ - link: https://www.tutlinks.com/create-and-deploy-fastapi-app-to-heroku/ - title: Create and Deploy FastAPI app to Heroku without using Docker - - author: Arthur Henrique - author_link: https://x.com/arthurheinrique - link: https://medium.com/@arthur393/another-boilerplate-to-fastapi-azure-pipeline-ci-pytest-3c8d9a4be0bb - title: 'Another Boilerplate to FastAPI: Azure Pipeline CI + Pytest' - - author: Shane Soh - author_link: https://medium.com/@shane.soh - link: https://medium.com/analytics-vidhya/deploy-machine-learning-models-with-keras-fastapi-redis-and-docker-4940df614ece - title: Deploy Machine Learning Models with Keras, FastAPI, Redis and Docker - - author: Mandy Gu - author_link: https://towardsdatascience.com/@mandygu - link: https://towardsdatascience.com/deploying-iris-classifications-with-fastapi-and-docker-7c9b83fdec3a - title: 'Towards Data Science: Deploying Iris Classifications with FastAPI and Docker' - - author: Michael Herman - author_link: https://testdriven.io/authors/herman - link: https://testdriven.io/blog/fastapi-crud/ - title: 'TestDriven.io: Developing and Testing an Asynchronous API with FastAPI and Pytest' - - author: Bernard Brenyah - author_link: https://medium.com/@bbrenyah - link: https://medium.com/python-data/how-to-deploy-tensorflow-2-0-models-as-an-api-service-with-fastapi-docker-128b177e81f3 - title: How To Deploy Tensorflow 2.0 Models As An API Service With FastAPI & Docker - - author: Dylan Anthony - author_link: https://dev.to/dbanty - link: https://dev.to/dbanty/why-i-m-leaving-flask-3ki6 - title: Why I'm Leaving Flask - - author: Mike Moritz - author_link: https://medium.com/@mike.p.moritz - link: https://medium.com/@mike.p.moritz/using-docker-compose-to-deploy-a-lightweight-python-rest-api-with-a-job-queue-37e6072a209b - title: Using Docker Compose to deploy a lightweight Python REST API with a job queue - - author: '@euri10' - author_link: https://gitlab.com/euri10 - link: https://gitlab.com/euri10/fastapi_cheatsheet - title: A FastAPI and Swagger UI visual cheatsheet - - author: Uber Engineering - author_link: https://eng.uber.com - link: https://eng.uber.com/ludwig-v0-2/ - title: 'Uber: Ludwig v0.2 Adds New Features and Other Improvements to its Deep Learning Toolbox [including a FastAPI server]' - - author: Maarten Grootendorst - author_link: https://www.linkedin.com/in/mgrootendorst/ - link: https://towardsdatascience.com/how-to-deploy-a-machine-learning-model-dc51200fe8cf - title: How to Deploy a Machine Learning Model - - author: Johannes Gontrum - author_link: https://x.com/gntrm - link: https://medium.com/@gntrm/jwt-authentication-with-fastapi-and-aws-cognito-1333f7f2729e - title: JWT Authentication with FastAPI and AWS Cognito - - author: Ankush Thakur - author_link: https://geekflare.com/author/ankush/ - link: https://geekflare.com/python-asynchronous-web-frameworks/ - title: Top 5 Asynchronous Web Frameworks for Python - - author: Nico Axtmann - author_link: https://www.linkedin.com/in/nico-axtmann - link: https://medium.com/@nico.axtmann95/deploying-a-scikit-learn-model-with-onnx-und-fastapi-1af398268915 - title: Deploying a scikit-learn model with ONNX and FastAPI - - author: Nils de Bruin - author_link: https://medium.com/@nilsdebruin - link: https://medium.com/data-rebels/fastapi-authentication-revisited-enabling-api-key-authentication-122dc5975680 - title: 'FastAPI authentication revisited: Enabling API key authentication' - - author: Nick Cortale - author_link: https://nickc1.github.io/ - link: https://nickc1.github.io/api,/scikit-learn/2019/01/10/scikit-fastapi.html - title: 'FastAPI and Scikit-Learn: Easily Deploy Models' - - author: Errieta Kostala - author_link: https://dev.to/errietta - link: https://dev.to/errietta/introduction-to-the-fastapi-python-framework-2n10 - title: Introduction to the fastapi python framework - - author: Nils de Bruin - author_link: https://medium.com/@nilsdebruin - link: https://medium.com/data-rebels/fastapi-how-to-add-basic-and-cookie-authentication-a45c85ef47d3 - title: FastAPI — How to add basic and cookie authentication - - author: Nils de Bruin - author_link: https://medium.com/@nilsdebruin - link: https://medium.com/data-rebels/fastapi-google-as-an-external-authentication-provider-3a527672cf33 - title: FastAPI — Google as an external authentication provider - - author: William Hayes - author_link: https://medium.com/@williamhayes - link: https://medium.com/@williamhayes/fastapi-starlette-debug-vs-prod-5f7561db3a59 - title: FastAPI/Starlette debug vs prod - - author: Mukul Mantosh - author_link: https://x.com/MantoshMukul - link: https://www.jetbrains.com/pycharm/guide/tutorials/fastapi-aws-kubernetes/ - title: Developing FastAPI Application using K8s & AWS - - author: KrishNa - author_link: https://medium.com/@krishnardt365 - link: https://medium.com/@krishnardt365/fastapi-docker-and-postgres-91943e71be92 - title: Fastapi, Docker(Docker compose) and Postgres - - author: Devon Ray - author_link: https://devonray.com - link: https://devonray.com/blog/deploying-a-fastapi-project-using-aws-lambda-aurora-cdk - title: Deployment using Docker, Lambda, Aurora, CDK & GH Actions - - author: Shubhendra Kushwaha - author_link: https://www.linkedin.com/in/theshubhendra/ - link: https://theshubhendra.medium.com/mastering-soft-delete-advanced-sqlalchemy-techniques-4678f4738947 - title: 'Mastering Soft Delete: Advanced SQLAlchemy Techniques' - - author: Shubhendra Kushwaha - author_link: https://www.linkedin.com/in/theshubhendra/ - link: https://theshubhendra.medium.com/role-based-row-filtering-advanced-sqlalchemy-techniques-733e6b1328f6 - title: 'Role based row filtering: Advanced SQLAlchemy Techniques' - German: - - author: Marcel Sander (actidoo) - author_link: https://www.actidoo.com - link: https://www.actidoo.com/de/blog/python-fastapi-domain-driven-design - title: Domain-driven Design mit Python und FastAPI - - author: Nico Axtmann - author_link: https://x.com/_nicoax - link: https://blog.codecentric.de/2019/08/inbetriebnahme-eines-scikit-learn-modells-mit-onnx-und-fastapi/ - title: Inbetriebnahme eines scikit-learn-Modells mit ONNX und FastAPI - - author: Felix Schürmeyer - author_link: https://hellocoding.de/autor/felix-schuermeyer/ - link: https://hellocoding.de/blog/coding-language/python/fastapi - title: REST-API Programmieren mittels Python und dem FastAPI Modul - Japanese: - - author: '@bee2' - author_link: https://qiita.com/bee2 - link: https://qiita.com/bee2/items/75d9c0d7ba20e7a4a0e9 - title: '[FastAPI] Python製のASGI Web フレームワーク FastAPIに入門する' - - author: '@bee2' - author_link: https://qiita.com/bee2 - link: https://qiita.com/bee2/items/0ad260ab9835a2087dae - title: PythonのWeb frameworkのパフォーマンス比較 (Django, Flask, responder, FastAPI, japronto) - - author: ライトコードメディア編集部 - author_link: https://rightcode.co.jp/author/jun - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-admin-page-improvement - title: '【第4回】FastAPIチュートリアル: toDoアプリを作ってみよう【管理者ページ改良編】' - - author: ライトコードメディア編集部 - author_link: https://rightcode.co.jp/author/jun - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-authentication-user-registration - title: '【第3回】FastAPIチュートリアル: toDoアプリを作ってみよう【認証・ユーザ登録編】' - - author: ライトコードメディア編集部 - author_link: https://rightcode.co.jp/author/jun - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-model-building - title: '【第2回】FastAPIチュートリアル: ToDoアプリを作ってみよう【モデル構築編】' - - author: ライトコードメディア編集部 - author_link: https://rightcode.co.jp/author/jun - link: https://rightcode.co.jp/blog/information-technology/fastapi-tutorial-todo-apps-environment - title: '【第1回】FastAPIチュートリアル: ToDoアプリを作ってみよう【環境構築編】' - - author: Hikaru Takahashi - author_link: https://qiita.com/hikarut - link: https://qiita.com/hikarut/items/b178af2e2440c67c6ac4 - title: フロントエンド開発者向けのDockerによるPython開発環境構築 - - author: '@angel_katayoku' - author_link: https://qiita.com/angel_katayoku - link: https://qiita.com/angel_katayoku/items/8a458a8952f50b73f420 - title: FastAPIでPOSTされたJSONのレスポンスbodyを受け取る - - author: '@angel_katayoku' - author_link: https://qiita.com/angel_katayoku - link: https://qiita.com/angel_katayoku/items/4fbc1a4e2b33fa2237d2 - title: FastAPIをMySQLと接続してDockerで管理してみる - - author: '@angel_katayoku' - author_link: https://qiita.com/angel_katayoku - link: https://qiita.com/angel_katayoku/items/0e1f5dbbe62efc612a78 - title: FastAPIでCORSを回避 - - author: '@ryoryomaru' - author_link: https://qiita.com/ryoryomaru - link: https://qiita.com/ryoryomaru/items/59958ed385b3571d50de - title: python製の最新APIフレームワーク FastAPI を触ってみた - - author: '@mtitg' - author_link: https://qiita.com/mtitg - link: https://qiita.com/mtitg/items/47770e9a562dd150631d - title: FastAPI|DB接続してCRUDするPython製APIサーバーを構築 - Portuguese: - - author: Eduardo Mendes - author_link: https://bolha.us/@dunossauro - link: https://fastapidozero.dunossauro.com/ - title: FastAPI do ZERO - - author: Jessica Temporal - author_link: https://jtemporal.com/socials - link: https://jtemporal.com/dicas-para-migrar-de-flask-para-fastapi-e-vice-versa/ - title: Dicas para migrar uma aplicação de Flask para FastAPI e vice-versa - Russian: - - author: Troy Köhler - author_link: https://www.linkedin.com/in/trkohler/ - link: https://trkohler.com/fast-api-introduction-to-framework - title: 'FastAPI: знакомимся с фреймворком' - - author: prostomarkeloff - author_link: https://github.com/prostomarkeloff - link: https://habr.com/ru/post/478620/ - title: Почему Вы должны попробовать FastAPI? - - author: Andrey Korchak - author_link: https://habr.com/ru/users/57uff3r/ - link: https://habr.com/ru/post/454440/ - title: 'Мелкая питонячая радость #2: Starlette - Солидная примочка – FastAPI' - Vietnamese: - - author: Nguyễn Nhân - author_link: https://fullstackstation.com/author/figonking/ - link: https://fullstackstation.com/fastapi-trien-khai-bang-docker/ - title: 'FASTAPI: TRIỂN KHAI BẰNG DOCKER' - Taiwanese: - - author: Leon - author_link: http://editor.leonh.space/ - link: https://editor.leonh.space/2022/tortoise/ - title: 'Tortoise ORM / FastAPI 整合快速筆記' - Spanish: - - author: Eduardo Zepeda - author_link: https://coffeebytes.dev/en/authors/eduardo-zepeda/ - link: https://coffeebytes.dev/es/python-fastapi-el-mejor-framework-de-python/ - title: 'Tutorial de FastAPI, ¿el mejor framework de Python?' -Podcasts: - English: - - author: Behind the Commit - author_link: https://www.youtube.com/@BehindtheCommit - link: https://youtu.be/iaDRYUQ0OMM - title: Why FastAPI Became Python’s Fastest‑Growing Framework – Chat with Sebastián Ramírez - - author: Real Python - author_link: https://realpython.com/ - link: https://realpython.com/podcasts/rpp/72/ - title: Starting With FastAPI and Examining Python's Import System - Episode 72 - - author: Python Bytes FM - author_link: https://pythonbytes.fm/ - link: https://www.pythonpodcast.com/fastapi-web-application-framework-episode-259/ - title: 'Do you dare to press "."? - Episode 247 - Dan #6: SQLModel - use the same models for SQL and FastAPI' - - author: Podcast.`__init__` - author_link: https://www.pythonpodcast.com/ - link: https://www.pythonpodcast.com/fastapi-web-application-framework-episode-259/ - title: Build The Next Generation Of Python Web Applications With FastAPI - Episode 259 - interview to Sebastían Ramírez (tiangolo) - - author: Python Bytes FM - author_link: https://pythonbytes.fm/ - link: https://pythonbytes.fm/episodes/show/123/time-to-right-the-py-wrongs?time_in_sec=855 - title: FastAPI on PythonBytes -Talks: - English: - - author: Sebastián Ramírez (tiangolo) - author_link: https://x.com/tiangolo - link: https://www.youtube.com/watch?v=mwvmfl8nN_U - title: 'Keynote: Behind the scenes of FastAPI and friends for developers and builders — Sebastián Ramírez' - - author: Jeny Sadadia - author_link: https://github.com/JenySadadia - link: https://www.youtube.com/watch?v=uZdTe8_Z6BQ - title: 'PyCon AU 2023: Testing asynchronous applications with FastAPI and pytest' - - author: Sebastián Ramírez (tiangolo) - author_link: https://x.com/tiangolo - link: https://www.youtube.com/watch?v=PnpTY1f4k2U - title: '[VIRTUAL] Py.Amsterdam''s flying Software Circus: Intro to FastAPI' - - author: Sebastián Ramírez (tiangolo) - author_link: https://x.com/tiangolo - link: https://www.youtube.com/watch?v=z9K5pwb0rt8 - title: 'PyConBY 2020: Serve ML models easily with FastAPI' - - author: Chris Withers - author_link: https://x.com/chriswithers13 - link: https://www.youtube.com/watch?v=3DLwPcrE5mA - title: 'PyCon UK 2019: FastAPI from the ground up' - Taiwanese: - - author: Blueswen - author_link: https://github.com/blueswen - link: https://www.youtube.com/watch?v=y3sumuoDq4w - title: 'PyCon TW 2024: 全方位強化 Python 服務可觀測性:以 FastAPI 和 Grafana Stack 為例' diff --git a/docs/en/docs/external-links.md b/docs/en/docs/external-links.md index 3ed04e5c55..481cf1d7f3 100644 --- a/docs/en/docs/external-links.md +++ b/docs/en/docs/external-links.md @@ -1,36 +1,22 @@ -# External Links and Articles +# External Links **FastAPI** has a great community constantly growing. There are many posts, articles, tools, and projects, related to **FastAPI**. -Here's an incomplete list of some of them. +You could easily use a search engine or video platform to find many resources related to FastAPI. -/// tip +/// info -If you have an article, project, tool, or anything related to **FastAPI** that is not yet listed here, create a Pull Request adding it. +Before, this page used to list links to external articles. + +But now that FastAPI is the backend framework with the most GitHub stars across languages, and the most starred and used framework in Python, it no longer makes sense to attempt to list all articles written about it. /// -{% for section_name, section_content in external_links.items() %} - -## {{ section_name }} - -{% for lang_name, lang_content in section_content.items() %} - -### {{ lang_name }} - -{% for item in lang_content %} - -* {{ item.title }} by {{ item.author }}. - -{% endfor %} -{% endfor %} -{% endfor %} - ## GitHub Repositories -Most starred GitHub repositories with the topic `fastapi`: +Most starred GitHub repositories with the topic `fastapi`: {% for repo in topic_repos %} diff --git a/docs/en/docs/resources/index.md b/docs/en/docs/resources/index.md index caefdf1254..f7d48576ff 100644 --- a/docs/en/docs/resources/index.md +++ b/docs/en/docs/resources/index.md @@ -1,3 +1,3 @@ # Resources { #resources } -Additional resources, external links, articles and more. ✈️ +Additional resources, external links, and more. ✈️ diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index fd346a3d38..0e0adab9b2 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -59,7 +59,6 @@ plugins: search: null macros: include_yaml: - - external_links: ../en/data/external_links.yml - github_sponsors: ../en/data/github_sponsors.yml - people: ../en/data/people.yml - contributors: ../en/data/contributors.yml From 4a98a66778987648d2195155c9cbe0824d67b02a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Dec 2025 12:29:04 +0000 Subject: [PATCH 039/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a0d0a40379..5af3540e9c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 🔥 Remove external links section. PR [#14486](https://github.com/fastapi/fastapi/pull/14486) by [@tiangolo](https://github.com/tiangolo). + ## 0.124.2 ### Fixes From cd9d093f603b81f34eb2751e9ca4522089fa9696 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 10 Dec 2025 04:56:50 -0800 Subject: [PATCH 040/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20about=20?= =?UTF-8?q?re-raising=20validation=20errors,=20do=20not=20include=20string?= =?UTF-8?q?=20as=20is=20to=20not=20leak=20information=20(#14487)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/tutorial/handling-errors.md | 35 +++++++------------ docs_src/handling_errors/tutorial004.py | 7 ++-- .../test_handling_errors/test_tutorial004.py | 14 ++------ 3 files changed, 19 insertions(+), 37 deletions(-) diff --git a/docs/en/docs/tutorial/handling-errors.md b/docs/en/docs/tutorial/handling-errors.md index 53501837c9..4092039b16 100644 --- a/docs/en/docs/tutorial/handling-errors.md +++ b/docs/en/docs/tutorial/handling-errors.md @@ -127,7 +127,7 @@ To override it, import the `RequestValidationError` and use it with `@app.except The exception handler will receive a `Request` and the exception. -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *} Now, if you go to `/items/foo`, instead of getting the default JSON error with: @@ -149,36 +149,17 @@ Now, if you go to `/items/foo`, instead of getting the default JSON error with: you will get a text version, with: ``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer ``` -#### `RequestValidationError` vs `ValidationError` { #requestvalidationerror-vs-validationerror } - -/// warning - -These are technical details that you might skip if it's not important for you now. - -/// - -`RequestValidationError` is a sub-class of Pydantic's `ValidationError`. - -**FastAPI** uses it so that, if you use a Pydantic model in `response_model`, and your data has an error, you will see the error in your log. - -But the client/user will not see it. Instead, the client will receive an "Internal Server Error" with an HTTP status code `500`. - -It should be this way because if you have a Pydantic `ValidationError` in your *response* or anywhere in your code (not in the client's *request*), it's actually a bug in your code. - -And while you fix it, your clients/users shouldn't have access to internal information about the error, as that could expose a security vulnerability. - ### Override the `HTTPException` error handler { #override-the-httpexception-error-handler } The same way, you can override the `HTTPException` handler. For example, you could want to return a plain text response instead of JSON for these errors: -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *} /// note | Technical Details @@ -188,6 +169,14 @@ You could also use `from starlette.responses import PlainTextResponse`. /// +/// warning + +Have in mind that the `RequestValidationError` contains the information of the file name and line where the validation error happens so that you can show it in your logs with the relevant information if you want to. + +But that means that if you just convert it to a string and return that information directly, you could be leaking a bit of information about your system, that's why here the code extracts and shows each error independently. + +/// + ### Use the `RequestValidationError` body { #use-the-requestvalidationerror-body } The `RequestValidationError` contains the `body` it received with invalid data. diff --git a/docs_src/handling_errors/tutorial004.py b/docs_src/handling_errors/tutorial004.py index 300a3834ff..ae50807e97 100644 --- a/docs_src/handling_errors/tutorial004.py +++ b/docs_src/handling_errors/tutorial004.py @@ -12,8 +12,11 @@ async def http_exception_handler(request, exc): @app.exception_handler(RequestValidationError) -async def validation_exception_handler(request, exc): - return PlainTextResponse(str(exc), status_code=400) +async def validation_exception_handler(request, exc: RequestValidationError): + message = "Validation errors:" + for error in exc.errors(): + message += f"\nField: {error['loc']}, Error: {error['msg']}" + return PlainTextResponse(message, status_code=400) @app.get("/items/{item_id}") diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py index 217159a596..c04bf37245 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py @@ -8,18 +8,8 @@ client = TestClient(app) def test_get_validation_error(): response = client.get("/items/foo") assert response.status_code == 400, response.text - # TODO: remove when deprecating Pydantic v1 - assert ( - # TODO: remove when deprecating Pydantic v1 - "path -> item_id" in response.text - or "'loc': ('path', 'item_id')" in response.text - ) - assert ( - # TODO: remove when deprecating Pydantic v1 - "value is not a valid integer" in response.text - or "Input should be a valid integer, unable to parse string as an integer" - in response.text - ) + assert "Validation errors:" in response.text + assert "Field: ('path', 'item_id')" in response.text def test_get_http_error(): From 30747a69c842ffa29084b811b69e1629f2bbb4bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Dec 2025 12:57:18 +0000 Subject: [PATCH 041/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5af3540e9c..81eeb9f99f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 📝 Update docs about re-raising validation errors, do not include string as is to not leak information. PR [#14487](https://github.com/fastapi/fastapi/pull/14487) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove external links section. PR [#14486](https://github.com/fastapi/fastapi/pull/14486) by [@tiangolo](https://github.com/tiangolo). ## 0.124.2 From 4a9f13763dee116092b610ef5b3a86c71b2d55b8 Mon Sep 17 00:00:00 2001 From: Nils-Hero Lindemann Date: Wed, 10 Dec 2025 14:54:34 +0100 Subject: [PATCH 042/140] =?UTF-8?q?=F0=9F=8C=90=20Sync=20German=20docs=20(?= =?UTF-8?q?#14488)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Sync with #14472 * Sync with #14413 * Sync with #14486 * Sync with #14487 --- docs/de/docs/advanced/additional-responses.md | 4 +- docs/de/docs/advanced/dataclasses.md | 6 +- docs/de/docs/advanced/openapi-callbacks.md | 8 +- .../path-operation-advanced-configuration.md | 10 +-- docs/de/docs/advanced/response-directly.md | 2 +- docs/de/docs/advanced/settings.md | 8 +- docs/de/docs/how-to/configure-swagger-ui.md | 2 +- .../docs/how-to/custom-request-and-route.md | 12 +-- docs/de/docs/project-generation.md | 18 ++--- docs/de/docs/resources/index.md | 2 +- docs/de/docs/tutorial/bigger-applications.md | 78 ++++--------------- docs/de/docs/tutorial/cookie-param-models.md | 2 +- docs/de/docs/tutorial/handling-errors.md | 35 +++------ docs/de/docs/tutorial/testing.md | 54 +------------ 14 files changed, 64 insertions(+), 177 deletions(-) diff --git a/docs/de/docs/advanced/additional-responses.md b/docs/de/docs/advanced/additional-responses.md index 218dd6c4fb..29a0a14777 100644 --- a/docs/de/docs/advanced/additional-responses.md +++ b/docs/de/docs/advanced/additional-responses.md @@ -175,7 +175,7 @@ Sie können denselben `responses`-Parameter verwenden, um verschiedene Medientyp Sie können beispielsweise einen zusätzlichen Medientyp `image/png` hinzufügen und damit deklarieren, dass Ihre *Pfadoperation* ein JSON-Objekt (mit dem Medientyp `application/json`) oder ein PNG-Bild zurückgeben kann: -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} /// note | Hinweis @@ -237,7 +237,7 @@ Mit dieser Technik können Sie einige vordefinierte Responses in Ihren *Pfadoper Zum Beispiel: -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} ## Weitere Informationen zu OpenAPI-Responses { #more-information-about-openapi-responses } diff --git a/docs/de/docs/advanced/dataclasses.md b/docs/de/docs/advanced/dataclasses.md index 12ea8e9eca..e2d59c776e 100644 --- a/docs/de/docs/advanced/dataclasses.md +++ b/docs/de/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI basiert auf **Pydantic**, und ich habe Ihnen gezeigt, wie Sie Pydantic-M Aber FastAPI unterstützt auf die gleiche Weise auch die Verwendung von `dataclasses`: -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} Das ist dank **Pydantic** ebenfalls möglich, da es `dataclasses` intern unterstützt. @@ -32,7 +32,7 @@ Wenn Sie jedoch eine Menge Datenklassen herumliegen haben, ist dies ein guter Tr Sie können `dataclasses` auch im Parameter `response_model` verwenden: -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} Die Datenklasse wird automatisch in eine Pydantic-Datenklasse konvertiert. @@ -48,7 +48,7 @@ In einigen Fällen müssen Sie möglicherweise immer noch Pydantics Version von In diesem Fall können Sie einfach die Standard-`dataclasses` durch `pydantic.dataclasses` ersetzen, was einen direkten Ersatz darstellt: -{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} +{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Wir importieren `field` weiterhin von Standard-`dataclasses`. diff --git a/docs/de/docs/advanced/openapi-callbacks.md b/docs/de/docs/advanced/openapi-callbacks.md index afc48bbb82..fd68ab8dca 100644 --- a/docs/de/docs/advanced/openapi-callbacks.md +++ b/docs/de/docs/advanced/openapi-callbacks.md @@ -31,7 +31,7 @@ Sie verfügt über eine *Pfadoperation*, die einen `Invoice`-Body empfängt, und Dieser Teil ist ziemlich normal, der größte Teil des Codes ist Ihnen wahrscheinlich bereits bekannt: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} /// tip | Tipp @@ -90,7 +90,7 @@ Wenn Sie diese Sichtweise (des *externen Entwicklers*) vorübergehend übernehme Erstellen Sie zunächst einen neuen `APIRouter`, der einen oder mehrere Callbacks enthält. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} ### Die Callback-*Pfadoperation* erstellen { #create-the-callback-path-operation } @@ -101,7 +101,7 @@ Sie sollte wie eine normale FastAPI-*Pfadoperation* aussehen: * Sie sollte wahrscheinlich eine Deklaration des Bodys enthalten, die sie erhalten soll, z. B. `body: InvoiceEvent`. * Und sie könnte auch eine Deklaration der Response enthalten, die zurückgegeben werden soll, z. B. `response_model=InvoiceEventReceived`. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} Es gibt zwei Hauptunterschiede zu einer normalen *Pfadoperation*: @@ -169,7 +169,7 @@ An diesem Punkt haben Sie die benötigte(n) *Callback-Pfadoperation(en)* (diejen Verwenden Sie nun den Parameter `callbacks` im *Pfadoperation-Dekorator Ihrer API*, um das Attribut `.routes` (das ist eigentlich nur eine `list`e von Routen/*Pfadoperationen*) dieses Callback-Routers zu übergeben: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} /// tip | Tipp diff --git a/docs/de/docs/advanced/path-operation-advanced-configuration.md b/docs/de/docs/advanced/path-operation-advanced-configuration.md index f5ec7c49ea..bad768feb5 100644 --- a/docs/de/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/de/docs/advanced/path-operation-advanced-configuration.md @@ -50,7 +50,7 @@ Das Hinzufügen eines `\f` (ein maskiertes „Form Feed“-Zeichen) führt dazu, Sie wird nicht in der Dokumentation angezeigt, aber andere Tools (z. B. Sphinx) können den Rest verwenden. -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} ## Zusätzliche Responses { #additional-responses } @@ -155,13 +155,13 @@ In der folgenden Anwendung verwenden wir beispielsweise weder die integrierte Fu //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} //// @@ -179,13 +179,13 @@ Und dann parsen wir in unserem Code diesen YAML-Inhalt direkt und verwenden dann //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} //// diff --git a/docs/de/docs/advanced/response-directly.md b/docs/de/docs/advanced/response-directly.md index d995173730..06ec2c32ed 100644 --- a/docs/de/docs/advanced/response-directly.md +++ b/docs/de/docs/advanced/response-directly.md @@ -34,7 +34,7 @@ Sie können beispielsweise kein Pydantic-Modell in eine `JSONResponse` einfügen In diesen Fällen können Sie den `jsonable_encoder` verwenden, um Ihre Daten zu konvertieren, bevor Sie sie an eine Response übergeben: -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} /// note | Technische Details diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md index ccd7f373dd..03263a28b0 100644 --- a/docs/de/docs/advanced/settings.md +++ b/docs/de/docs/advanced/settings.md @@ -148,7 +148,7 @@ Dies könnte besonders beim Testen nützlich sein, da es sehr einfach ist, eine Ausgehend vom vorherigen Beispiel könnte Ihre Datei `config.py` so aussehen: -{* ../../docs_src/settings/app02/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} Beachten Sie, dass wir jetzt keine Standardinstanz `settings = Settings()` erstellen. @@ -174,7 +174,7 @@ Und dann können wir das von der *Pfadoperation-Funktion* als Abhängigkeit einf Dann wäre es sehr einfach, beim Testen ein anderes Einstellungsobjekt bereitzustellen, indem man eine Abhängigkeitsüberschreibung für `get_settings` erstellt: -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} Bei der Abhängigkeitsüberschreibung legen wir einen neuen Wert für `admin_email` fest, wenn wir das neue `Settings`-Objekt erstellen, und geben dann dieses neue Objekt zurück. @@ -217,7 +217,7 @@ Und dann aktualisieren Sie Ihre `config.py` mit: //// tab | Pydantic v2 -{* ../../docs_src/settings/app03_an/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Tipp @@ -229,7 +229,7 @@ Das Attribut `model_config` wird nur für die Pydantic-Konfiguration verwendet. //// tab | Pydantic v1 -{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} +{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} /// tip | Tipp diff --git a/docs/de/docs/how-to/configure-swagger-ui.md b/docs/de/docs/how-to/configure-swagger-ui.md index 351cb996c5..3616f03ac4 100644 --- a/docs/de/docs/how-to/configure-swagger-ui.md +++ b/docs/de/docs/how-to/configure-swagger-ui.md @@ -40,7 +40,7 @@ FastAPI enthält einige Defaultkonfigurationsparameter, die für die meisten Anw Es umfasst die folgenden Defaultkonfigurationen: -{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} Sie können jede davon überschreiben, indem Sie im Argument `swagger_ui_parameters` einen anderen Wert festlegen. diff --git a/docs/de/docs/how-to/custom-request-and-route.md b/docs/de/docs/how-to/custom-request-and-route.md index 246717c04d..017de20967 100644 --- a/docs/de/docs/how-to/custom-request-and-route.md +++ b/docs/de/docs/how-to/custom-request-and-route.md @@ -42,7 +42,7 @@ Wenn der Header kein `gzip` enthält, wird nicht versucht, den Body zu dekomprim Auf diese Weise kann dieselbe Routenklasse gzip-komprimierte oder unkomprimierte Requests verarbeiten. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} ### Eine benutzerdefinierte `GzipRoute`-Klasse erstellen { #create-a-custom-gziproute-class } @@ -54,7 +54,7 @@ Diese Methode gibt eine Funktion zurück. Und diese Funktion empfängt einen ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | Tipp - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app/dependencies.py!} -``` - -//// +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Tipp @@ -181,9 +149,7 @@ Wir wissen, dass alle *Pfadoperationen* in diesem Modul folgendes haben: Anstatt also alles zu jeder *Pfadoperation* hinzuzufügen, können wir es dem `APIRouter` hinzufügen. -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Da der Pfad jeder *Pfadoperation* mit `/` beginnen muss, wie in: @@ -242,9 +208,7 @@ Und wir müssen die Abhängigkeitsfunktion aus dem Modul `app.dependencies` impo Daher verwenden wir einen relativen Import mit `..` für die Abhängigkeiten: -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} #### Wie relative Importe funktionieren { #how-relative-imports-work } @@ -315,9 +279,7 @@ Wir fügen weder das Präfix `/items` noch `tags=["items"]` zu jeder *Pfadoperat Aber wir können immer noch _mehr_ `tags` hinzufügen, die auf eine bestimmte *Pfadoperation* angewendet werden, sowie einige zusätzliche `responses`, die speziell für diese *Pfadoperation* gelten: -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Tipp @@ -343,17 +305,13 @@ Sie importieren und erstellen wie gewohnt eine `FastAPI`-Klasse. Und wir können sogar [globale Abhängigkeiten](dependencies/global-dependencies.md){.internal-link target=_blank} deklarieren, die mit den Abhängigkeiten für jeden `APIRouter` kombiniert werden: -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} ### Den `APIRouter` importieren { #import-the-apirouter } Jetzt importieren wir die anderen Submodule, die `APIRouter` haben: -```Python hl_lines="4-5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} Da es sich bei den Dateien `app/routers/users.py` und `app/routers/items.py` um Submodule handelt, die Teil desselben Python-Packages `app` sind, können wir einen einzelnen Punkt `.` verwenden, um sie mit „relativen Imports“ zu importieren. @@ -416,17 +374,13 @@ würde der `router` von `users` den von `items` überschreiben und wir könnten Um also beide in derselben Datei verwenden zu können, importieren wir die Submodule direkt: -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} ### Die `APIRouter` für `users` und `items` inkludieren { #include-the-apirouters-for-users-and-items } Inkludieren wir nun die `router` aus diesen Submodulen `users` und `items`: -```Python hl_lines="10-11" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} /// info | Info @@ -466,17 +420,13 @@ Sie enthält einen `APIRouter` mit einigen administrativen *Pfadoperationen*, di In diesem Beispiel wird es ganz einfach sein. Nehmen wir jedoch an, dass wir, da sie mit anderen Projekten in der Organisation geteilt wird, sie nicht ändern und kein `prefix`, `dependencies`, `tags`, usw. direkt zum `APIRouter` hinzufügen können: -```Python hl_lines="3" title="app/internal/admin.py" -{!../../docs_src/bigger_applications/app/internal/admin.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} Aber wir möchten immer noch ein benutzerdefiniertes `prefix` festlegen, wenn wir den `APIRouter` einbinden, sodass alle seine *Pfadoperationen* mit `/admin` beginnen, wir möchten es mit den `dependencies` sichern, die wir bereits für dieses Projekt haben, und wir möchten `tags` und `responses` hinzufügen. Wir können das alles deklarieren, ohne den ursprünglichen `APIRouter` ändern zu müssen, indem wir diese Parameter an `app.include_router()` übergeben: -```Python hl_lines="14-17" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} Auf diese Weise bleibt der ursprüngliche `APIRouter` unverändert, sodass wir dieselbe `app/internal/admin.py`-Datei weiterhin mit anderen Projekten in der Organisation teilen können. @@ -497,9 +447,7 @@ Wir können *Pfadoperationen* auch direkt zur `FastAPI`-App hinzufügen. Hier machen wir es ... nur um zu zeigen, dass wir es können 🤷: -```Python hl_lines="21-23" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} und es wird korrekt funktionieren, zusammen mit allen anderen *Pfadoperationen*, die mit `app.include_router()` hinzugefügt wurden. diff --git a/docs/de/docs/tutorial/cookie-param-models.md b/docs/de/docs/tutorial/cookie-param-models.md index 2baf3d70dd..25718bd33a 100644 --- a/docs/de/docs/tutorial/cookie-param-models.md +++ b/docs/de/docs/tutorial/cookie-param-models.md @@ -50,7 +50,7 @@ Ihre API hat jetzt die Macht, ihre eigene Response**. diff --git a/docs/de/docs/tutorial/handling-errors.md b/docs/de/docs/tutorial/handling-errors.md index 58e4607c5a..a39c3db37a 100644 --- a/docs/de/docs/tutorial/handling-errors.md +++ b/docs/de/docs/tutorial/handling-errors.md @@ -127,7 +127,7 @@ Um diesen zu überschreiben, importieren Sie den `RequestValidationError` und ve Der Exceptionhandler erhält einen `Request` und die Exception. -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *} Wenn Sie nun zu `/items/foo` gehen, erhalten Sie anstelle des standardmäßigen JSON-Fehlers mit: @@ -149,36 +149,17 @@ Wenn Sie nun zu `/items/foo` gehen, erhalten Sie anstelle des standardmäßigen eine Textversion mit: ``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer ``` -#### `RequestValidationError` vs. `ValidationError` { #requestvalidationerror-vs-validationerror } - -/// warning | Achtung - -Dies sind technische Details, die Sie überspringen können, wenn sie für Sie jetzt nicht wichtig sind. - -/// - -`RequestValidationError` ist eine Unterklasse von Pydantics `ValidationError`. - -**FastAPI** verwendet diesen so, dass, wenn Sie ein Pydantic-Modell in `response_model` verwenden und Ihre Daten einen Fehler haben, Sie den Fehler in Ihrem Log sehen. - -Aber der Client/Benutzer wird ihn nicht sehen. Stattdessen erhält der Client einen „Internal Server Error“ mit einem HTTP-Statuscode `500`. - -Es sollte so sein, denn wenn Sie einen Pydantic `ValidationError` in Ihrer *Response* oder irgendwo anders in Ihrem Code haben (nicht im *Request* des Clients), ist es tatsächlich ein Fehler in Ihrem Code. - -Und während Sie den Fehler beheben, sollten Ihre Clients/Benutzer keinen Zugriff auf interne Informationen über den Fehler haben, da das eine Sicherheitslücke aufdecken könnte. - ### Überschreiben des `HTTPException`-Fehlerhandlers { #override-the-httpexception-error-handler } Auf die gleiche Weise können Sie den `HTTPException`-Handler überschreiben. Zum Beispiel könnten Sie eine Klartext-Response statt JSON für diese Fehler zurückgeben wollen: -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *} /// note | Technische Details @@ -188,6 +169,14 @@ Sie könnten auch `from starlette.responses import PlainTextResponse` verwenden. /// +/// warning | Achtung + +Beachten Sie, dass der `RequestValidationError` Informationen über den Dateinamen und die Zeile enthält, in der der Validierungsfehler auftritt, sodass Sie ihn bei Bedarf mit den relevanten Informationen in Ihren Logs anzeigen können. + +Das bedeutet aber auch, dass, wenn Sie ihn einfach in einen String umwandeln und diese Informationen direkt zurückgeben, Sie möglicherweise ein paar Informationen über Ihr System preisgeben. Daher extrahiert und zeigt der Code hier jeden Fehler getrennt. + +/// + ### Verwenden des `RequestValidationError`-Bodys { #use-the-requestvalidationerror-body } Der `RequestValidationError` enthält den empfangenen `body` mit den ungültigen Daten. diff --git a/docs/de/docs/tutorial/testing.md b/docs/de/docs/tutorial/testing.md index 9c28a2a223..b18469998d 100644 --- a/docs/de/docs/tutorial/testing.md +++ b/docs/de/docs/tutorial/testing.md @@ -122,63 +122,13 @@ Sie verfügt über eine `POST`-Operation, die mehrere Fehler zurückgeben könnt Beide *Pfadoperationen* erfordern einen `X-Token`-Header. -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/app_testing/app_b_an/main.py!} -``` - -//// - -//// tab | Python 3.10+ nicht annotiert - -/// tip | Tipp - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.8+ nicht annotiert - -/// tip | Tipp - -Bevorzugen Sie die `Annotated`-Version, falls möglich. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b/main.py!} -``` - -//// +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} ### Erweiterte Testdatei { #extended-testing-file } Anschließend könnten Sie `test_main.py` mit den erweiterten Tests aktualisieren: -{* ../../docs_src/app_testing/app_b/test_main.py *} +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} Wenn Sie möchten, dass der Client Informationen im Request übergibt und Sie nicht wissen, wie das geht, können Sie suchen (googeln), wie es mit `httpx` gemacht wird, oder sogar, wie es mit `requests` gemacht wird, da das Design von HTTPX auf dem Design von Requests basiert. From 1cf7cd8af0bd71a9d02d45744cced86f660003ea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Dec 2025 13:54:57 +0000 Subject: [PATCH 043/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 81eeb9f99f..bb1e95aa9b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,10 @@ hide: * 📝 Update docs about re-raising validation errors, do not include string as is to not leak information. PR [#14487](https://github.com/fastapi/fastapi/pull/14487) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove external links section. PR [#14486](https://github.com/fastapi/fastapi/pull/14486) by [@tiangolo](https://github.com/tiangolo). +### Translations + +* 🌐 Sync German docs. PR [#14488](https://github.com/fastapi/fastapi/pull/14488) by [@nilslindemann](https://github.com/nilslindemann). + ## 0.124.2 ### Fixes From 4c4d5201986dce315107ba70ae930d9db4575904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 11 Dec 2025 06:48:47 -0800 Subject: [PATCH 044/140] =?UTF-8?q?=F0=9F=93=9D=20Tweak=20links=20format?= =?UTF-8?q?=20(#14505)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/_llm-test.md | 4 ++-- docs/en/docs/index.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/docs/_llm-test.md b/docs/en/docs/_llm-test.md index e72450b916..9f216f9d79 100644 --- a/docs/en/docs/_llm-test.md +++ b/docs/en/docs/_llm-test.md @@ -15,7 +15,7 @@ Use as follows: The tests: -## Code snippets { #code-snippets} +## Code snippets { #code-snippets } //// tab | Test @@ -53,7 +53,7 @@ See for example section `### Quotes` in `docs/de/llm-prompt.md`. //// -## Quotes in code snippets { #quotes-in-code-snippets} +## Quotes in code snippets { #quotes-in-code-snippets } //// tab | Test diff --git a/docs/en/docs/index.md b/docs/en/docs/index.md index df03b7675e..a0a5de3b71 100644 --- a/docs/en/docs/index.md +++ b/docs/en/docs/index.md @@ -52,13 +52,13 @@ The key features are: -### Keystone Sponsor +### Keystone Sponsor { #keystone-sponsor } {% for sponsor in sponsors.keystone -%} {% endfor -%} -### Gold and Silver Sponsors +### Gold and Silver Sponsors { #gold-and-silver-sponsors } {% for sponsor in sponsors.gold -%} From 009c8af7fec44206c97cb08fba20effe7895fd77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Dec 2025 14:49:09 +0000 Subject: [PATCH 045/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bb1e95aa9b..dc97a1a15d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Docs +* 📝 Tweak links format. PR [#14505](https://github.com/fastapi/fastapi/pull/14505) by [@tiangolo](https://github.com/tiangolo). * 📝 Update docs about re-raising validation errors, do not include string as is to not leak information. PR [#14487](https://github.com/fastapi/fastapi/pull/14487) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove external links section. PR [#14486](https://github.com/fastapi/fastapi/pull/14486) by [@tiangolo](https://github.com/tiangolo). From a7ba9932ba5fe87adb3ea3884dc6cd6294d117dd Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Thu, 11 Dec 2025 20:58:21 +0530 Subject: [PATCH 046/140] =?UTF-8?q?=E2=9C=85=20Expand=20test=20matrix=20to?= =?UTF-8?q?=20include=20Windows=20and=20MacOS=20(#14171)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez Co-authored-by: github-actions[bot] --- .github/workflows/test.yml | 50 ++++++++++++++++++++++++++++---------- pyproject.toml | 1 + 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8157e364ba..94b3fbd9c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,22 +44,44 @@ jobs: run: bash scripts/lint.sh test: - runs-on: ubuntu-latest strategy: matrix: - python-version: - - "3.14" - - "3.13" - - "3.12" - - "3.11" - - "3.10" - - "3.9" - - "3.8" - pydantic-version: ["pydantic-v1", "pydantic-v2"] - exclude: - - python-version: "3.14" + os: [ ubuntu-latest, windows-latest, macos-latest ] + python-version: [ "3.14" ] + pydantic-version: [ "pydantic-v2" ] + coverage: ["coverage"] + include: + - os: macos-latest + python-version: "3.8" pydantic-version: "pydantic-v1" + coverage: coverage + - os: windows-latest + python-version: "3.8" + pydantic-version: "pydantic-v2" + coverage: coverage + - os: ubuntu-latest + python-version: "3.9" + pydantic-version: "pydantic-v1" + coverage: coverage + - os: macos-latest + python-version: "3.10" + pydantic-version: "pydantic-v2" + - os: windows-latest + python-version: "3.11" + pydantic-version: "pydantic-v1" + - os: ubuntu-latest + python-version: "3.12" + pydantic-version: "pydantic-v2" + - os: macos-latest + python-version: "3.13" + pydantic-version: "pydantic-v1" + coverage: coverage + - os: windows-latest + python-version: "3.13" + pydantic-version: "pydantic-v2" + coverage: coverage fail-fast: false + runs-on: ${{ matrix.os }} steps: - name: Dump GitHub context env: @@ -96,10 +118,12 @@ jobs: env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} + # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow - name: Store coverage files + if: matrix.coverage == 'coverage' uses: actions/upload-artifact@v5 with: - name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }} + name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.pydantic-version }} path: coverage include-hidden-files: true diff --git a/pyproject.toml b/pyproject.toml index f8d5fa7c7a..ef4440b1b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -196,6 +196,7 @@ source = [ "tests", "fastapi" ] +relative_files = true context = '${CONTEXT}' dynamic_context = "test_function" omit = [ From 931e80f20c54b6587a76f8b6249c904cce3c5af3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Dec 2025 15:28:47 +0000 Subject: [PATCH 047/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index dc97a1a15d..17d1796144 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -17,6 +17,10 @@ hide: * 🌐 Sync German docs. PR [#14488](https://github.com/fastapi/fastapi/pull/14488) by [@nilslindemann](https://github.com/nilslindemann). +### Internal + +* ✅ Expand test matrix to include Windows and MacOS. PR [#14171](https://github.com/fastapi/fastapi/pull/14171) by [@svlandeg](https://github.com/svlandeg). + ## 0.124.2 ### Fixes From 564a4ac1b8c7c75c7229f18f06c5800944b38508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 11 Dec 2025 08:02:26 -0800 Subject: [PATCH 048/140] =?UTF-8?q?=F0=9F=91=B7=20Tweak=20coverage=20to=20?= =?UTF-8?q?not=20pass=20Smokeshow=20max=20file=20size=20limit=20(#14507)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 94b3fbd9c6..85f9c4afd0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,15 +46,13 @@ jobs: test: strategy: matrix: - os: [ ubuntu-latest, windows-latest, macos-latest ] + os: [ windows-latest, macos-latest ] python-version: [ "3.14" ] pydantic-version: [ "pydantic-v2" ] - coverage: ["coverage"] include: - os: macos-latest python-version: "3.8" pydantic-version: "pydantic-v1" - coverage: coverage - os: windows-latest python-version: "3.8" pydantic-version: "pydantic-v2" @@ -75,11 +73,14 @@ jobs: - os: macos-latest python-version: "3.13" pydantic-version: "pydantic-v1" - coverage: coverage - os: windows-latest python-version: "3.13" pydantic-version: "pydantic-v2" coverage: coverage + - os: ubuntu-latest + python-version: "3.14" + pydantic-version: "pydantic-v2" + coverage: coverage fail-fast: false runs-on: ${{ matrix.os }} steps: From 475ce41268f600d88e094443ecf2e2781763e47e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Dec 2025 16:02:50 +0000 Subject: [PATCH 049/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 17d1796144..a6fc2f9238 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Internal +* 👷 Tweak coverage to not pass Smokeshow max file size limit. PR [#14507](https://github.com/fastapi/fastapi/pull/14507) by [@tiangolo](https://github.com/tiangolo). * ✅ Expand test matrix to include Windows and MacOS. PR [#14171](https://github.com/fastapi/fastapi/pull/14171) by [@svlandeg](https://github.com/svlandeg). ## 0.124.2 From 6c54bcefd3c37c8656e909980af00f86acea99c3 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:15:36 +0100 Subject: [PATCH 050/140] =?UTF-8?q?=E2=9C=85=20Add=20set=20of=20tests=20fo?= =?UTF-8?q?r=20request=20parameters=20and=20alias=20(#14358)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- tests/test_request_params/__init__.py | 0 .../test_request_params/test_body/__init__.py | 0 .../test_body/test_list.py | 523 +++++++++++++++ .../test_body/test_optional_list.py | 600 ++++++++++++++++++ .../test_body/test_optional_str.py | 569 +++++++++++++++++ .../test_body/test_required_str.py | 514 +++++++++++++++ tests/test_request_params/test_body/utils.py | 7 + .../test_cookie/__init__.py | 0 .../test_cookie/test_list.py | 3 + .../test_cookie/test_optional_list.py | 3 + .../test_cookie/test_optional_str.py | 383 +++++++++++ .../test_cookie/test_required_str.py | 503 +++++++++++++++ .../test_request_params/test_file/__init__.py | 0 .../test_file/test_list.py | 597 +++++++++++++++++ .../test_file/test_optional.py | 443 +++++++++++++ .../test_file/test_optional_list.py | 487 ++++++++++++++ .../test_file/test_required.py | 536 ++++++++++++++++ tests/test_request_params/test_file/utils.py | 7 + .../test_request_params/test_form/__init__.py | 0 .../test_form/test_list.py | 527 +++++++++++++++ .../test_form/test_optional_list.py | 454 +++++++++++++ .../test_form/test_optional_str.py | 419 ++++++++++++ .../test_form/test_required_str.py | 502 +++++++++++++++ tests/test_request_params/test_form/utils.py | 7 + .../test_header/__init__.py | 0 .../test_header/test_list.py | 505 +++++++++++++++ .../test_header/test_optional_list.py | 407 ++++++++++++ .../test_header/test_optional_str.py | 375 +++++++++++ .../test_header/test_required_str.py | 492 ++++++++++++++ .../test_request_params/test_path/__init__.py | 0 .../test_path/test_list.py | 1 + .../test_path/test_optional_list.py | 1 + .../test_path/test_optional_str.py | 1 + .../test_path/test_required_str.py | 102 +++ .../test_query/__init__.py | 0 .../test_query/test_list.py | 506 +++++++++++++++ .../test_query/test_optional_list.py | 403 ++++++++++++ .../test_query/test_optional_str.py | 375 +++++++++++ .../test_query/test_required_str.py | 495 +++++++++++++++ 39 files changed, 10747 insertions(+) create mode 100644 tests/test_request_params/__init__.py create mode 100644 tests/test_request_params/test_body/__init__.py create mode 100644 tests/test_request_params/test_body/test_list.py create mode 100644 tests/test_request_params/test_body/test_optional_list.py create mode 100644 tests/test_request_params/test_body/test_optional_str.py create mode 100644 tests/test_request_params/test_body/test_required_str.py create mode 100644 tests/test_request_params/test_body/utils.py create mode 100644 tests/test_request_params/test_cookie/__init__.py create mode 100644 tests/test_request_params/test_cookie/test_list.py create mode 100644 tests/test_request_params/test_cookie/test_optional_list.py create mode 100644 tests/test_request_params/test_cookie/test_optional_str.py create mode 100644 tests/test_request_params/test_cookie/test_required_str.py create mode 100644 tests/test_request_params/test_file/__init__.py create mode 100644 tests/test_request_params/test_file/test_list.py create mode 100644 tests/test_request_params/test_file/test_optional.py create mode 100644 tests/test_request_params/test_file/test_optional_list.py create mode 100644 tests/test_request_params/test_file/test_required.py create mode 100644 tests/test_request_params/test_file/utils.py create mode 100644 tests/test_request_params/test_form/__init__.py create mode 100644 tests/test_request_params/test_form/test_list.py create mode 100644 tests/test_request_params/test_form/test_optional_list.py create mode 100644 tests/test_request_params/test_form/test_optional_str.py create mode 100644 tests/test_request_params/test_form/test_required_str.py create mode 100644 tests/test_request_params/test_form/utils.py create mode 100644 tests/test_request_params/test_header/__init__.py create mode 100644 tests/test_request_params/test_header/test_list.py create mode 100644 tests/test_request_params/test_header/test_optional_list.py create mode 100644 tests/test_request_params/test_header/test_optional_str.py create mode 100644 tests/test_request_params/test_header/test_required_str.py create mode 100644 tests/test_request_params/test_path/__init__.py create mode 100644 tests/test_request_params/test_path/test_list.py create mode 100644 tests/test_request_params/test_path/test_optional_list.py create mode 100644 tests/test_request_params/test_path/test_optional_str.py create mode 100644 tests/test_request_params/test_path/test_required_str.py create mode 100644 tests/test_request_params/test_query/__init__.py create mode 100644 tests/test_request_params/test_query/test_list.py create mode 100644 tests/test_request_params/test_query/test_optional_list.py create mode 100644 tests/test_request_params/test_query/test_optional_str.py create mode 100644 tests/test_request_params/test_query/test_required_str.py diff --git a/tests/test_request_params/__init__.py b/tests/test_request_params/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_body/__init__.py b/tests/test_request_params/test_body/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py new file mode 100644 index 0000000000..884e1d08ab --- /dev/null +++ b/tests/test_request_params/test_body/test_list.py @@ -0,0 +1,523 @@ +from typing import List, Union + +import pytest +from dirty_equals import IsDict, IsOneOf, IsPartialDict +from fastapi import Body, FastAPI +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/required-list-str", operation_id="required_list_str") +async def read_required_list_str(p: Annotated[List[str], Body(embed=True)]): + return {"p": p} + + +class BodyModelRequiredListStr(BaseModel): + p: List[str] + + +@app.post("/model-required-list-str", operation_id="model_required_list_str") +def read_model_required_list_str(p: BodyModelRequiredListStr): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": { + "items": {"type": "string"}, + "title": "P", + "type": "array", + }, + }, + "required": ["p"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_missing(path: str, json: Union[dict, None]): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body", "p"], ["body"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + { + "detail": [ + { + "loc": IsOneOf(["body", "p"], ["body"]), + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.post("/required-list-alias", operation_id="required_list_alias") +async def read_required_list_alias( + p: Annotated[List[str], Body(embed=True, alias="p_alias")], +): + return {"p": p} + + +class BodyModelRequiredListAlias(BaseModel): + p: List[str] = Field(alias="p_alias") + + +@app.post("/model-required-list-alias", operation_id="model_required_list_alias") +async def read_model_required_list_alias(p: BodyModelRequiredListAlias): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + "/model-required-list-alias", + ], +) +def test_required_list_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": { + "items": {"type": "string"}, + "title": "P Alias", + "type": "array", + }, + }, + "required": ["p_alias"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_missing(path: str, json: Union[dict, None]): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": ["hello", "world"]}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": ["hello", "world"]}) + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Validation alias + + +@app.post( + "/required-list-validation-alias", operation_id="required_list_validation_alias" +) +def read_required_list_validation_alias( + p: Annotated[List[str], Body(embed=True, validation_alias="p_val_alias")], +): + return {"p": p} + + +class BodyModelRequiredListValidationAlias(BaseModel): + p: List[str] = Field(validation_alias="p_val_alias") + + +@app.post( + "/model-required-list-validation-alias", + operation_id="model_required_list_validation_alias", +) +async def read_model_required_list_validation_alias( + p: BodyModelRequiredListValidationAlias, +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "title": "P Val Alias", + "type": "array", + }, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_missing(path: str, json: Union[dict, None]): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf( # /required-validation-alias fails here + ["body"], ["body", "p_val_alias"] + ), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 422, ( + response.text # /required-list-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, ( + response.text # /required-list-validation-alias fails here + ) + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/required-list-alias-and-validation-alias", + operation_id="required_list_alias_and_validation_alias", +) +def read_required_list_alias_and_validation_alias( + p: Annotated[ + List[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") + ], +): + return {"p": p} + + +class BodyModelRequiredListAliasAndValidationAlias(BaseModel): + p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.post( + "/model-required-list-alias-and-validation-alias", + operation_id="model_required_list_alias_and_validation_alias", +) +def read_model_required_list_alias_and_validation_alias( + p: BodyModelRequiredListAliasAndValidationAlias, +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "title": "P Val Alias", + "type": "array", + }, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_missing(path: str, json): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf( # /required-list-alias-and-validation-alias fails here + ["body"], ["body", "p_val_alias"] + ), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ # /required-list-alias-and-validation-alias fails here + "body", + "p_val_alias", + ], + "msg": "Field required", + "input": IsOneOf(None, {"p": ["hello", "world"]}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": ["hello", "world"]}) + assert response.status_code == 422, response.text + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p_alias": ["hello", "world"]}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, ( + response.text # /required-list-alias-and-validation-alias fails here + ) + assert response.json() == {"p": ["hello", "world"]} diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py new file mode 100644 index 0000000000..c86398ce9f --- /dev/null +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -0,0 +1,600 @@ +from typing import List, Optional + +import pytest +from dirty_equals import IsDict +from fastapi import Body, FastAPI +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/optional-list-str", operation_id="optional_list_str") +async def read_optional_list_str( + p: Annotated[Optional[List[str]], Body(embed=True)] = None, +): + return {"p": p} + + +class BodyModelOptionalListStr(BaseModel): + p: Optional[List[str]] = None + + +@app.post("/model-optional-list-str", operation_id="model_optional_list_str") +async def read_model_optional_list_str(p: BodyModelOptionalListStr): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p": {"items": {"type": "string"}, "type": "array", "title": "P"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +def test_optional_list_str_missing(): + client = TestClient(app) + response = client.post("/optional-list-str") + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +def test_model_optional_list_str_missing(): + client = TestClient(app) + response = client.post("/model-optional-list-str") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.post("/optional-list-alias", operation_id="optional_list_alias") +async def read_optional_list_alias( + p: Annotated[Optional[List[str]], Body(embed=True, alias="p_alias")] = None, +): + return {"p": p} + + +class BodyModelOptionalListAlias(BaseModel): + p: Optional[List[str]] = Field(None, alias="p_alias") + + +@app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") +async def read_model_optional_list_alias(p: BodyModelOptionalListAlias): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + strict=False, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + ), + ), + "/model-optional-list-alias", + ], +) +def test_optional_list_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_alias": { + "items": {"type": "string"}, + "type": "array", + "title": "P Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) + + +def test_optional_list_alias_missing(): + client = TestClient(app) + response = client.post("/optional-list-alias") + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +def test_model_optional_list_alias_missing(): + client = TestClient(app) + response = client.post("/model-optional-list-alias") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Validation alias + + +@app.post( + "/optional-list-validation-alias", operation_id="optional_list_validation_alias" +) +def read_optional_list_validation_alias( + p: Annotated[ + Optional[List[str]], Body(embed=True, validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class BodyModelOptionalListValidationAlias(BaseModel): + p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + + +@app.post( + "/model-optional-list-validation-alias", + operation_id="model_optional_list_validation_alias", +) +def read_model_optional_list_validation_alias( + p: BodyModelOptionalListValidationAlias, +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "type": "array", + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) + + +def test_optional_list_validation_alias_missing(): + client = TestClient(app) + response = client.post("/optional-list-validation-alias") + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +def test_model_optional_list_validation_alias_missing(): + client = TestClient(app) + response = client.post("/model-optional-list-validation-alias") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-validation-alias", + ], +) +def test_optional_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-list-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-validation-alias", + ], +) +def test_optional_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, response.text + assert response.json() == { # /optional-list-validation-alias fails here + "p": ["hello", "world"] + } + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/optional-list-alias-and-validation-alias", + operation_id="optional_list_alias_and_validation_alias", +) +def read_optional_list_alias_and_validation_alias( + p: Annotated[ + Optional[List[str]], + Body(embed=True, alias="p_alias", validation_alias="p_val_alias"), + ] = None, +): + return {"p": p} + + +class BodyModelOptionalListAliasAndValidationAlias(BaseModel): + p: Optional[List[str]] = Field( + None, alias="p_alias", validation_alias="p_val_alias" + ) + + +@app.post( + "/model-optional-list-alias-and-validation-alias", + operation_id="model_optional_list_alias_and_validation_alias", +) +def read_model_optional_list_alias_and_validation_alias( + p: BodyModelOptionalListAliasAndValidationAlias, +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "type": "array", + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) + + +def test_optional_list_alias_and_validation_alias_missing(): + client = TestClient(app) + response = client.post("/optional-list-alias-and-validation-alias") + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +def test_model_optional_list_alias_and_validation_alias_missing(): + client = TestClient(app) + response = client.post("/model-optional-list-alias-and-validation-alias") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-list-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, response.text + assert response.json() == { + "p": [ # /optional-list-alias-and-validation-alias fails here + "hello", + "world", + ] + } diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py new file mode 100644 index 0000000000..43ed367dde --- /dev/null +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -0,0 +1,569 @@ +from typing import Optional + +import pytest +from dirty_equals import IsDict +from fastapi import Body, FastAPI +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/optional-str", operation_id="optional_str") +async def read_optional_str(p: Annotated[Optional[str], Body(embed=True)] = None): + return {"p": p} + + +class BodyModelOptionalStr(BaseModel): + p: Optional[str] = None + + +@app.post("/model-optional-str", operation_id="model_optional_str") +async def read_model_optional_str(p: BodyModelOptionalStr): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p": {"type": "string", "title": "P"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +def test_optional_str_missing(): + client = TestClient(app) + response = client.post("/optional-str") + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +def test_model_optional_str_missing(): + client = TestClient(app) + response = client.post("/model-optional-str") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.post("/optional-alias", operation_id="optional_alias") +async def read_optional_alias( + p: Annotated[Optional[str], Body(embed=True, alias="p_alias")] = None, +): + return {"p": p} + + +class BodyModelOptionalAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias") + + +@app.post("/model-optional-alias", operation_id="model_optional_alias") +async def read_model_optional_alias(p: BodyModelOptionalAlias): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + strict=False, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + ), + ), + "/model-optional-alias", + ], +) +def test_optional_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_alias": {"type": "string", "title": "P Alias"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +def test_optional_alias_missing(): + client = TestClient(app) + response = client.post("/optional-alias") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +def test_model_optional_alias_missing(): + client = TestClient(app) + response = client.post("/model-optional-alias") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_model_optional_alias_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.post("/optional-validation-alias", operation_id="optional_validation_alias") +def read_optional_validation_alias( + p: Annotated[ + Optional[str], Body(embed=True, validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class BodyModelOptionalValidationAlias(BaseModel): + p: Optional[str] = Field(None, validation_alias="p_val_alias") + + +@app.post( + "/model-optional-validation-alias", operation_id="model_optional_validation_alias" +) +def read_model_optional_validation_alias( + p: BodyModelOptionalValidationAlias, +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": {"type": "string", "title": "P Val Alias"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@needs_pydanticv2 +def test_optional_validation_alias_missing(): + client = TestClient(app) + response = client.post("/optional-validation-alias") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +def test_model_optional_validation_alias_missing(): + client = TestClient(app) + response = client.post("/model-optional-validation-alias") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_model_optional_validation_alias_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/optional-alias-and-validation-alias", + operation_id="optional_alias_and_validation_alias", +) +def read_optional_alias_and_validation_alias( + p: Annotated[ + Optional[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class BodyModelOptionalAliasAndValidationAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + + +@app.post( + "/model-optional-alias-and-validation-alias", + operation_id="model_optional_alias_and_validation_alias", +) +def read_model_optional_alias_and_validation_alias( + p: BodyModelOptionalAliasAndValidationAlias, +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": {"type": "string", "title": "P Val Alias"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@needs_pydanticv2 +def test_optional_alias_and_validation_alias_missing(): + client = TestClient(app) + response = client.post("/optional-alias-and-validation-alias") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +def test_model_optional_alias_and_validation_alias_missing(): + client = TestClient(app) + response = client.post("/model-optional-alias-and-validation-alias") + assert response.status_code == 422, response.text + assert response.json() == IsDict( + { + "detail": [ + { + "input": None, + "loc": ["body"], + "msg": "Field required", + "type": "missing", + }, + ], + } + ) | IsDict( + { + # TODO: remove when deprecating Pydantic v1 + "detail": [ + { + "loc": ["body"], + "msg": "field required", + "type": "value_error.missing", + }, + ], + } + ) + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_model_optional_alias_and_validation_alias_missing_empty_dict(path: str): + client = TestClient(app) + response = client.post(path, json={}) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == { + "p": "hello" # /optional-alias-and-validation-alias fails here + } diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py new file mode 100644 index 0000000000..fba3fe1f60 --- /dev/null +++ b/tests/test_request_params/test_body/test_required_str.py @@ -0,0 +1,514 @@ +from typing import Any, Dict, Union + +import pytest +from dirty_equals import IsDict, IsOneOf +from fastapi import Body, FastAPI +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/required-str", operation_id="required_str") +async def read_required_str(p: Annotated[str, Body(embed=True)]): + return {"p": p} + + +class BodyModelRequiredStr(BaseModel): + p: str + + +@app.post("/model-required-str", operation_id="model_required_str") +async def read_model_required_str(p: BodyModelRequiredStr): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": {"title": "P", "type": "string"}, + }, + "required": ["p"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_missing(path: str, json: Union[Dict[str, Any], None]): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body"], ["body", "p"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": IsOneOf(["body"], ["body", "p"]), + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.post("/required-alias", operation_id="required_alias") +async def read_required_alias( + p: Annotated[str, Body(embed=True, alias="p_alias")], +): + return {"p": p} + + +class BodyModelRequiredAlias(BaseModel): + p: str = Field(alias="p_alias") + + +@app.post("/model-required-alias", operation_id="model_required_alias") +async def read_model_required_alias(p: BodyModelRequiredAlias): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + strict=False, + ), + ), + "/model-required-alias", + ], +) +def test_required_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": {"title": "P Alias", "type": "string"}, + }, + "required": ["p_alias"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_missing(path: str, json: Union[Dict[str, Any], None]): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": IsOneOf(["body", "p_alias"], ["body"]), + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": "hello"}) + assert response.status_code == 200, response.text + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.post("/required-validation-alias", operation_id="required_validation_alias") +def read_required_validation_alias( + p: Annotated[str, Body(embed=True, validation_alias="p_val_alias")], +): + return {"p": p} + + +class BodyModelRequiredValidationAlias(BaseModel): + p: str = Field(validation_alias="p_val_alias") + + +@app.post( + "/model-required-validation-alias", operation_id="model_required_validation_alias" +) +def read_model_required_validation_alias( + p: BodyModelRequiredValidationAlias, +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/required-validation-alias", "/model-required-validation-alias"], +) +def test_required_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": {"title": "P Val Alias", "type": "string"}, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_missing( + path: str, json: Union[Dict[str, Any], None] +): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf( # /required-validation-alias fails here + ["body", "p_val_alias"], ["body"] + ), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 422, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": "hello"}) + assert response.status_code == 200, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/required-alias-and-validation-alias", + operation_id="required_alias_and_validation_alias", +) +def read_required_alias_and_validation_alias( + p: Annotated[ + str, Body(embed=True, alias="p_alias", validation_alias="p_val_alias") + ], +): + return {"p": p} + + +class BodyModelRequiredAliasAndValidationAlias(BaseModel): + p: str = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.post( + "/model-required-alias-and-validation-alias", + operation_id="model_required_alias_and_validation_alias", +) +def read_model_required_alias_and_validation_alias( + p: BodyModelRequiredAliasAndValidationAlias, +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": {"title": "P Val Alias", "type": "string"}, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize("json", [None, {}]) +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_missing( + path: str, json: Union[Dict[str, Any], None] +): + client = TestClient(app) + response = client.post(path, json=json) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": IsOneOf( # /required-alias-and-validation-alias fails here + ["body"], ["body", "p_val_alias"] + ), + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, json={"p": "hello"}) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_alias": "hello"}) + assert response.status_code == 422, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p_alias": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, json={"p_val_alias": "hello"}) + assert response.status_code == 200, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_body/utils.py b/tests/test_request_params/test_body/utils.py new file mode 100644 index 0000000000..5151a82d36 --- /dev/null +++ b/tests/test_request_params/test_body/utils.py @@ -0,0 +1,7 @@ +from typing import Any, Dict + + +def get_body_model_name(openapi: Dict[str, Any], path: str) -> str: + body = openapi["paths"][path]["post"]["requestBody"] + body_schema = body["content"]["application/json"]["schema"] + return body_schema.get("$ref", "").split("/")[-1] diff --git a/tests/test_request_params/test_cookie/__init__.py b/tests/test_request_params/test_cookie/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_cookie/test_list.py b/tests/test_request_params/test_cookie/test_list.py new file mode 100644 index 0000000000..4ae80e0015 --- /dev/null +++ b/tests/test_request_params/test_cookie/test_list.py @@ -0,0 +1,3 @@ +# Currently, there is no way to pass multiple cookies with the same name. +# The only way to pass multiple values for cookie params is to serialize them using +# a comma as a delimiter, but this is not currently supported by Starlette. diff --git a/tests/test_request_params/test_cookie/test_optional_list.py b/tests/test_request_params/test_cookie/test_optional_list.py new file mode 100644 index 0000000000..4ae80e0015 --- /dev/null +++ b/tests/test_request_params/test_cookie/test_optional_list.py @@ -0,0 +1,3 @@ +# Currently, there is no way to pass multiple cookies with the same name. +# The only way to pass multiple values for cookie params is to serialize them using +# a comma as a delimiter, but this is not currently supported by Starlette. diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py new file mode 100644 index 0000000000..7298baacd1 --- /dev/null +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -0,0 +1,383 @@ +from typing import Optional + +import pytest +from dirty_equals import IsDict +from fastapi import Cookie, FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/optional-str") +async def read_optional_str(p: Annotated[Optional[str], Cookie()] = None): + return {"p": p} + + +class CookieModelOptionalStr(BaseModel): + p: Optional[str] = None + + +@app.get("/model-optional-str") +async def read_model_optional_str(p: Annotated[CookieModelOptionalStr, Cookie()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "cookie", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "cookie", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.get("/optional-alias") +async def read_optional_alias( + p: Annotated[Optional[str], Cookie(alias="p_alias")] = None, +): + return {"p": p} + + +class CookieModelOptionalAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias") + + +@app.get("/model-optional-alias") +async def read_model_optional_alias(p: Annotated[CookieModelOptionalAlias, Cookie()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "cookie", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "cookie", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_name(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-alias", + pytest.param( + "/model-optional-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_alias_by_alias(path: str): + client = TestClient(app) + client.cookies.set("p_alias", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /model-optional-alias fails here + + +# ===================================================================================== +# Validation alias + + +@app.get("/optional-validation-alias") +def read_optional_validation_alias( + p: Annotated[Optional[str], Cookie(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class CookieModelOptionalValidationAlias(BaseModel): + p: Optional[str] = Field(None, validation_alias="p_val_alias") + + +@app.get("/model-optional-validation-alias") +def read_model_optional_validation_alias( + p: Annotated[CookieModelOptionalValidationAlias, Cookie()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "cookie", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + client.cookies.set("p_val_alias", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/optional-alias-and-validation-alias") +def read_optional_alias_and_validation_alias( + p: Annotated[ + Optional[str], Cookie(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class CookieModelOptionalAliasAndValidationAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-optional-alias-and-validation-alias") +def read_model_optional_alias_and_validation_alias( + p: Annotated[CookieModelOptionalAliasAndValidationAlias, Cookie()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "cookie", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + client.cookies.set("p_alias", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + client.cookies.set("p_val_alias", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == { + "p": "hello" # /optional-alias-and-validation-alias fails here + } diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py new file mode 100644 index 0000000000..9c1442ccb7 --- /dev/null +++ b/tests/test_request_params/test_cookie/test_required_str.py @@ -0,0 +1,503 @@ +import pytest +from dirty_equals import IsDict, IsOneOf +from fastapi import Cookie, FastAPI +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/required-str") +async def read_required_str(p: Annotated[str, Cookie()]): + return {"p": p} + + +class CookieModelRequiredStr(BaseModel): + p: str + + +@app.get("/model-required-str") +async def read_model_required_str(p: Annotated[CookieModelRequiredStr, Cookie()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "cookie", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["cookie", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.get("/required-alias") +async def read_required_alias(p: Annotated[str, Cookie(alias="p_alias")]): + return {"p": p} + + +class CookieModelRequiredAlias(BaseModel): + p: str = Field(alias="p_alias") + + +@app.get("/model-required-alias") +async def read_model_required_alias(p: Annotated[CookieModelRequiredAlias, Cookie()]): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "cookie", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["cookie", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-alias", + pytest.param( + "/model-required-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + ], +) +def test_required_alias_by_name(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p_alias"], + "msg": "Field required", + "input": IsOneOf( + None, + {"p": "hello"}, # /model-required-alias PDv2 fails here + ), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["cookie", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-alias", + pytest.param( + "/model-required-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_by_alias(path: str): + client = TestClient(app) + client.cookies.set("p_alias", "hello") + response = client.get(path) + assert response.status_code == 200, ( # /model-required-alias fails here + response.text + ) + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.get("/required-validation-alias") +def read_required_validation_alias( + p: Annotated[str, Cookie(validation_alias="p_val_alias")], +): + return {"p": p} + + +class CookieModelRequiredValidationAlias(BaseModel): + p: str = Field(validation_alias="p_val_alias") + + +@app.get("/model-required-validation-alias") +def read_model_required_validation_alias( + p: Annotated[CookieModelRequiredValidationAlias, Cookie()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-validation-alias", "/model-required-validation-alias"], +) +def test_required_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "cookie", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "cookie", + "p_val_alias", # /required-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_name(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 422, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + client.cookies.set("p_val_alias", "hello") + response = client.get(path) + assert response.status_code == 200, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/required-alias-and-validation-alias") +def read_required_alias_and_validation_alias( + p: Annotated[str, Cookie(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class CookieModelRequiredAliasAndValidationAlias(BaseModel): + p: str = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-required-alias-and-validation-alias") +def read_model_required_alias_and_validation_alias( + p: Annotated[CookieModelRequiredAliasAndValidationAlias, Cookie()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "cookie", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "cookie", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + client.cookies.set("p", "hello") + response = client.get(path) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "cookie", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf( # /model-alias-and-validation-alias fails here + None, + {"p": "hello"}, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + client.cookies.set("p_alias", "hello") + response = client.get(path) + assert ( + response.status_code == 422 # /required-alias-and-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["cookie", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf( # /model-alias-and-validation-alias fails here + None, + {"p_alias": "hello"}, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + client.cookies.set("p_val_alias", "hello") + response = client.get(path) + assert response.status_code == 200, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_file/__init__.py b/tests/test_request_params/test_file/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py new file mode 100644 index 0000000000..8722ce5ab2 --- /dev/null +++ b/tests/test_request_params/test_file/test_list.py @@ -0,0 +1,597 @@ +from typing import List + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, File, UploadFile +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/list-bytes", operation_id="list_bytes") +async def read_list_bytes(p: Annotated[List[bytes], File()]): + return {"file_size": [len(file) for file in p]} + + +@app.post("/list-uploadfile", operation_id="list_uploadfile") +async def read_list_uploadfile(p: Annotated[List[UploadFile], File()]): + return {"file_size": [file.size for file in p]} + + +@pytest.mark.parametrize( + "path", + [ + "/list-bytes", + "/list-uploadfile", + ], +) +def test_list_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P", + }, + ) + | IsDict( + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P", + }, + ) + ) + }, + "required": ["p"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/list-bytes", + "/list-uploadfile", + ], +) +def test_list_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": None, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/list-bytes", + "/list-uploadfile", + ], +) +def test_list(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) + assert response.status_code == 200 + assert response.json() == {"file_size": [5, 5]} + + +# ===================================================================================== +# Alias + + +@app.post("/list-bytes-alias", operation_id="list_bytes_alias") +async def read_list_bytes_alias(p: Annotated[List[bytes], File(alias="p_alias")]): + return {"file_size": [len(file) for file in p]} + + +@app.post("/list-uploadfile-alias", operation_id="list_uploadfile_alias") +async def read_list_uploadfile_alias( + p: Annotated[List[UploadFile], File(alias="p_alias")], +): + return {"file_size": [file.size for file in p]} + + +@pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + strict=False, +) +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias", + "/list-uploadfile-alias", + ], +) +def test_list_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P Alias", + }, + ) + | IsDict( + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P Alias", + }, + ) + ) + }, + "required": ["p_alias"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias", + "/list-uploadfile-alias", + ], +) +def test_list_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias", + "/list-uploadfile-alias", + ], +) +def test_list_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias", + "/list-uploadfile-alias", + ], +) +def test_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": [5, 5]} + + +# ===================================================================================== +# Validation alias + + +@app.post("/list-bytes-validation-alias", operation_id="list_bytes_validation_alias") +def read_list_bytes_validation_alias( + p: Annotated[List[bytes], File(validation_alias="p_val_alias")], +): + return {"file_size": [len(file) for file in p]} + + +@app.post( + "/list-uploadfile-validation-alias", + operation_id="list_uploadfile_validation_alias", +) +def read_list_uploadfile_validation_alias( + p: Annotated[List[UploadFile], File(validation_alias="p_val_alias")], +): + return {"file_size": [file.size for file in p]} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-validation-alias", + "/list-uploadfile-validation-alias", + ], +) +def test_list_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + ) + | IsDict( + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P Val Alias", + }, + ) + ) + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/list-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/list-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ # /list-*-validation-alias fail here + "body", + "p_val_alias", + ], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/list-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/list-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) + assert response.status_code == 422, ( # /list-*-validation-alias fail here + response.text + ) + + assert response.json() == { # pragma: no cover + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": None, + } + ] + } + + +@pytest.mark.xfail(raises=AssertionError, strict=False) +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-validation-alias", + "/list-uploadfile-validation-alias", + ], +) +def test_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post( + path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] + ) + assert response.status_code == 200, response.text # all 2 fail here + assert response.json() == {"file_size": [5, 5]} # pragma: no cover + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/list-bytes-alias-and-validation-alias", + operation_id="list_bytes_alias_and_validation_alias", +) +def read_list_bytes_alias_and_validation_alias( + p: Annotated[List[bytes], File(alias="p_alias", validation_alias="p_val_alias")], +): + return {"file_size": [len(file) for file in p]} + + +@app.post( + "/list-uploadfile-alias-and-validation-alias", + operation_id="list_uploadfile_alias_and_validation_alias", +) +def read_list_uploadfile_alias_and_validation_alias( + p: Annotated[ + List[UploadFile], File(alias="p_alias", validation_alias="p_val_alias") + ], +): + return {"file_size": [file.size for file in p]} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias-and-validation-alias", + "/list-uploadfile-alias-and-validation-alias", + ], +) +def test_list_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + ) + | IsDict( + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + "title": "P Val Alias", + }, + ) + ) + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/list-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/list-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /list-*-alias-and-validation-alias fail here + ], + "msg": "Field required", + "input": None, + } + ] + } + + +@pytest.mark.xfail(raises=AssertionError, strict=False) +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias-and-validation-alias", + "/list-uploadfile-alias-and-validation-alias", + ], +) +def test_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", "hello"), ("p", "world")]) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /list-*-alias-and-validation-alias fail here + ], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/list-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/list-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")]) + assert response.status_code == 422, ( + response.text # /list-*-alias-and-validation-alias fails here + ) + + assert response.json() == { # pragma: no cover + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": None, + } + ] + } + + +@pytest.mark.xfail(raises=AssertionError, strict=False) +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/list-bytes-alias-and-validation-alias", + "/list-uploadfile-alias-and-validation-alias", + ], +) +def test_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post( + path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] + ) + assert response.status_code == 200, ( # all 2 fail here + response.text + ) + assert response.json() == {"file_size": [5, 5]} # pragma: no cover diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py new file mode 100644 index 0000000000..14fc0a220a --- /dev/null +++ b/tests/test_request_params/test_file/test_optional.py @@ -0,0 +1,443 @@ +from typing import Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, File, UploadFile +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/optional-bytes", operation_id="optional_bytes") +async def read_optional_bytes(p: Annotated[Optional[bytes], File()] = None): + return {"file_size": len(p) if p else None} + + +@app.post("/optional-uploadfile", operation_id="optional_uploadfile") +async def read_optional_uploadfile(p: Annotated[Optional[UploadFile], File()] = None): + return {"file_size": p.size if p else None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes", + "/optional-uploadfile", + ], +) +def test_optional_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": ( + IsDict( + { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "P", "type": "string", "format": "binary"} + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes", + "/optional-uploadfile", + ], +) +def test_optional_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes", + "/optional-uploadfile", + ], +) +def test_optional(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello")]) + assert response.status_code == 200 + assert response.json() == {"file_size": 5} + + +# ===================================================================================== +# Alias + + +@app.post("/optional-bytes-alias", operation_id="optional_bytes_alias") +async def read_optional_bytes_alias( + p: Annotated[Optional[bytes], File(alias="p_alias")] = None, +): + return {"file_size": len(p) if p else None} + + +@app.post("/optional-uploadfile-alias", operation_id="optional_uploadfile_alias") +async def read_optional_uploadfile_alias( + p: Annotated[Optional[UploadFile], File(alias="p_alias")] = None, +): + return {"file_size": p.size if p else None} + + +@pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + strict=False, +) +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias", + "/optional-uploadfile-alias", + ], +) +def test_optional_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": ( + IsDict( + { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P Alias", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "P Alias", "type": "string", "format": "binary"} + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias", + "/optional-uploadfile-alias", + ], +) +def test_optional_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias", + "/optional-uploadfile-alias", + ], +) +def test_optional_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello")]) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias", + "/optional-uploadfile-alias", + ], +) +def test_optional_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 5} + + +# ===================================================================================== +# Validation alias + + +@app.post( + "/optional-bytes-validation-alias", operation_id="optional_bytes_validation_alias" +) +def read_optional_bytes_validation_alias( + p: Annotated[Optional[bytes], File(validation_alias="p_val_alias")] = None, +): + return {"file_size": len(p) if p else None} + + +@app.post( + "/optional-uploadfile-validation-alias", + operation_id="optional_uploadfile_validation_alias", +) +def read_optional_uploadfile_validation_alias( + p: Annotated[Optional[UploadFile], File(validation_alias="p_val_alias")] = None, +): + return {"file_size": p.size if p else None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-validation-alias", + "/optional-uploadfile-validation-alias", + ], +) +def test_optional_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": ( + IsDict( + { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P Val Alias", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "P Val Alias", "type": "string", "format": "binary"} + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-validation-alias", + "/optional-uploadfile-validation-alias", + ], +) +def test_optional_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/optional-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello")]) + assert response.status_code == 200, response.text + assert response.json() == { # /optional-*-validation-alias fail here + "file_size": None + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/optional-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_val_alias", b"hello")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 5} # /optional-*-validation-alias fail here + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/optional-bytes-alias-and-validation-alias", + operation_id="optional_bytes_alias_and_validation_alias", +) +def read_optional_bytes_alias_and_validation_alias( + p: Annotated[ + Optional[bytes], File(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"file_size": len(p) if p else None} + + +@app.post( + "/optional-uploadfile-alias-and-validation-alias", + operation_id="optional_uploadfile_alias_and_validation_alias", +) +def read_optional_uploadfile_alias_and_validation_alias( + p: Annotated[ + Optional[UploadFile], File(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"file_size": p.size if p else None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias-and-validation-alias", + "/optional-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": ( + IsDict( + { + "anyOf": [ + {"type": "string", "format": "binary"}, + {"type": "null"}, + ], + "title": "P Val Alias", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "P Val Alias", "type": "string", "format": "binary"} + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias-and-validation-alias", + "/optional-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-bytes-alias-and-validation-alias", + "/optional-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/optional-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/optional-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_val_alias", b"hello")]) + assert response.status_code == 200, response.text + assert response.json() == { + "file_size": 5 + } # /optional-*-alias-and-validation-alias fail here diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py new file mode 100644 index 0000000000..f266642a66 --- /dev/null +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -0,0 +1,487 @@ +from typing import List, Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, File, UploadFile +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/optional-list-bytes") +async def read_optional_list_bytes(p: Annotated[Optional[List[bytes]], File()] = None): + return {"file_size": [len(file) for file in p] if p else None} + + +@app.post("/optional-list-uploadfile") +async def read_optional_list_uploadfile( + p: Annotated[Optional[List[UploadFile]], File()] = None, +): + return {"file_size": [file.size for file in p] if p else None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes", + "/optional-list-uploadfile", + ], +) +def test_optional_list_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "title": "P", + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes", + "/optional-list-uploadfile", + ], +) +def test_optional_list_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": None} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-bytes", + marks=pytest.mark.xfail( + raises=(TypeError, AssertionError), + condition=PYDANTIC_V2, + reason="Fails only with PDv2 due to #14297", + strict=False, + ), + ), + "/optional-list-uploadfile", + ], +) +def test_optional_list(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) + assert response.status_code == 200 + assert response.json() == {"file_size": [5, 5]} + + +# ===================================================================================== +# Alias + + +@app.post("/optional-list-bytes-alias") +async def read_optional_list_bytes_alias( + p: Annotated[Optional[List[bytes]], File(alias="p_alias")] = None, +): + return {"file_size": [len(file) for file in p] if p else None} + + +@app.post("/optional-list-uploadfile-alias") +async def read_optional_list_uploadfile_alias( + p: Annotated[Optional[List[UploadFile]], File(alias="p_alias")] = None, +): + return {"file_size": [file.size for file in p] if p else None} + + +@pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + strict=False, +) +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias", + "/optional-list-uploadfile-alias", + ], +) +def test_optional_list_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P Alias", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "title": "P Alias", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias", + "/optional-list-uploadfile-alias", + ], +) +def test_optional_list_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias", + "/optional-list-uploadfile-alias", + ], +) +def test_optional_list_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": None} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-bytes-alias", + marks=pytest.mark.xfail( + raises=(TypeError, AssertionError), + strict=False, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 model due to #14297", + ), + ), + "/optional-list-uploadfile-alias", + ], +) +def test_optional_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": [5, 5]} + + +# ===================================================================================== +# Validation alias + + +@app.post("/optional-list-bytes-validation-alias") +def read_optional_list_bytes_validation_alias( + p: Annotated[Optional[List[bytes]], File(validation_alias="p_val_alias")] = None, +): + return {"file_size": [len(file) for file in p] if p else None} + + +@app.post("/optional-list-uploadfile-validation-alias") +def read_optional_list_uploadfile_validation_alias( + p: Annotated[ + Optional[List[UploadFile]], File(validation_alias="p_val_alias") + ] = None, +): + return {"file_size": [file.size for file in p] if p else None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-validation-alias", + "/optional-list-uploadfile-validation-alias", + ], +) +def test_optional_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P Val Alias", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-validation-alias", + "/optional-list-uploadfile-validation-alias", + ], +) +def test_optional_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-bytes-validation-alias", + marks=pytest.mark.xfail( + raises=(TypeError, AssertionError), + strict=False, + reason="Fails due to #14297", + ), + ), + pytest.param( + "/optional-list-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) + assert response.status_code == 200, response.text + assert response.json() == { # /optional-list-uploadfile-validation-alias fails here + "file_size": None + } + + +@pytest.mark.xfail(raises=AssertionError, strict=False) +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-validation-alias", + "/optional-list-uploadfile-validation-alias", + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post( + path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] + ) + assert response.status_code == 200, response.text + assert response.json() == { + "file_size": [5, 5] # /optional-list-*-validation-alias fail here + } + + +# ===================================================================================== +# Alias and validation alias + + +@app.post("/optional-list-bytes-alias-and-validation-alias") +def read_optional_list_bytes_alias_and_validation_alias( + p: Annotated[ + Optional[List[bytes]], File(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"file_size": [len(file) for file in p] if p else None} + + +@app.post("/optional-list-uploadfile-alias-and-validation-alias") +def read_optional_list_uploadfile_alias_and_validation_alias( + p: Annotated[ + Optional[List[UploadFile]], + File(alias="p_alias", validation_alias="p_val_alias"), + ] = None, +): + return {"file_size": [file.size for file in p] if p else None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias-and-validation-alias", + "/optional-list-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": ( + IsDict( + { + "anyOf": [ + { + "type": "array", + "items": {"type": "string", "format": "binary"}, + }, + {"type": "null"}, + ], + "title": "P Val Alias", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string", "format": "binary"}, + } + ) + ), + }, + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias-and-validation-alias", + "/optional-list-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias-and-validation-alias", + "/optional-list-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"file_size": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail( + raises=(TypeError, AssertionError), + strict=False, + reason="Fails due to #14297", + ), + ), + pytest.param( + "/optional-list-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")]) + assert response.status_code == 200, response.text + assert ( # /optional-list-uploadfile-alias-and-validation-alias fails here + response.json() == {"file_size": None} + ) + + +@pytest.mark.xfail(raises=AssertionError, strict=False) +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-bytes-alias-and-validation-alias", + "/optional-list-uploadfile-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post( + path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] + ) + assert response.status_code == 200, response.text + assert response.json() == { + "file_size": [5, 5] # /optional-list-*-alias-and-validation-alias fail here + } diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py new file mode 100644 index 0000000000..e505973706 --- /dev/null +++ b/tests/test_request_params/test_file/test_required.py @@ -0,0 +1,536 @@ +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, File, UploadFile +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/required-bytes", operation_id="required_bytes") +async def read_required_bytes(p: Annotated[bytes, File()]): + return {"file_size": len(p)} + + +@app.post("/required-uploadfile", operation_id="required_uploadfile") +async def read_required_uploadfile(p: Annotated[UploadFile, File()]): + return {"file_size": p.size} + + +@pytest.mark.parametrize( + "path", + [ + "/required-bytes", + "/required-uploadfile", + ], +) +def test_required_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": {"title": "P", "type": "string", "format": "binary"}, + }, + "required": ["p"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/required-bytes", + "/required-uploadfile", + ], +) +def test_required_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": None, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-bytes", + "/required-uploadfile", + ], +) +def test_required(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello")]) + assert response.status_code == 200 + assert response.json() == {"file_size": 5} + + +# ===================================================================================== +# Alias + + +@app.post("/required-bytes-alias", operation_id="required_bytes_alias") +async def read_required_bytes_alias(p: Annotated[bytes, File(alias="p_alias")]): + return {"file_size": len(p)} + + +@app.post("/required-uploadfile-alias", operation_id="required_uploadfile_alias") +async def read_required_uploadfile_alias( + p: Annotated[UploadFile, File(alias="p_alias")], +): + return {"file_size": p.size} + + +@pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + strict=False, +) +@pytest.mark.parametrize( + "path", + [ + "/required-bytes-alias", + "/required-uploadfile-alias", + ], +) +def test_required_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": {"title": "P Alias", "type": "string", "format": "binary"}, + }, + "required": ["p_alias"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + [ + "/required-bytes-alias", + "/required-uploadfile-alias", + ], +) +def test_required_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-bytes-alias", + "/required-uploadfile-alias", + ], +) +def test_required_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello")]) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": None, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-bytes-alias", + "/required-uploadfile-alias", + ], +) +def test_required_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello")]) + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 5} + + +# ===================================================================================== +# Validation alias + + +@app.post( + "/required-bytes-validation-alias", operation_id="required_bytes_validation_alias" +) +def read_required_bytes_validation_alias( + p: Annotated[bytes, File(validation_alias="p_val_alias")], +): + return {"file_size": len(p)} + + +@app.post( + "/required-uploadfile-validation-alias", + operation_id="required_uploadfile_validation_alias", +) +def read_required_uploadfile_validation_alias( + p: Annotated[UploadFile, File(validation_alias="p_val_alias")], +): + return {"file_size": p.size} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/required-bytes-validation-alias", + "/required-uploadfile-validation-alias", + ], +) +def test_required_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "title": "P Val Alias", + "type": "string", + "format": "binary", + }, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ # /required-*-validation-alias fail here + "body", + "p_val_alias", + ], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files=[("p", b"hello")]) + assert response.status_code == 422, ( # /required-*-validation-alias fail here + response.text + ) + + assert response.json() == { # pragma: no cover + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_val_alias", b"hello")]) + assert response.status_code == 200, ( # all 2 fail here + response.text + ) + assert response.json() == {"file_size": 5} # pragma: no cover + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/required-bytes-alias-and-validation-alias", + operation_id="required_bytes_alias_and_validation_alias", +) +def read_required_bytes_alias_and_validation_alias( + p: Annotated[bytes, File(alias="p_alias", validation_alias="p_val_alias")], +): + return {"file_size": len(p)} + + +@app.post( + "/required-uploadfile-alias-and-validation-alias", + operation_id="required_uploadfile_alias_and_validation_alias", +) +def read_required_uploadfile_alias_and_validation_alias( + p: Annotated[UploadFile, File(alias="p_alias", validation_alias="p_val_alias")], +): + return {"file_size": p.size} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/required-bytes-alias-and-validation-alias", + "/required-uploadfile-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "title": "P Val Alias", + "type": "string", + "format": "binary", + }, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-*-alias-and-validation-alias fail here + ], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, files={"p": "hello"}) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-*-alias-and-validation-alias fail here + ], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_alias", b"hello")]) + assert response.status_code == 422, ( + response.text # /required-*-alias-and-validation-alias fails here + ) + + assert response.json() == { # pragma: no cover + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": None, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-bytes-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + pytest.param( + "/required-uploadfile-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, files=[("p_val_alias", b"hello")]) + assert response.status_code == 200, ( # all 2 fail here + response.text + ) + assert response.json() == {"file_size": 5} # pragma: no cover diff --git a/tests/test_request_params/test_file/utils.py b/tests/test_request_params/test_file/utils.py new file mode 100644 index 0000000000..e33f64385f --- /dev/null +++ b/tests/test_request_params/test_file/utils.py @@ -0,0 +1,7 @@ +from typing import Any, Dict + + +def get_body_model_name(openapi: Dict[str, Any], path: str) -> str: + body = openapi["paths"][path]["post"]["requestBody"] + body_schema = body["content"]["multipart/form-data"]["schema"] + return body_schema.get("$ref", "").split("/")[-1] diff --git a/tests/test_request_params/test_form/__init__.py b/tests/test_request_params/test_form/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py new file mode 100644 index 0000000000..c57180f6aa --- /dev/null +++ b/tests/test_request_params/test_form/test_list.py @@ -0,0 +1,527 @@ +from typing import List + +import pytest +from dirty_equals import IsDict, IsOneOf, IsPartialDict +from fastapi import FastAPI, Form +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/required-list-str", operation_id="required_list_str") +async def read_required_list_str(p: Annotated[List[str], Form()]): + return {"p": p} + + +class FormModelRequiredListStr(BaseModel): + p: List[str] + + +@app.post("/model-required-list-str", operation_id="model_required_list_str") +def read_model_required_list_str(p: Annotated[FormModelRequiredListStr, Form()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": { + "items": {"type": "string"}, + "title": "P", + "type": "array", + }, + }, + "required": ["p"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + { + "detail": [ + { + "loc": ["body", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.post("/required-list-alias", operation_id="required_list_alias") +async def read_required_list_alias(p: Annotated[List[str], Form(alias="p_alias")]): + return {"p": p} + + +class FormModelRequiredListAlias(BaseModel): + p: List[str] = Field(alias="p_alias") + + +@app.post("/model-required-list-alias", operation_id="model_required_list_alias") +async def read_model_required_list_alias( + p: Annotated[FormModelRequiredListAlias, Form()], +): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + "/model-required-list-alias", + ], +) +def test_required_list_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": { + "items": {"type": "string"}, + "title": "P Alias", + "type": "array", + }, + }, + "required": ["p_alias"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias", + pytest.param( + "/model-required-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + ], +) +def test_required_list_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf( # /model-required-list-alias with PDv2 fails here + None, {"p": ["hello", "world"]} + ), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": ["hello", "world"]}) + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Validation alias + + +@app.post( + "/required-list-validation-alias", operation_id="required_list_validation_alias" +) +def read_required_list_validation_alias( + p: Annotated[List[str], Form(validation_alias="p_val_alias")], +): + return {"p": p} + + +class FormModelRequiredListValidationAlias(BaseModel): + p: List[str] = Field(validation_alias="p_val_alias") + + +@app.post( + "/model-required-list-validation-alias", + operation_id="model_required_list_validation_alias", +) +async def read_model_required_list_validation_alias( + p: Annotated[FormModelRequiredListValidationAlias, Form()], +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "title": "P Val Alias", + "type": "array", + }, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-list-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 422, ( + response.text # /required-list-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, response.text # both fail here + + assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/required-list-alias-and-validation-alias", + operation_id="required_list_alias_and_validation_alias", +) +def read_required_list_alias_and_validation_alias( + p: Annotated[List[str], Form(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class FormModelRequiredListAliasAndValidationAlias(BaseModel): + p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.post( + "/model-required-list-alias-and-validation-alias", + operation_id="model_required_list_alias_and_validation_alias", +) +def read_model_required_list_alias_and_validation_alias( + p: Annotated[FormModelRequiredListAliasAndValidationAlias, Form()], +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "title": "P Val Alias", + "type": "array", + }, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + # /required-list-alias-and-validation-alias fails here + "p_val_alias", + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + # /required-list-alias-and-validation-alias fails here + "p_val_alias", + ], + "msg": "Field required", + "input": IsOneOf( + None, + # /model-required-list-alias-and-validation-alias fails here + {"p": ["hello", "world"]}, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": ["hello", "world"]}) + assert ( # /required-list-alias-and-validation-alias fails here + response.status_code == 422 + ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p_alias": ["hello", "world"]}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, response.text # both fail here + assert response.json() == {"p": ["hello", "world"]} # pragma: no cover diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py new file mode 100644 index 0000000000..288a0cfe44 --- /dev/null +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -0,0 +1,454 @@ +from typing import List, Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Form +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/optional-list-str", operation_id="optional_list_str") +async def read_optional_list_str( + p: Annotated[Optional[List[str]], Form()] = None, +): + return {"p": p} + + +class FormModelOptionalListStr(BaseModel): + p: Optional[List[str]] = None + + +@app.post("/model-optional-list-str", operation_id="model_optional_list_str") +async def read_model_optional_list_str(p: Annotated[FormModelOptionalListStr, Form()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p": {"items": {"type": "string"}, "type": "array", "title": "P"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.post("/optional-list-alias", operation_id="optional_list_alias") +async def read_optional_list_alias( + p: Annotated[Optional[List[str]], Form(alias="p_alias")] = None, +): + return {"p": p} + + +class FormModelOptionalListAlias(BaseModel): + p: Optional[List[str]] = Field(None, alias="p_alias") + + +@app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") +async def read_model_optional_list_alias( + p: Annotated[FormModelOptionalListAlias, Form()], +): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + strict=False, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + ), + ), + "/model-optional-list-alias", + ], +) +def test_optional_list_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_alias": { + "items": {"type": "string"}, + "type": "array", + "title": "P Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Validation alias + + +@app.post( + "/optional-list-validation-alias", operation_id="optional_list_validation_alias" +) +def read_optional_list_validation_alias( + p: Annotated[Optional[List[str]], Form(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class FormModelOptionalListValidationAlias(BaseModel): + p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + + +@app.post( + "/model-optional-list-validation-alias", + operation_id="model_optional_list_validation_alias", +) +def read_model_optional_list_validation_alias( + p: Annotated[FormModelOptionalListValidationAlias, Form()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "type": "array", + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-validation-alias", + ], +) +def test_optional_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-list-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, ( + response.text # /model-optional-list-validation-alias fails here + ) + assert response.json() == { # /optional-list-validation-alias fails here + "p": ["hello", "world"] + } + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/optional-list-alias-and-validation-alias", + operation_id="optional_list_alias_and_validation_alias", +) +def read_optional_list_alias_and_validation_alias( + p: Annotated[ + Optional[List[str]], Form(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class FormModelOptionalListAliasAndValidationAlias(BaseModel): + p: Optional[List[str]] = Field( + None, alias="p_alias", validation_alias="p_val_alias" + ) + + +@app.post( + "/model-optional-list-alias-and-validation-alias", + operation_id="model_optional_list_alias_and_validation_alias", +) +def read_model_optional_list_alias_and_validation_alias( + p: Annotated[FormModelOptionalListAliasAndValidationAlias, Form()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": { + "items": {"type": "string"}, + "type": "array", + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": ["hello", "world"]}) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-list-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": ["hello", "world"]}) + assert response.status_code == 200, ( + response.text # /model-optional-list-alias-and-validation-alias fails here + ) + assert response.json() == { + "p": [ # /optional-list-alias-and-validation-alias fails here + "hello", + "world", + ] + } diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py new file mode 100644 index 0000000000..66c003a952 --- /dev/null +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -0,0 +1,419 @@ +from typing import Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Form +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/optional-str", operation_id="optional_str") +async def read_optional_str(p: Annotated[Optional[str], Form()] = None): + return {"p": p} + + +class FormModelOptionalStr(BaseModel): + p: Optional[str] = None + + +@app.post("/model-optional-str", operation_id="model_optional_str") +async def read_model_optional_str(p: Annotated[FormModelOptionalStr, Form()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p": {"type": "string", "title": "P"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.post("/optional-alias", operation_id="optional_alias") +async def read_optional_alias( + p: Annotated[Optional[str], Form(alias="p_alias")] = None, +): + return {"p": p} + + +class FormModelOptionalAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias") + + +@app.post("/model-optional-alias", operation_id="model_optional_alias") +async def read_model_optional_alias(p: Annotated[FormModelOptionalAlias, Form()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + strict=False, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + ), + ), + "/model-optional-alias", + ], +) +def test_optional_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_alias": {"type": "string", "title": "P Alias"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.post("/optional-validation-alias", operation_id="optional_validation_alias") +def read_optional_validation_alias( + p: Annotated[Optional[str], Form(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class FormModelOptionalValidationAlias(BaseModel): + p: Optional[str] = Field(None, validation_alias="p_val_alias") + + +@app.post( + "/model-optional-validation-alias", operation_id="model_optional_validation_alias" +) +def read_model_optional_validation_alias( + p: Annotated[FormModelOptionalValidationAlias, Form()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": {"type": "string", "title": "P Val Alias"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/optional-alias-and-validation-alias", + operation_id="optional_alias_and_validation_alias", +) +def read_optional_alias_and_validation_alias( + p: Annotated[ + Optional[str], Form(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class FormModelOptionalAliasAndValidationAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + + +@app.post( + "/model-optional-alias-and-validation-alias", + operation_id="model_optional_alias_and_validation_alias", +) +def read_model_optional_alias_and_validation_alias( + p: Annotated[FormModelOptionalAliasAndValidationAlias, Form()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == IsDict( + { + "properties": { + "p_val_alias": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + }, + "title": body_model_name, + "type": "object", + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "properties": { + "p_val_alias": {"type": "string", "title": "P Val Alias"}, + }, + "title": body_model_name, + "type": "object", + } + ) + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == { + "p": "hello" # /optional-alias-and-validation-alias fails here + } diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py new file mode 100644 index 0000000000..fcbce015d5 --- /dev/null +++ b/tests/test_request_params/test_form/test_required_str.py @@ -0,0 +1,502 @@ +import pytest +from dirty_equals import IsDict, IsOneOf +from fastapi import FastAPI, Form +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +from .utils import get_body_model_name + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.post("/required-str", operation_id="required_str") +async def read_required_str(p: Annotated[str, Form()]): + return {"p": p} + + +class FormModelRequiredStr(BaseModel): + p: str + + +@app.post("/model-required-str", operation_id="model_required_str") +async def read_model_required_str(p: Annotated[FormModelRequiredStr, Form()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p": {"title": "P", "type": "string"}, + }, + "required": ["p"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.post("/required-alias", operation_id="required_alias") +async def read_required_alias(p: Annotated[str, Form(alias="p_alias")]): + return {"p": p} + + +class FormModelRequiredAlias(BaseModel): + p: str = Field(alias="p_alias") + + +@app.post("/model-required-alias", operation_id="model_required_alias") +async def read_model_required_alias(p: Annotated[FormModelRequiredAlias, Form()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2", + strict=False, + ), + ), + "/model-required-alias", + ], +) +def test_required_str_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_alias": {"title": "P Alias", "type": "string"}, + }, + "required": ["p_alias"], + "title": body_model_name, + "type": "object", + } + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": "hello"}) + assert response.status_code == 200, response.text + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.post("/required-validation-alias", operation_id="required_validation_alias") +def read_required_validation_alias( + p: Annotated[str, Form(validation_alias="p_val_alias")], +): + return {"p": p} + + +class FormModelRequiredValidationAlias(BaseModel): + p: str = Field(validation_alias="p_val_alias") + + +@app.post( + "/model-required-validation-alias", operation_id="model_required_validation_alias" +) +def read_model_required_validation_alias( + p: Annotated[FormModelRequiredValidationAlias, Form()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/required-validation-alias", "/model-required-validation-alias"], +) +def test_required_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": {"title": "P Val Alias", "type": "string"}, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 422, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": "hello"}) + assert response.status_code == 200, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias and validation alias + + +@app.post( + "/required-alias-and-validation-alias", + operation_id="required_alias_and_validation_alias", +) +def read_required_alias_and_validation_alias( + p: Annotated[str, Form(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class FormModelRequiredAliasAndValidationAlias(BaseModel): + p: str = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.post( + "/model-required-alias-and-validation-alias", + operation_id="model_required_alias_and_validation_alias", +) +def read_model_required_alias_and_validation_alias( + p: Annotated[FormModelRequiredAliasAndValidationAlias, Form()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_schema(path: str): + openapi = app.openapi() + body_model_name = get_body_model_name(openapi, path) + + assert app.openapi()["components"]["schemas"][body_model_name] == { + "properties": { + "p_val_alias": {"title": "P Val Alias", "type": "string"}, + }, + "required": ["p_val_alias"], + "title": body_model_name, + "type": "object", + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.post(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.post(path, data={"p": "hello"}) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "body", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_alias": "hello"}) + assert response.status_code == 422, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["body", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p_alias": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.post(path, data={"p_val_alias": "hello"}) + assert response.status_code == 200, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_form/utils.py b/tests/test_request_params/test_form/utils.py new file mode 100644 index 0000000000..d200650dfd --- /dev/null +++ b/tests/test_request_params/test_form/utils.py @@ -0,0 +1,7 @@ +from typing import Any, Dict + + +def get_body_model_name(openapi: Dict[str, Any], path: str) -> str: + body = openapi["paths"][path]["post"]["requestBody"] + body_schema = body["content"]["application/x-www-form-urlencoded"]["schema"] + return body_schema.get("$ref", "").split("/")[-1] diff --git a/tests/test_request_params/test_header/__init__.py b/tests/test_request_params/test_header/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py new file mode 100644 index 0000000000..1bd3628b89 --- /dev/null +++ b/tests/test_request_params/test_header/test_list.py @@ -0,0 +1,505 @@ +from typing import List + +import pytest +from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict +from fastapi import FastAPI, Header +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/required-list-str") +async def read_required_list_str(p: Annotated[List[str], Header()]): + return {"p": p} + + +class HeaderModelRequiredListStr(BaseModel): + p: List[str] + + +@app.get("/model-required-list-str") +def read_model_required_list_str(p: Annotated[HeaderModelRequiredListStr, Header()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p", + "in": "header", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["header", "p"], + "msg": "Field required", + "input": AnyThing, + } + ] + } + ) | IsDict( + { + "detail": [ + { + "loc": ["header", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.get("/required-list-alias") +async def read_required_list_alias(p: Annotated[List[str], Header(alias="p_alias")]): + return {"p": p} + + +class HeaderModelRequiredListAlias(BaseModel): + p: List[str] = Field(alias="p_alias") + + +@app.get("/model-required-list-alias") +async def read_model_required_list_alias( + p: Annotated[HeaderModelRequiredListAlias, Header()], +): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_alias", + "in": "header", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": AnyThing, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["header", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias", + pytest.param( + "/model-required-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + ], +) +def test_required_list_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": IsOneOf( # /model-required-list-alias with PDv2 fails here + None, IsPartialDict({"p": ["hello", "world"]}) + ), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["header", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias", + pytest.param( + "/model-required-list-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) + assert response.status_code == 200, ( # /model-required-list-alias fails here + response.text + ) + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Validation alias + + +@app.get("/required-list-validation-alias") +def read_required_list_validation_alias( + p: Annotated[List[str], Header(validation_alias="p_val_alias")], +): + return {"p": p} + + +class HeaderModelRequiredListValidationAlias(BaseModel): + p: List[str] = Field(validation_alias="p_val_alias") + + +@app.get("/model-required-list-validation-alias") +async def read_model_required_list_validation_alias( + p: Annotated[HeaderModelRequiredListValidationAlias, Header()], +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "header", + "p_val_alias", # /required-list-validation-alias fails here + ], + "msg": "Field required", + "input": AnyThing, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 422 # /required-list-validation-alias fails here + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get( + path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] + ) + assert response.status_code == 200, response.text # both fail here + + assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/required-list-alias-and-validation-alias") +def read_required_list_alias_and_validation_alias( + p: Annotated[List[str], Header(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class HeaderModelRequiredListAliasAndValidationAlias(BaseModel): + p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-required-list-alias-and-validation-alias") +def read_model_required_list_alias_and_validation_alias( + p: Annotated[HeaderModelRequiredListAliasAndValidationAlias, Header()], +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "header", + # /required-list-alias-and-validation-alias fails here + "p_val_alias", + ], + "msg": "Field required", + "input": AnyThing, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "header", + # /required-list-alias-and-validation-alias fails here + "p_val_alias", + ], + "msg": "Field required", + "input": IsOneOf( + None, + # /model-required-list-alias-and-validation-alias fails here + IsPartialDict({"p": ["hello", "world"]}), + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) + assert ( # /required-list-alias-and-validation-alias fails here + response.status_code == 422 + ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf( + None, + # /model-required-list-alias-and-validation-alias fails here + IsPartialDict({"p_alias": ["hello", "world"]}), + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get( + path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] + ) + assert response.status_code == 200, response.text # both fail here + assert response.json() == {"p": ["hello", "world"]} # pragma: no cover diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py new file mode 100644 index 0000000000..328f039ba3 --- /dev/null +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -0,0 +1,407 @@ +from typing import List, Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Header +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/optional-list-str") +async def read_optional_list_str( + p: Annotated[Optional[List[str]], Header()] = None, +): + return {"p": p} + + +class HeaderModelOptionalListStr(BaseModel): + p: Optional[List[str]] = None + + +@app.get("/model-optional-list-str") +async def read_model_optional_list_str( + p: Annotated[HeaderModelOptionalListStr, Header()], +): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + "name": "p", + "in": "header", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"items": {"type": "string"}, "type": "array", "title": "P"}, + "name": "p", + "in": "header", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.get("/optional-list-alias") +async def read_optional_list_alias( + p: Annotated[Optional[List[str]], Header(alias="p_alias")] = None, +): + return {"p": p} + + +class HeaderModelOptionalListAlias(BaseModel): + p: Optional[List[str]] = Field(None, alias="p_alias") + + +@app.get("/model-optional-list-alias") +async def read_model_optional_list_alias( + p: Annotated[HeaderModelOptionalListAlias, Header()], +): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": { + "items": {"type": "string"}, + "type": "array", + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias", + pytest.param( + "/model-optional-list-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) + assert response.status_code == 200 + assert response.json() == { + "p": ["hello", "world"] # /model-optional-list-alias fails here + } + + +# ===================================================================================== +# Validation alias + + +@app.get("/optional-list-validation-alias") +def read_optional_list_validation_alias( + p: Annotated[Optional[List[str]], Header(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class HeaderModelOptionalListValidationAlias(BaseModel): + p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + + +@app.get("/model-optional-list-validation-alias") +def read_model_optional_list_validation_alias( + p: Annotated[HeaderModelOptionalListValidationAlias, Header()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-validation-alias", + ], +) +def test_optional_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-list-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get( + path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] + ) + assert response.status_code == 200, ( + response.text # /model-optional-list-validation-alias fails here + ) + assert response.json() == { # /optional-list-validation-alias fails here + "p": ["hello", "world"] + } + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/optional-list-alias-and-validation-alias") +def read_optional_list_alias_and_validation_alias( + p: Annotated[ + Optional[List[str]], Header(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class HeaderModelOptionalListAliasAndValidationAlias(BaseModel): + p: Optional[List[str]] = Field( + None, alias="p_alias", validation_alias="p_val_alias" + ) + + +@app.get("/model-optional-list-alias-and-validation-alias") +def read_model_optional_list_alias_and_validation_alias( + p: Annotated[HeaderModelOptionalListAliasAndValidationAlias, Header()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p", "hello"), ("p", "world")]) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-list-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get( + path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] + ) + assert response.status_code == 200, ( + response.text # /model-optional-list-alias-and-validation-alias fails here + ) + assert response.json() == { + "p": [ # /optional-list-alias-and-validation-alias fails here + "hello", + "world", + ] + } diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py new file mode 100644 index 0000000000..d63e0a2b8c --- /dev/null +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -0,0 +1,375 @@ +from typing import Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Header +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/optional-str") +async def read_optional_str(p: Annotated[Optional[str], Header()] = None): + return {"p": p} + + +class HeaderModelOptionalStr(BaseModel): + p: Optional[str] = None + + +@app.get("/model-optional-str") +async def read_model_optional_str(p: Annotated[HeaderModelOptionalStr, Header()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "header", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "header", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.get("/optional-alias") +async def read_optional_alias( + p: Annotated[Optional[str], Header(alias="p_alias")] = None, +): + return {"p": p} + + +class HeaderModelOptionalAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias") + + +@app.get("/model-optional-alias") +async def read_model_optional_alias(p: Annotated[HeaderModelOptionalAlias, Header()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "header", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "header", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-alias", + pytest.param( + "/model-optional-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /model-optional-alias fails here + + +# ===================================================================================== +# Validation alias + + +@app.get("/optional-validation-alias") +def read_optional_validation_alias( + p: Annotated[Optional[str], Header(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class HeaderModelOptionalValidationAlias(BaseModel): + p: Optional[str] = Field(None, validation_alias="p_val_alias") + + +@app.get("/model-optional-validation-alias") +def read_model_optional_validation_alias( + p: Annotated[HeaderModelOptionalValidationAlias, Header()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_val_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/optional-alias-and-validation-alias") +def read_optional_alias_and_validation_alias( + p: Annotated[ + Optional[str], Header(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class HeaderModelOptionalAliasAndValidationAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-optional-alias-and-validation-alias") +def read_model_optional_alias_and_validation_alias( + p: Annotated[HeaderModelOptionalAliasAndValidationAlias, Header()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_val_alias": "hello"}) + assert response.status_code == 200 + assert response.json() == { + "p": "hello" # /optional-alias-and-validation-alias fails here + } diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py new file mode 100644 index 0000000000..6eb4fd6f6a --- /dev/null +++ b/tests/test_request_params/test_header/test_required_str.py @@ -0,0 +1,492 @@ +import pytest +from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict +from fastapi import FastAPI, Header +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/required-str") +async def read_required_str(p: Annotated[str, Header()]): + return {"p": p} + + +class HeaderModelRequiredStr(BaseModel): + p: str + + +@app.get("/model-required-str") +async def read_model_required_str(p: Annotated[HeaderModelRequiredStr, Header()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "header", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["header", "p"], + "msg": "Field required", + "input": AnyThing, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["header", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.get("/required-alias") +async def read_required_alias(p: Annotated[str, Header(alias="p_alias")]): + return {"p": p} + + +class HeaderModelRequiredAlias(BaseModel): + p: str = Field(alias="p_alias") + + +@app.get("/model-required-alias") +async def read_model_required_alias(p: Annotated[HeaderModelRequiredAlias, Header()]): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "header", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": AnyThing, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["header", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-alias", + pytest.param( + "/model-required-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + ], +) +def test_required_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": "hello"})), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["header", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-alias", + pytest.param( + "/model-required-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_alias": "hello"}) + assert response.status_code == 200, ( # /model-required-alias fails here + response.text + ) + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.get("/required-validation-alias") +def read_required_validation_alias( + p: Annotated[str, Header(validation_alias="p_val_alias")], +): + return {"p": p} + + +class HeaderModelRequiredValidationAlias(BaseModel): + p: str = Field(validation_alias="p_val_alias") + + +@app.get("/model-required-validation-alias") +def read_model_required_validation_alias( + p: Annotated[HeaderModelRequiredValidationAlias, Header()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-validation-alias", "/model-required-validation-alias"], +) +def test_required_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "header", + "p_val_alias", # /required-validation-alias fails here + ], + "msg": "Field required", + "input": AnyThing, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 422, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, IsPartialDict({"p": "hello"})), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_val_alias": "hello"}) + assert response.status_code == 200, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/required-alias-and-validation-alias") +def read_required_alias_and_validation_alias( + p: Annotated[str, Header(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class HeaderModelRequiredAliasAndValidationAlias(BaseModel): + p: str = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-required-alias-and-validation-alias") +def read_model_required_alias_and_validation_alias( + p: Annotated[HeaderModelRequiredAliasAndValidationAlias, Header()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "header", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "header", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": AnyThing, + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(path, headers={"p": "hello"}) + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "header", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf( # /model-alias-and-validation-alias fails here + None, + IsPartialDict({"p": "hello"}), + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_alias": "hello"}) + assert ( + response.status_code == 422 # /required-alias-and-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["header", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf( # /model-alias-and-validation-alias fails here + None, + IsPartialDict({"p_alias": "hello"}), + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(path, headers={"p_val_alias": "hello"}) + assert response.status_code == 200, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_path/__init__.py b/tests/test_request_params/test_path/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_path/test_list.py b/tests/test_request_params/test_path/test_list.py new file mode 100644 index 0000000000..bba055d9a9 --- /dev/null +++ b/tests/test_request_params/test_path/test_list.py @@ -0,0 +1 @@ +# FastAPI doesn't currently support non-scalar Path parameters diff --git a/tests/test_request_params/test_path/test_optional_list.py b/tests/test_request_params/test_path/test_optional_list.py new file mode 100644 index 0000000000..0719430ac7 --- /dev/null +++ b/tests/test_request_params/test_path/test_optional_list.py @@ -0,0 +1 @@ +# Optional Path parameters are not supported diff --git a/tests/test_request_params/test_path/test_optional_str.py b/tests/test_request_params/test_path/test_optional_str.py new file mode 100644 index 0000000000..0719430ac7 --- /dev/null +++ b/tests/test_request_params/test_path/test_optional_str.py @@ -0,0 +1 @@ +# Optional Path parameters are not supported diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py new file mode 100644 index 0000000000..8e2e60004c --- /dev/null +++ b/tests/test_request_params/test_path/test_required_str.py @@ -0,0 +1,102 @@ +import pytest +from fastapi import FastAPI, Path +from fastapi.testclient import TestClient +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + + +@app.get("/required-str/{p}") +async def read_required_str(p: Annotated[str, Path()]): + return {"p": p} + + +@app.get("/required-alias/{p_alias}") +async def read_required_alias(p: Annotated[str, Path(alias="p_alias")]): + return {"p": p} + + +@app.get("/required-validation-alias/{p_val_alias}") +def read_required_validation_alias( + p: Annotated[str, Path(validation_alias="p_val_alias")], +): + return {"p": p} # pragma: no cover + + +@app.get("/required-alias-and-validation-alias/{p_val_alias}") +def read_required_alias_and_validation_alias( + p: Annotated[str, Path(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} # pragma: no cover + + +@pytest.mark.parametrize( + ("path", "expected_name", "expected_title"), + [ + pytest.param("/required-str/{p}", "p", "P", id="required-str"), + pytest.param( + "/required-alias/{p_alias}", "p_alias", "P Alias", id="required-alias" + ), + pytest.param( + "/required-validation-alias/{p_val_alias}", + "p_val_alias", + "P Val Alias", + id="required-validation-alias", + marks=( + needs_pydanticv2, + pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ), + pytest.param( + "/required-alias-and-validation-alias/{p_val_alias}", + "p_val_alias", + "P Val Alias", + id="required-alias-and-validation-alias", + marks=( + needs_pydanticv2, + pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ), + ], +) +def test_schema(path: str, expected_name: str, expected_title: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": expected_title, "type": "string"}, + "name": expected_name, + "in": "path", + } + ] + + +@pytest.mark.parametrize( + "path", + [ + pytest.param("/required-str", id="required-str"), + pytest.param("/required-alias", id="required-alias"), + pytest.param( + "/required-validation-alias", + id="required-validation-alias", + marks=( + needs_pydanticv2, + pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ), + pytest.param( + "/required-alias-and-validation-alias", + id="required-alias-and-validation-alias", + marks=( + needs_pydanticv2, + pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ), + ], +) +def test_success(path: str): + client = TestClient(app) + response = client.get(f"{path}/hello") + assert response.status_code == 200, response.text + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_query/__init__.py b/tests/test_request_params/test_query/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py new file mode 100644 index 0000000000..4edd192e06 --- /dev/null +++ b/tests/test_request_params/test_query/test_list.py @@ -0,0 +1,506 @@ +from typing import List + +import pytest +from dirty_equals import IsDict, IsOneOf +from fastapi import FastAPI, Query +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/required-list-str") +async def read_required_list_str(p: Annotated[List[str], Query()]): + return {"p": p} + + +class QueryModelRequiredListStr(BaseModel): + p: List[str] + + +@app.get("/model-required-list-str") +def read_model_required_list_str(p: Annotated[QueryModelRequiredListStr, Query()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p", + "in": "query", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["query", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + { + "detail": [ + { + "loc": ["query", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-list-str", "/model-required-list-str"], +) +def test_required_list_str(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.get("/required-list-alias") +async def read_required_list_alias(p: Annotated[List[str], Query(alias="p_alias")]): + return {"p": p} + + +class QueryModelRequiredListAlias(BaseModel): + p: List[str] = Field(alias="p_alias") + + +@app.get("/model-required-list-alias") +async def read_model_required_list_alias( + p: Annotated[QueryModelRequiredListAlias, Query()], +): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_alias", + "in": "query", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-list-alias", "/model-required-list-alias"], +) +def test_required_list_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["query", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias", + pytest.param( + "/model-required-list-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + ], +) +def test_required_list_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf( # /model-required-list-alias with PDv2 fails here + None, {"p": ["hello", "world"]} + ), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["query", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias", + pytest.param( + "/model-required-list-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello&p_alias=world") + assert response.status_code == 200, ( # /model-required-list-alias fails here + response.text + ) + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Validation alias + + +@app.get("/required-list-validation-alias") +def read_required_list_validation_alias( + p: Annotated[List[str], Query(validation_alias="p_val_alias")], +): + return {"p": p} + + +class QueryModelRequiredListValidationAlias(BaseModel): + p: List[str] = Field(validation_alias="p_val_alias") + + +@app.get("/model-required-list-validation-alias") +async def read_model_required_list_validation_alias( + p: Annotated[QueryModelRequiredListValidationAlias, Query()], +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "p_val_alias", # /required-list-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-validation-alias", + ], +) +def test_required_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 422 # /required-list-validation-alias fails here + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": ["hello", "world"]}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-list-validation-alias", "/model-required-list-validation-alias"], +) +def test_required_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") + assert response.status_code == 200, response.text # both fail here + + assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/required-list-alias-and-validation-alias") +def read_required_list_alias_and_validation_alias( + p: Annotated[List[str], Query(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class QueryModelRequiredListAliasAndValidationAlias(BaseModel): + p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-required-list-alias-and-validation-alias") +def read_model_required_list_alias_and_validation_alias( + p: Annotated[QueryModelRequiredListAliasAndValidationAlias, Query()], +): + return {"p": p.p} # pragma: no cover + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": { + "title": "P Val Alias", + "type": "array", + "items": {"type": "string"}, + }, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "query", + # /required-list-alias-and-validation-alias fails here + "p_val_alias", + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "query", + # /required-list-alias-and-validation-alias fails here + "p_val_alias", + ], + "msg": "Field required", + "input": IsOneOf( + None, + # /model-required-list-alias-and-validation-alias fails here + { + "p": [ + "hello", + "world", + ] + }, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello&p_alias=world") + assert ( # /required-list-alias-and-validation-alias fails here + response.status_code == 422 + ) + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf( + None, + # /model-required-list-alias-and-validation-alias fails here + {"p_alias": ["hello", "world"]}, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-list-alias-and-validation-alias", + "/model-required-list-alias-and-validation-alias", + ], +) +def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") + assert response.status_code == 200, response.text # both fail here + assert response.json() == {"p": ["hello", "world"]} # pragma: no cover diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py new file mode 100644 index 0000000000..76f960554c --- /dev/null +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -0,0 +1,403 @@ +from typing import List, Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Query +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/optional-list-str") +async def read_optional_list_str( + p: Annotated[Optional[List[str]], Query()] = None, +): + return {"p": p} + + +class QueryModelOptionalListStr(BaseModel): + p: Optional[List[str]] = None + + +@app.get("/model-optional-list-str") +async def read_model_optional_list_str( + p: Annotated[QueryModelOptionalListStr, Query()], +): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P", + }, + "name": "p", + "in": "query", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"items": {"type": "string"}, "type": "array", "title": "P"}, + "name": "p", + "in": "query", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200, response.text + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-str", "/model-optional-list-str"], +) +def test_optional_list_str(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 200 + assert response.json() == {"p": ["hello", "world"]} + + +# ===================================================================================== +# Alias + + +@app.get("/optional-list-alias") +async def read_optional_list_alias( + p: Annotated[Optional[List[str]], Query(alias="p_alias")] = None, +): + return {"p": p} + + +class QueryModelOptionalListAlias(BaseModel): + p: Optional[List[str]] = Field(None, alias="p_alias") + + +@app.get("/model-optional-list-alias") +async def read_model_optional_list_alias( + p: Annotated[QueryModelOptionalListAlias, Query()], +): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": { + "items": {"type": "string"}, + "type": "array", + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-list-alias", "/model-optional-list-alias"], +) +def test_optional_list_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias", + pytest.param( + "/model-optional-list-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_list_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello&p_alias=world") + assert response.status_code == 200 + assert response.json() == { + "p": ["hello", "world"] # /model-optional-list-alias fails here + } + + +# ===================================================================================== +# Validation alias + + +@app.get("/optional-list-validation-alias") +def read_optional_list_validation_alias( + p: Annotated[Optional[List[str]], Query(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class QueryModelOptionalListValidationAlias(BaseModel): + p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + + +@app.get("/model-optional-list-validation-alias") +def read_model_optional_list_validation_alias( + p: Annotated[QueryModelOptionalListValidationAlias, Query()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-validation-alias", + ], +) +def test_optional_list_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 200 + assert response.json() == {"p": None} # /optional-list-validation-alias fails here + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], +) +def test_optional_list_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") + assert response.status_code == 200, ( + response.text # /model-optional-list-validation-alias fails here + ) + assert response.json() == { # /optional-list-validation-alias fails here + "p": ["hello", "world"] + } + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/optional-list-alias-and-validation-alias") +def read_optional_list_alias_and_validation_alias( + p: Annotated[ + Optional[List[str]], Query(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class QueryModelOptionalListAliasAndValidationAlias(BaseModel): + p: Optional[List[str]] = Field( + None, alias="p_alias", validation_alias="p_val_alias" + ) + + +@app.get("/model-optional-list-alias-and-validation-alias") +def read_model_optional_list_alias_and_validation_alias( + p: Annotated[QueryModelOptionalListAliasAndValidationAlias, Query()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [ + {"items": {"type": "string"}, "type": "array"}, + {"type": "null"}, + ], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello&p=world") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-list-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello&p_alias=world") + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-list-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-list-alias-and-validation-alias", + "/model-optional-list-alias-and-validation-alias", + ], +) +def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") + assert response.status_code == 200, ( + response.text # /model-optional-list-alias-and-validation-alias fails here + ) + assert response.json() == { + "p": [ # /optional-list-alias-and-validation-alias fails here + "hello", + "world", + ] + } diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py new file mode 100644 index 0000000000..77da9bee61 --- /dev/null +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -0,0 +1,375 @@ +from typing import Optional + +import pytest +from dirty_equals import IsDict +from fastapi import FastAPI, Query +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/optional-str") +async def read_optional_str(p: Optional[str] = None): + return {"p": p} + + +class QueryModelOptionalStr(BaseModel): + p: Optional[str] = None + + +@app.get("/model-optional-str") +async def read_model_optional_str(p: Annotated[QueryModelOptionalStr, Query()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P", + }, + "name": "p", + "in": "query", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "query", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-str", "/model-optional-str"], +) +def test_optional_str(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.get("/optional-alias") +async def read_optional_alias( + p: Annotated[Optional[str], Query(alias="p_alias")] = None, +): + return {"p": p} + + +class QueryModelOptionalAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias") + + +@app.get("/model-optional-alias") +async def read_model_optional_alias(p: Annotated[QueryModelOptionalAlias, Query()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + IsDict( + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Alias", + }, + "name": "p_alias", + "in": "query", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "required": False, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "query", + } + ) + ] + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + ["/optional-alias", "/model-optional-alias"], +) +def test_optional_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@pytest.mark.parametrize( + "path", + [ + "/optional-alias", + pytest.param( + "/model-optional-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_optional_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello") + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /model-optional-alias fails here + + +# ===================================================================================== +# Validation alias + + +@app.get("/optional-validation-alias") +def read_optional_validation_alias( + p: Annotated[Optional[str], Query(validation_alias="p_val_alias")] = None, +): + return {"p": p} + + +class QueryModelOptionalValidationAlias(BaseModel): + p: Optional[str] = Field(None, validation_alias="p_val_alias") + + +@app.get("/model-optional-validation-alias") +def read_model_optional_validation_alias( + p: Annotated[QueryModelOptionalValidationAlias, Query()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + ["/optional-validation-alias", "/model-optional-validation-alias"], +) +def test_optional_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-validation-alias", + ], +) +def test_optional_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello") + assert response.status_code == 200 + assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/optional-alias-and-validation-alias") +def read_optional_alias_and_validation_alias( + p: Annotated[ + Optional[str], Query(alias="p_alias", validation_alias="p_val_alias") + ] = None, +): + return {"p": p} + + +class QueryModelOptionalAliasAndValidationAlias(BaseModel): + p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-optional-alias-and-validation-alias") +def read_model_optional_alias_and_validation_alias( + p: Annotated[QueryModelOptionalAliasAndValidationAlias, Query()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": False, + "schema": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "P Val Alias", + }, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + "/optional-alias-and-validation-alias", + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 200 + assert response.json() == {"p": None} + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello") + assert response.status_code == 200 + assert response.json() == { + "p": None # /optional-alias-and-validation-alias fails here + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/optional-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-optional-alias-and-validation-alias", + ], +) +def test_optional_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello") + assert response.status_code == 200 + assert response.json() == { + "p": "hello" # /optional-alias-and-validation-alias fails here + } diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py new file mode 100644 index 0000000000..aa3a27683c --- /dev/null +++ b/tests/test_request_params/test_query/test_required_str.py @@ -0,0 +1,495 @@ +import pytest +from dirty_equals import IsDict, IsOneOf +from fastapi import FastAPI, Query +from fastapi._compat import PYDANTIC_V2 +from fastapi.testclient import TestClient +from pydantic import BaseModel, Field +from typing_extensions import Annotated + +from tests.utils import needs_pydanticv2 + +app = FastAPI() + +# ===================================================================================== +# Without aliases + + +@app.get("/required-str") +async def read_required_str(p: str): + return {"p": p} + + +class QueryModelRequiredStr(BaseModel): + p: str + + +@app.get("/model-required-str") +async def read_model_required_str(p: Annotated[QueryModelRequiredStr, Query()]): + return {"p": p.p} + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P", "type": "string"}, + "name": "p", + "in": "query", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["query", "p"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["query", "p"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + ["/required-str", "/model-required-str"], +) +def test_required_str(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 200 + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias + + +@app.get("/required-alias") +async def read_required_alias(p: Annotated[str, Query(alias="p_alias")]): + return {"p": p} + + +class QueryModelRequiredAlias(BaseModel): + p: str = Field(alias="p_alias") + + +@app.get("/model-required-alias") +async def read_model_required_alias(p: Annotated[QueryModelRequiredAlias, Query()]): + return {"p": p.p} # pragma: no cover + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_str_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Alias", "type": "string"}, + "name": "p_alias", + "in": "query", + } + ] + + +@pytest.mark.parametrize( + "path", + ["/required-alias", "/model-required-alias"], +) +def test_required_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["query", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-alias", + pytest.param( + "/model-required-alias", + marks=pytest.mark.xfail( + raises=AssertionError, + condition=PYDANTIC_V2, + reason="Fails only with PDv2 models", + strict=False, + ), + ), + ], +) +def test_required_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_alias"], + "msg": "Field required", + "input": IsOneOf( + None, + {"p": "hello"}, # /model-required-alias PDv2 fails here + ), + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["query", "p_alias"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +@pytest.mark.parametrize( + "path", + [ + "/required-alias", + pytest.param( + "/model-required-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + ], +) +def test_required_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello") + assert response.status_code == 200, ( # /model-required-alias fails here + response.text + ) + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Validation alias + + +@app.get("/required-validation-alias") +def read_required_validation_alias( + p: Annotated[str, Query(validation_alias="p_val_alias")], +): + return {"p": p} + + +class QueryModelRequiredValidationAlias(BaseModel): + p: str = Field(validation_alias="p_val_alias") + + +@app.get("/model-required-validation-alias") +def read_model_required_validation_alias( + p: Annotated[QueryModelRequiredValidationAlias, Query()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + ["/required-validation-alias", "/model-required-validation-alias"], +) +def test_required_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "p_val_alias", # /required-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 422, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf(None, {"p": "hello"}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-validation-alias", + ], +) +def test_required_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello") + assert response.status_code == 200, ( # /required-validation-alias fails here + response.text + ) + + assert response.json() == {"p": "hello"} + + +# ===================================================================================== +# Alias and validation alias + + +@app.get("/required-alias-and-validation-alias") +def read_required_alias_and_validation_alias( + p: Annotated[str, Query(alias="p_alias", validation_alias="p_val_alias")], +): + return {"p": p} + + +class QueryModelRequiredAliasAndValidationAlias(BaseModel): + p: str = Field(alias="p_alias", validation_alias="p_val_alias") + + +@app.get("/model-required-alias-and-validation-alias") +def read_model_required_alias_and_validation_alias( + p: Annotated[QueryModelRequiredAliasAndValidationAlias, Query()], +): + return {"p": p.p} + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_schema(path: str): + assert app.openapi()["paths"][path]["get"]["parameters"] == [ + { + "required": True, + "schema": {"title": "P Val Alias", "type": "string"}, + "name": "p_val_alias", + "in": "query", + } + ] + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_missing(path: str): + client = TestClient(app) + response = client.get(path) + assert response.status_code == 422 + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf(None, {}), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_name(path: str): + client = TestClient(app) + response = client.get(f"{path}?p=hello") + assert response.status_code == 422 + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": [ + "query", + "p_val_alias", # /required-alias-and-validation-alias fails here + ], + "msg": "Field required", + "input": IsOneOf( # /model-alias-and-validation-alias fails here + None, + {"p": "hello"}, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.xfail(raises=AssertionError, strict=False) +@pytest.mark.parametrize( + "path", + [ + "/required-alias-and-validation-alias", + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_alias=hello") + assert ( + response.status_code == 422 # /required-alias-and-validation-alias fails here + ) + + assert response.json() == { + "detail": [ + { + "type": "missing", + "loc": ["query", "p_val_alias"], + "msg": "Field required", + "input": IsOneOf( # /model-alias-and-validation-alias fails here + None, + {"p_alias": "hello"}, + ), + } + ] + } + + +@needs_pydanticv2 +@pytest.mark.parametrize( + "path", + [ + pytest.param( + "/required-alias-and-validation-alias", + marks=pytest.mark.xfail(raises=AssertionError, strict=False), + ), + "/model-required-alias-and-validation-alias", + ], +) +def test_required_alias_and_validation_alias_by_validation_alias(path: str): + client = TestClient(app) + response = client.get(f"{path}?p_val_alias=hello") + assert response.status_code == 200, ( + response.text # /required-alias-and-validation-alias fails here + ) + + assert response.json() == {"p": "hello"} From 4b905b614c84fbf4a278bdb69f4a22d52a43721e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Dec 2025 16:16:13 +0000 Subject: [PATCH 051/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index a6fc2f9238..5bb7698c8f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Refactors + +* ✅ Add set of tests for request parameters and alias. PR [#14358](https://github.com/fastapi/fastapi/pull/14358) by [@YuriiMotov](https://github.com/YuriiMotov). + ### Docs * 📝 Tweak links format. PR [#14505](https://github.com/fastapi/fastapi/pull/14505) by [@tiangolo](https://github.com/tiangolo). From f8b216df30f4d4fd36dccf7c7e885154a2699838 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:25:03 +0100 Subject: [PATCH 052/140] =?UTF-8?q?=F0=9F=8C=90=20Sync=20Russian=20docs=20?= =?UTF-8?q?(#14509)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translate missing pages * Update outdated translations --- docs/ru/docs/_llm-test.md | 4 +- docs/ru/docs/advanced/additional-responses.md | 4 +- .../ru/docs/advanced/advanced-dependencies.md | 2 +- docs/ru/docs/advanced/behind-a-proxy.md | 10 ++- docs/ru/docs/advanced/dataclasses.md | 6 +- docs/ru/docs/advanced/openapi-callbacks.md | 8 +- .../path-operation-advanced-configuration.md | 10 +-- docs/ru/docs/advanced/response-directly.md | 2 +- docs/ru/docs/advanced/settings.md | 8 +- docs/ru/docs/deployment/cloud.md | 14 +++- docs/ru/docs/deployment/fastapicloud.md | 65 ++++++++++++++++ docs/ru/docs/deployment/index.md | 4 +- .../authentication-error-status-code.md | 17 ++++ docs/ru/docs/how-to/configure-swagger-ui.md | 2 +- .../docs/how-to/custom-request-and-route.md | 12 +-- docs/ru/docs/index.md | 62 ++++++++++++++- docs/ru/docs/project-generation.md | 4 +- docs/ru/docs/resources/index.md | 2 +- docs/ru/docs/tutorial/bigger-applications.md | 78 ++++--------------- docs/ru/docs/tutorial/cookie-param-models.md | 2 +- docs/ru/docs/tutorial/first-steps.md | 57 ++++++++++++++ docs/ru/docs/tutorial/handling-errors.md | 39 ++++------ docs/ru/docs/tutorial/security/index.md | 18 ++--- docs/ru/docs/tutorial/sql-databases.md | 6 +- docs/ru/docs/tutorial/testing.md | 54 +------------ docs/ru/docs/virtual-environments.md | 22 +++++- 26 files changed, 317 insertions(+), 195 deletions(-) create mode 100644 docs/ru/docs/deployment/fastapicloud.md create mode 100644 docs/ru/docs/how-to/authentication-error-status-code.md diff --git a/docs/ru/docs/_llm-test.md b/docs/ru/docs/_llm-test.md index 476cc19244..9a15f6bb21 100644 --- a/docs/ru/docs/_llm-test.md +++ b/docs/ru/docs/_llm-test.md @@ -15,7 +15,7 @@ Тесты: -## Фрагменты кода { #code-snippets} +## Фрагменты кода { #code-snippets } //// tab | Тест @@ -53,7 +53,7 @@ LLM, вероятно, переведёт это неправильно. Инт //// -## Кавычки во фрагментах кода { #quotes-in-code-snippets} +## Кавычки во фрагментах кода { #quotes-in-code-snippets } //// tab | Тест diff --git a/docs/ru/docs/advanced/additional-responses.md b/docs/ru/docs/advanced/additional-responses.md index c63c0c08b0..1fc3715e41 100644 --- a/docs/ru/docs/advanced/additional-responses.md +++ b/docs/ru/docs/advanced/additional-responses.md @@ -175,7 +175,7 @@ Например, вы можете добавить дополнительный тип содержимого `image/png`, объявив, что ваша операция пути может возвращать JSON‑объект (с типом содержимого `application/json`) или PNG‑изображение: -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} /// note | Примечание @@ -237,7 +237,7 @@ new_dict = {**old_dict, "new key": "new value"} Например: -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} ## Дополнительная информация об ответах OpenAPI { #more-information-about-openapi-responses } diff --git a/docs/ru/docs/advanced/advanced-dependencies.md b/docs/ru/docs/advanced/advanced-dependencies.md index 339c0a3631..cc6691b300 100644 --- a/docs/ru/docs/advanced/advanced-dependencies.md +++ b/docs/ru/docs/advanced/advanced-dependencies.md @@ -144,7 +144,7 @@ checker(q="somequery") ### Фоновые задачи и зависимости с `yield`, технические детали { #background-tasks-and-dependencies-with-yield-technical-details } -До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали. +До FastAPI 0.106.0 вызывать исключения после `yield` было невозможно: код после `yield` в зависимостях выполнялся уже после отправки ответа, поэтому [Обработчики исключений](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} к тому моменту уже отработали. Так было сделано в основном для того, чтобы можно было использовать те же объекты, «отданные» зависимостями через `yield`, внутри фоновых задач, потому что код после `yield` выполнялся после завершения фоновых задач. diff --git a/docs/ru/docs/advanced/behind-a-proxy.md b/docs/ru/docs/advanced/behind-a-proxy.md index 281cb7f735..7119efe2dc 100644 --- a/docs/ru/docs/advanced/behind-a-proxy.md +++ b/docs/ru/docs/advanced/behind-a-proxy.md @@ -64,7 +64,7 @@ https://mysuperapp.com/items/ /// -### Как работают пересылаемые заголовки прокси +### Как работают пересылаемые заголовки прокси { #how-proxy-forwarded-headers-work } Ниже показано, как прокси добавляет пересылаемые заголовки между клиентом и сервером приложения: @@ -443,6 +443,14 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 /// +/// note | Технические детали + +Свойство `servers` в спецификации OpenAPI является необязательным. + +Если вы не укажете параметр `servers`, а `root_path` равен `/`, то свойство `servers` в сгенерированной схеме OpenAPI по умолчанию будет опущено. Это эквивалентно серверу со значением `url` равным `/`. + +/// + ### Отключить автоматическое добавление сервера из `root_path` { #disable-automatic-server-from-root-path } Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`: diff --git a/docs/ru/docs/advanced/dataclasses.md b/docs/ru/docs/advanced/dataclasses.md index 816f744048..c37ce30236 100644 --- a/docs/ru/docs/advanced/dataclasses.md +++ b/docs/ru/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в Но FastAPI также поддерживает использование `dataclasses` тем же способом: -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} Это по-прежнему поддерживается благодаря **Pydantic**, так как в нём есть встроенная поддержка `dataclasses`. @@ -32,7 +32,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в Вы также можете использовать `dataclasses` в параметре `response_model`: -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} Этот dataclass будет автоматически преобразован в Pydantic dataclass. @@ -48,7 +48,7 @@ FastAPI построен поверх **Pydantic**, и я показывал в В таком случае вы можете просто заменить стандартные `dataclasses` на `pydantic.dataclasses`, которая является полностью совместимой заменой (drop-in replacement): -{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} +{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Мы по-прежнему импортируем `field` из стандартных `dataclasses`. diff --git a/docs/ru/docs/advanced/openapi-callbacks.md b/docs/ru/docs/advanced/openapi-callbacks.md index faf58370be..de7e283017 100644 --- a/docs/ru/docs/advanced/openapi-callbacks.md +++ b/docs/ru/docs/advanced/openapi-callbacks.md @@ -31,7 +31,7 @@ Эта часть вполне обычна, большая часть кода вам уже знакома: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} /// tip | Совет @@ -90,7 +90,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) Сначала создайте новый `APIRouter`, который будет содержать один или несколько обратных вызовов. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} ### Создайте *операцию пути* для обратного вызова { #create-the-callback-path-operation } @@ -101,7 +101,7 @@ httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) * Вероятно, в ней должно быть объявление тела запроса, например `body: InvoiceEvent`. * А также может быть объявление модели ответа, например `response_model=InvoiceEventReceived`. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} Есть 2 основных отличия от обычной *операции пути*: @@ -169,7 +169,7 @@ https://www.external.org/events/invoices/2expen51ve Теперь используйте параметр `callbacks` в *декораторе операции пути вашего API*, чтобы передать атрибут `.routes` (это, по сути, просто `list` маршрутов/*операций пути*) из этого маршрутизатора обратных вызовов: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md index fcb3cd47f2..78a16a5583 100644 --- a/docs/ru/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md @@ -50,7 +50,7 @@ Эта часть не попадёт в документацию, но другие инструменты (например, Sphinx) смогут использовать остальное. -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} ## Дополнительные ответы { #additional-responses } @@ -155,13 +155,13 @@ //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} //// @@ -179,13 +179,13 @@ //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} //// diff --git a/docs/ru/docs/advanced/response-directly.md b/docs/ru/docs/advanced/response-directly.md index febd40ed4a..3c10633e91 100644 --- a/docs/ru/docs/advanced/response-directly.md +++ b/docs/ru/docs/advanced/response-directly.md @@ -34,7 +34,7 @@ В таких случаях вы можете использовать `jsonable_encoder` для преобразования данных перед передачей их в ответ: -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} /// note | Технические детали diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md index a335548c3c..0ef46fb13c 100644 --- a/docs/ru/docs/advanced/settings.md +++ b/docs/ru/docs/advanced/settings.md @@ -148,7 +148,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Продолжая предыдущий пример, ваш файл `config.py` может выглядеть так: -{* ../../docs_src/settings/app02/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} Обратите внимание, что теперь мы не создаем экземпляр по умолчанию `settings = Settings()`. @@ -174,7 +174,7 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Далее будет очень просто предоставить другой объект настроек во время тестирования, создав переопределение зависимости для `get_settings`: -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} В переопределении зависимости мы задаем новое значение `admin_email` при создании нового объекта `Settings`, а затем возвращаем этот новый объект. @@ -217,7 +217,7 @@ APP_NAME="ChimichangApp" //// tab | Pydantic v2 -{* ../../docs_src/settings/app03_an/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Совет @@ -229,7 +229,7 @@ APP_NAME="ChimichangApp" //// tab | Pydantic v1 -{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} +{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} /// tip | Совет diff --git a/docs/ru/docs/deployment/cloud.md b/docs/ru/docs/deployment/cloud.md index a400d18434..955db2a157 100644 --- a/docs/ru/docs/deployment/cloud.md +++ b/docs/ru/docs/deployment/cloud.md @@ -4,11 +4,19 @@ В большинстве случаев у основных облачных провайдеров есть руководства по развертыванию FastAPI на их платформе. +## FastAPI Cloud { #fastapi-cloud } + +**FastAPI Cloud** создан тем же автором и командой, стоящими за **FastAPI**. + +Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями. + +Он переносит тот же **опыт разработчика** создания приложений с FastAPI на их **развертывание** в облаке. 🎉 + +FastAPI Cloud — основной спонсор и источник финансирования для open source проектов *FastAPI and friends*. ✨ + ## Облачные провайдеры — спонсоры { #cloud-providers-sponsors } -Некоторые облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ — это обеспечивает непрерывное и здоровое развитие FastAPI и его экосистемы. - -И это показывает их искреннюю приверженность FastAPI и его сообществу (вам): они не только хотят предоставить вам хороший сервис, но и стремятся гарантировать, что у вас будет хороший и стабильный фреймворк — FastAPI. 🙇 +Некоторые другие облачные провайдеры ✨ [**спонсируют FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ тоже. 🙇 Возможно, вы захотите попробовать их сервисы и воспользоваться их руководствами: diff --git a/docs/ru/docs/deployment/fastapicloud.md b/docs/ru/docs/deployment/fastapicloud.md new file mode 100644 index 0000000000..9e7430ecb0 --- /dev/null +++ b/docs/ru/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +Вы можете развернуть своё приложение FastAPI в FastAPI Cloud одной командой, присоединяйтесь к списку ожидания, если ещё не сделали этого. 🚀 + +## Вход { #login } + +Убедитесь, что у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉). + +Затем выполните вход: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## Деплой { #deploy } + +Теперь разверните приложение одной командой: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +Вот и всё! Теперь вы можете открыть своё приложение по этому URL. ✨ + +## О FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** создан тем же автором и командой, что и **FastAPI**. + +Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями. + +Он переносит тот же **опыт разработчика**, что вы получаете при создании приложений на FastAPI, на их **развертывание** в облаке. 🎉 + +Он также возьмёт на себя большинство вещей, которые требуются при развертывании приложения, например: + +* HTTPS +* Репликация с автоматическим масштабированием на основе запросов +* и т.д. + +FastAPI Cloud — основной спонсор и источник финансирования open source‑проектов «FastAPI и друзья». ✨ + +## Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers } + +FastAPI — проект с открытым исходным кодом и основан на стандартах. Вы можете развернуть приложения FastAPI у любого облачного провайдера на ваш выбор. + +Следуйте руководствам вашего облачного провайдера, чтобы развернуть приложения FastAPI у них. 🤓 + +## Развертывание на собственном сервере { #deploy-your-own-server } + +Позже в этом руководстве по **развертыванию** я также расскажу все детали — чтобы вы понимали, что происходит, что нужно сделать и как развернуть приложения FastAPI самостоятельно, в том числе на собственных серверах. 🤓 diff --git a/docs/ru/docs/deployment/index.md b/docs/ru/docs/deployment/index.md index c85fa0d529..ffb77641dd 100644 --- a/docs/ru/docs/deployment/index.md +++ b/docs/ru/docs/deployment/index.md @@ -12,10 +12,12 @@ ## Стратегии развёртывания { #deployment-strategies } -В зависимости от вашего конкретного случая, есть несколько способов сделать это. +Есть несколько способов сделать это, в зависимости от вашего конкретного случая и используемых вами инструментов. Вы можете **развернуть сервер** самостоятельно, используя различные инструменты. Например, можно использовать **облачный сервис**, который выполнит часть работы за вас. Также возможны и другие варианты. +Например, мы, команда, стоящая за FastAPI, создали **FastAPI Cloud**, чтобы сделать развёртывание приложений FastAPI в облаке как можно более простым и прямолинейным, с тем же удобством для разработчика, что и при работе с FastAPI. + В этом блоке я покажу вам некоторые из основных концепций, которые вы, вероятно, должны иметь в виду при развертывании приложения **FastAPI** (хотя большинство из них применимо к любому другому типу веб-приложений). В последующих разделах вы узнаете больше деталей и методов, необходимых для этого. ✨ diff --git a/docs/ru/docs/how-to/authentication-error-status-code.md b/docs/ru/docs/how-to/authentication-error-status-code.md new file mode 100644 index 0000000000..5675cecc52 --- /dev/null +++ b/docs/ru/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# Использование старых статус-кодов ошибок аутентификации 403 { #use-old-403-authentication-error-status-codes } + +До версии FastAPI `0.122.0`, когда встроенные утилиты безопасности возвращали ошибку клиенту после неудачной аутентификации, они использовали HTTP статус-код `403 Forbidden`. + +Начиная с версии FastAPI `0.122.0`, используется более подходящий HTTP статус-код `401 Unauthorized`, и в ответе возвращается имеющий смысл HTTP-заголовок `WWW-Authenticate` в соответствии со спецификациями HTTP, RFC 7235, RFC 9110. + +Но если по какой-то причине ваши клиенты зависят от старого поведения, вы можете вернуть его, переопределив метод `make_not_authenticated_error` в ваших Security-классах. + +Например, вы можете создать подкласс `HTTPBearer`, который будет возвращать ошибку `403 Forbidden` вместо стандартной `401 Unauthorized`: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} + +/// tip | Совет + +Обратите внимание, что функция возвращает экземпляр исключения, не вызывает его. Выброс выполняется остальным внутренним кодом. + +/// diff --git a/docs/ru/docs/how-to/configure-swagger-ui.md b/docs/ru/docs/how-to/configure-swagger-ui.md index 4793cc9db3..9d104423d7 100644 --- a/docs/ru/docs/how-to/configure-swagger-ui.md +++ b/docs/ru/docs/how-to/configure-swagger-ui.md @@ -40,7 +40,7 @@ FastAPI включает некоторые параметры конфигур Это включает следующие настройки по умолчанию: -{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} Вы можете переопределить любую из них, указав другое значение в аргументе `swagger_ui_parameters`. diff --git a/docs/ru/docs/how-to/custom-request-and-route.md b/docs/ru/docs/how-to/custom-request-and-route.md index 1b8d7f7ed3..feef9670ad 100644 --- a/docs/ru/docs/how-to/custom-request-and-route.md +++ b/docs/ru/docs/how-to/custom-request-and-route.md @@ -42,7 +42,7 @@ Таким образом, один и тот же класс маршрута сможет обрабатывать как gzip-сжатые, так и несжатые запросы. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} ### Создать пользовательский класс `GzipRoute` { #create-a-custom-gziproute-class } @@ -54,7 +54,7 @@ Здесь мы используем её, чтобы создать `GzipRequest` из исходного HTTP-запроса. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} /// note | Технические детали @@ -92,18 +92,18 @@ Нужно лишь обработать запрос внутри блока `try`/`except`: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} Если произойдёт исключение, экземпляр `Request` всё ещё будет в области видимости, поэтому мы сможем прочитать тело запроса и использовать его при обработке ошибки: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} ## Пользовательский класс `APIRoute` в роутере { #custom-apiroute-class-in-a-router } Вы также можете задать параметр `route_class` у `APIRouter`: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} В этом примере *операции пути*, объявленные в `router`, будут использовать пользовательский класс `TimedRoute` и получат дополнительный HTTP-заголовок `X-Response-Time` в ответе с временем, затраченным на формирование ответа: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/ru/docs/index.md b/docs/ru/docs/index.md index 75cd63223d..b562cbe5bc 100644 --- a/docs/ru/docs/index.md +++ b/docs/ru/docs/index.md @@ -52,14 +52,20 @@ FastAPI — это современный, быстрый (высокопрои -{% if sponsors %} +### Ключевой-спонсор { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} + +{% endfor -%} + +### Золотые и серебряные спонсоры { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} {% endfor -%} {%- for sponsor in sponsors.silver -%} {% endfor %} -{% endif %} @@ -444,6 +450,58 @@ item: Item * **сессии с использованием cookie** * ...и многое другое. +### Разверните приложение (опционально) { #deploy-your-app-optional } + +При желании вы можете развернуть своё приложение FastAPI в FastAPI Cloud, присоединяйтесь к списку ожидания, если ещё не сделали этого. 🚀 + +Если у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉), вы можете развернуть ваше приложение одной командой. + +Перед развертыванием убедитесь, что вы вошли в систему: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Затем разверните приложение: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +Вот и всё! Теперь вы можете открыть ваше приложение по этой ссылке. ✨ + +#### О FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** создан тем же автором и командой, что и **FastAPI**. + +Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API при минимальных усилиях. + +Он переносит тот же **опыт разработчика**, что и при создании приложений на FastAPI, на их **развертывание** в облаке. 🎉 + +FastAPI Cloud — основной спонсор и источник финансирования для проектов с открытым исходным кодом из экосистемы *FastAPI and friends*. ✨ + +#### Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers } + +FastAPI — это open source и стандартизированный фреймворк. Вы можете развернуть приложения FastAPI у любого облачного провайдера на ваш выбор. + +Следуйте руководствам вашего облачного провайдера по развертыванию приложений FastAPI. 🤓 + ## Производительность { #performance } Независимые бенчмарки TechEmpower показывают приложения **FastAPI**, работающие под управлением Uvicorn, как один из самых быстрых доступных фреймворков Python, уступающий только самим Starlette и Uvicorn (используются внутри FastAPI). (*) diff --git a/docs/ru/docs/project-generation.md b/docs/ru/docs/project-generation.md index 8c5681115b..dbedf76fe3 100644 --- a/docs/ru/docs/project-generation.md +++ b/docs/ru/docs/project-generation.md @@ -13,8 +13,8 @@ - 🔍 [Pydantic](https://docs.pydantic.dev), используется FastAPI, для валидации данных и управления настройками. - 💾 [PostgreSQL](https://www.postgresql.org) в качестве SQL‑базы данных. - 🚀 [React](https://react.dev) для фронтенда. - - 💃 Используются TypeScript, хуки, [Vite](https://vitejs.dev) и другие части современного фронтенд‑стека. - - 🎨 [Chakra UI](https://chakra-ui.com) для компонентов фронтенда. + - 💃 Используются TypeScript, хуки, Vite и другие части современного фронтенд‑стека. + - 🎨 [Tailwind CSS](https://tailwindcss.com) и [shadcn/ui](https://ui.shadcn.com) для компонентов фронтенда. - 🤖 Автоматически сгенерированный фронтенд‑клиент. - 🧪 [Playwright](https://playwright.dev) для End‑to‑End тестирования. - 🦇 Поддержка тёмной темы. diff --git a/docs/ru/docs/resources/index.md b/docs/ru/docs/resources/index.md index 54be4e5fd3..faf80f7f49 100644 --- a/docs/ru/docs/resources/index.md +++ b/docs/ru/docs/resources/index.md @@ -1,3 +1,3 @@ # Ресурсы { #resources } -Дополнительные ресурсы, внешние ссылки, статьи и многое другое. ✈️ +Дополнительные ресурсы, внешние ссылки и многое другое. ✈️ diff --git a/docs/ru/docs/tutorial/bigger-applications.md b/docs/ru/docs/tutorial/bigger-applications.md index b832383cc9..5e5d6ada94 100644 --- a/docs/ru/docs/tutorial/bigger-applications.md +++ b/docs/ru/docs/tutorial/bigger-applications.md @@ -85,17 +85,13 @@ from app.routers import items Точно также, как и в случае с классом `FastAPI`, вам нужно импортировать и создать объект класса `APIRouter`. -```Python hl_lines="1 3" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### Создание *эндпоинтов* с помощью `APIRouter` { #path-operations-with-apirouter } В дальнейшем используйте `APIRouter` для объявления *эндпоинтов*, точно также, как вы используете класс `FastAPI`: -```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Вы можете думать об `APIRouter` как об "уменьшенной версии" класса FastAPI`. @@ -119,35 +115,7 @@ from app.routers import items Теперь мы воспользуемся простой зависимостью, чтобы прочитать кастомизированный `X-Token` из заголовка: -//// tab | Python 3.9+ - -```Python hl_lines="3 6-8" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Подсказка - -Мы рекомендуем использовать версию `Annotated`, когда это возможно. - -/// - -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app/dependencies.py!} -``` - -//// +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Подсказка @@ -180,9 +148,7 @@ from app.routers import items Таким образом, вместо того чтобы добавлять все эти свойства в функцию каждого отдельного *эндпоинта*, мы добавим их в `APIRouter`. -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Так как каждый *эндпоинт* начинается с символа `/`: @@ -241,9 +207,7 @@ async def read_item(item_id: str): Мы используем операцию относительного импорта `..` для импорта зависимости: -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} #### Как работает относительный импорт? { #how-relative-imports-work } @@ -313,9 +277,7 @@ from ...dependencies import get_token_header Но помимо этого мы можем добавить новые теги для каждого отдельного *эндпоинта*, а также некоторые дополнительные ответы (`responses`), характерные для данного *эндпоинта*: -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Подсказка @@ -341,17 +303,13 @@ from ...dependencies import get_token_header Мы даже можем объявить [глобальные зависимости](dependencies/global-dependencies.md){.internal-link target=_blank}, которые будут объединены с зависимостями для каждого отдельного маршрутизатора: -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} ### Импорт `APIRouter` { #import-the-apirouter } Теперь мы импортируем другие суб-модули, содержащие `APIRouter`: -```Python hl_lines="4-5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} Так как файлы `app/routers/users.py` и `app/routers/items.py` являются суб-модулями одного и того же Python-пакета `app`, то мы сможем их импортировать, воспользовавшись операцией относительного импорта `.`. @@ -414,17 +372,13 @@ from .routers.users import router Поэтому, для того чтобы использовать обе эти переменные в одном файле, мы импортировали соответствующие суб-модули: -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} ### Подключение маршрутизаторов (`APIRouter`) для `users` и для `items` { #include-the-apirouters-for-users-and-items } Давайте подключим маршрутизаторы (`router`) из суб-модулей `users` и `items`: -```Python hl_lines="10-11" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} /// info | Примечание @@ -465,17 +419,13 @@ from .routers.users import router В данном примере это сделать очень просто. Но давайте предположим, что поскольку файл используется для нескольких проектов, то мы не можем модифицировать его, добавляя префиксы (`prefix`), зависимости (`dependencies`), теги (`tags`), и т.д. непосредственно в `APIRouter`: -```Python hl_lines="3" title="app/internal/admin.py" -{!../../docs_src/bigger_applications/app/internal/admin.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} Но, несмотря на это, мы хотим использовать кастомный префикс (`prefix`) для подключенного маршрутизатора (`APIRouter`), в результате чего, каждая *операция пути* будет начинаться с `/admin`. Также мы хотим защитить наш маршрутизатор с помощью зависимостей, созданных для нашего проекта. И ещё мы хотим включить теги (`tags`) и ответы (`responses`). Мы можем применить все вышеперечисленные настройки, не изменяя начальный `APIRouter`. Нам всего лишь нужно передать нужные параметры в `app.include_router()`. -```Python hl_lines="14-17" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} Таким образом, оригинальный `APIRouter` не будет модифицирован, и мы сможем использовать файл `app/internal/admin.py` сразу в нескольких проектах организации. @@ -496,9 +446,7 @@ from .routers.users import router Здесь мы это делаем ... просто, чтобы показать, что это возможно 🤷: -```Python hl_lines="21-23" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} и это будет работать корректно вместе с другими *эндпоинтами*, добавленными с помощью `app.include_router()`. diff --git a/docs/ru/docs/tutorial/cookie-param-models.md b/docs/ru/docs/tutorial/cookie-param-models.md index daac764e3f..182813afdf 100644 --- a/docs/ru/docs/tutorial/cookie-param-models.md +++ b/docs/ru/docs/tutorial/cookie-param-models.md @@ -50,7 +50,7 @@ Вы можете сконфигурировать Pydantic-модель так, чтобы запретить (`forbid`) любые дополнительные (`extra`) поля: -{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} Если клиент попробует отправить **дополнительные cookies**, то в ответ он получит **ошибку**. diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md index c82118cbe5..6f59d72054 100644 --- a/docs/ru/docs/tutorial/first-steps.md +++ b/docs/ru/docs/tutorial/first-steps.md @@ -143,6 +143,42 @@ OpenAPI определяет схему API для вашего API. И эта Вы также можете использовать её для автоматической генерации кода для клиентов, которые взаимодействуют с вашим API. Например, для фронтенд-, мобильных или IoT-приложений. +### Разверните приложение (необязательно) { #deploy-your-app-optional } + +При желании вы можете развернуть своё приложение FastAPI в FastAPI Cloud, перейдите и присоединитесь к списку ожидания, если ещё не сделали этого. 🚀 + +Если у вас уже есть аккаунт **FastAPI Cloud** (мы пригласили вас из списка ожидания 😉), вы можете развернуть приложение одной командой. + +Перед развертыванием убедитесь, что вы вошли в систему: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Затем разверните приложение: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +Готово! Теперь вы можете открыть своё приложение по этому URL. ✨ + ## Рассмотрим поэтапно { #recap-step-by-step } ### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi } @@ -314,6 +350,26 @@ https://example.com/items/foo Многие другие объекты и модели будут автоматически преобразованы в JSON (включая ORM и т. п.). Попробуйте использовать те, что вам привычнее, с высокой вероятностью они уже поддерживаются. +### Шаг 6: разверните приложение { #step-6-deploy-it } + +Разверните приложение в **FastAPI Cloud** одной командой: `fastapi deploy`. 🎉 + +#### О FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** создан тем же автором и командой, что и **FastAPI**. + +Он упрощает процесс **создания образа**, **развертывания** и **доступа** к API с минимальными усилиями. + +Он переносит тот же **опыт разработчика** при создании приложений с FastAPI на их **развертывание** в облаке. 🎉 + +FastAPI Cloud — основной спонсор и источник финансирования для open-source проектов «FastAPI и друзья». ✨ + +#### Развертывание у других облачных провайдеров { #deploy-to-other-cloud-providers } + +FastAPI — open-source и основан на стандартах. Вы можете развернуть приложения FastAPI у любого облачного провайдера по вашему выбору. + +Следуйте руководствам вашего облачного провайдера, чтобы развернуть приложения FastAPI у них. 🤓 + ## Резюме { #recap } * Импортируйте `FastAPI`. @@ -321,3 +377,4 @@ https://example.com/items/foo * Напишите **декоратор операции пути**, например `@app.get("/")`. * Определите **функцию операции пути**; например, `def root(): ...`. * Запустите сервер разработки командой `fastapi dev`. +* При желании разверните приложение командой `fastapi deploy`. diff --git a/docs/ru/docs/tutorial/handling-errors.md b/docs/ru/docs/tutorial/handling-errors.md index 2378c8b04c..63ca8665ef 100644 --- a/docs/ru/docs/tutorial/handling-errors.md +++ b/docs/ru/docs/tutorial/handling-errors.md @@ -81,7 +81,7 @@ ## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers } -Вы можете добавить пользовательские обработчики исключений с помощью то же самое исключение - утилиты от Starlette. +Вы можете добавить пользовательские обработчики исключений с помощью тех же утилит обработки исключений из Starlette. Допустим, у вас есть пользовательское исключение `UnicornException`, которое вы (или используемая вами библиотека) можете `вызвать`. @@ -117,7 +117,7 @@ Вы можете переопределить эти обработчики исключений на свои собственные. -### Переопределение исключений проверки запроса { #override-request-validation-exceptions } +### Переопределение обработчика исключений проверки запроса { #override-request-validation-exceptions } Когда запрос содержит недопустимые данные, **FastAPI** внутренне вызывает ошибку `RequestValidationError`. @@ -127,7 +127,7 @@ Обработчик исключения получит объект `Request` и исключение. -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *} Теперь, если перейти к `/items/foo`, то вместо стандартной JSON-ошибки с: @@ -149,36 +149,17 @@ вы получите текстовую версию: ``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer ``` -#### `RequestValidationError` или `ValidationError` { #requestvalidationerror-vs-validationerror } - -/// warning | Внимание - -Это технические детали, которые можно пропустить, если они не важны для вас сейчас. - -/// - -`RequestValidationError` является подклассом Pydantic `ValidationError`. - -**FastAPI** использует его для того, чтобы, если вы используете Pydantic-модель в `response_model`, и ваши данные содержат ошибку, вы увидели ошибку в журнале. - -Но клиент/пользователь этого не увидит. Вместо этого клиент получит сообщение "Internal Server Error" с кодом состояния HTTP `500`. - -Так и должно быть, потому что если в вашем *ответе* или где-либо в вашем коде (не в *запросе* клиента) возникает Pydantic `ValidationError`, то это действительно ошибка в вашем коде. - -И пока вы не устраните ошибку, ваши клиенты/пользователи не должны иметь доступа к внутренней информации о ней, так как это может привести к уязвимости в системе безопасности. - ### Переопределите обработчик ошибок `HTTPException` { #override-the-httpexception-error-handler } Аналогичным образом можно переопределить обработчик `HTTPException`. Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON: -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *} /// note | Технические детали @@ -188,6 +169,14 @@ path -> item_id /// +/// warning | Внимание + +Имейте в виду, что `RequestValidationError` содержит информацию об имени файла и строке, где произошла ошибка валидации, чтобы вы могли при желании отобразить её в логах с релевантными данными. + +Но это означает, что если вы просто преобразуете её в строку и вернёте эту информацию напрямую, вы можете допустить небольшую утечку информации о своей системе, поэтому здесь код извлекает и показывает каждую ошибку отдельно. + +/// + ### Используйте тело `RequestValidationError` { #use-the-requestvalidationerror-body } Ошибка `RequestValidationError` содержит полученное `тело` с недопустимыми данными. diff --git a/docs/ru/docs/tutorial/security/index.md b/docs/ru/docs/tutorial/security/index.md index 8fb4bf24f2..ebac013b6c 100644 --- a/docs/ru/docs/tutorial/security/index.md +++ b/docs/ru/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# Настройка авторизации +# Настройка авторизации { #security } Существует множество способов обеспечения безопасности, аутентификации и авторизации. @@ -10,11 +10,11 @@ Но сначала давайте рассмотрим некоторые небольшие концепции. -## Куда-то торопишься? +## Куда-то торопишься? { #in-a-hurry } Если вам не нужна информация о каких-либо из следующих терминов и вам просто нужно добавить защиту с аутентификацией на основе логина и пароля *прямо сейчас*, переходите к следующим главам. -## OAuth2 +## OAuth2 { #oauth2 } OAuth2 - это протокол, который определяет несколько способов обработки аутентификации и авторизации. @@ -24,7 +24,7 @@ OAuth2 включает в себя способы аутентификации Это то, что используют под собой все кнопки "вход с помощью Facebook, Google, X (Twitter), GitHub" на страницах авторизации. -### OAuth 1 +### OAuth 1 { #oauth-1 } Ранее использовался протокол OAuth 1, который сильно отличается от OAuth2 и является более сложным, поскольку он включал прямые описания того, как шифровать сообщение. @@ -34,11 +34,11 @@ OAuth2 не указывает, как шифровать сообщение, о /// tip | Подсказка -В разделе **Развертывание** вы увидите [как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt.](https://fastapi.tiangolo.com/ru/deployment/https/) +В разделе **Развертывание** вы увидите как настроить протокол HTTPS бесплатно, используя Traefik и Let's Encrypt. /// -## OpenID Connect +## OpenID Connect { #openid-connect } OpenID Connect - это еще один протокол, основанный на **OAuth2**. @@ -48,7 +48,7 @@ OpenID Connect - это еще один протокол, основанный Но вход в Facebook не поддерживает OpenID Connect. У него есть собственная вариация OAuth2. -### OpenID (не "OpenID Connect") +### OpenID (не "OpenID Connect") { #openid-not-openid-connect } Также ранее использовался стандарт "OpenID", который пытался решить ту же проблему, что и **OpenID Connect**, но не был основан на OAuth2. @@ -56,7 +56,7 @@ OpenID Connect - это еще один протокол, основанный В настоящее время не очень популярен и не используется. -## OpenAPI +## OpenAPI { #openapi } OpenAPI (ранее известный как Swagger) - это открытая спецификация для создания API (в настоящее время является частью Linux Foundation). @@ -97,7 +97,7 @@ OpenAPI может использовать следующие схемы авт /// -## Преимущества **FastAPI** +## Преимущества **FastAPI** { #fastapi-utilities } Fast API предоставляет несколько инструментов для каждой из этих схем безопасности в модуле `fastapi.security`, которые упрощают использование этих механизмов безопасности. diff --git a/docs/ru/docs/tutorial/sql-databases.md b/docs/ru/docs/tutorial/sql-databases.md index c44f37b9ae..1d03465337 100644 --- a/docs/ru/docs/tutorial/sql-databases.md +++ b/docs/ru/docs/tutorial/sql-databases.md @@ -63,9 +63,9 @@ $ pip install sqlmodel * `table=True` сообщает SQLModel, что это *модель-таблица*, она должна представлять **таблицу** в SQL базе данных, это не просто *модель данных* (как обычный класс Pydantic). -* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах можно узнать в документации SQLModel). +* `Field(primary_key=True)` сообщает SQLModel, что `id` — это **первичный ключ** в SQL базе данных (подробнее о первичных ключах SQL можно узнать в документации SQLModel). - Благодаря типу `int | None`, SQLModel будет знать, что этот столбец должен быть `INTEGER` в SQL базе данных и должен допускать значение `NULL`. + **Примечание:** Мы используем `int | None` для поля первичного ключа, чтобы в Python-коде можно было *создать объект без `id`* (`id=None`), предполагая, что база данных *сгенерирует его при сохранении*. SQLModel понимает, что база данных предоставит `id`, и *определяет столбец как `INTEGER` (не `NULL`)* в схеме базы данных. См. документацию SQLModel о первичных ключах для подробностей. * `Field(index=True)` сообщает SQLModel, что нужно создать **SQL индекс** для этого столбца, что позволит быстрее выполнять выборки при чтении данных, отфильтрованных по этому столбцу. @@ -107,7 +107,7 @@ $ pip install sqlmodel Здесь мы создаём таблицы в обработчике события запуска приложения. -Для продакшна вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓 +Для продакшн вы, вероятно, будете использовать скрипт миграций, который выполняется до запуска приложения. 🤓 /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/testing.md b/docs/ru/docs/tutorial/testing.md index 0224798b1a..7354ed895f 100644 --- a/docs/ru/docs/tutorial/testing.md +++ b/docs/ru/docs/tutorial/testing.md @@ -121,63 +121,13 @@ $ pip install httpx Обе *операции пути* требуют наличия в запросе заголовка `X-Token`. -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/app_testing/app_b_an/main.py!} -``` - -//// - -//// tab | Python 3.10+ без Annotated - -/// tip | Подсказка - -По возможности используйте версию с `Annotated`. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.8+ без Annotated - -/// tip | Подсказка - -По возможности используйте версию с `Annotated`. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b/main.py!} -``` - -//// +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} ### Расширенный файл тестов { #extended-testing-file } Теперь обновим файл `test_main.py`, добавив в него тестов: -{* ../../docs_src/app_testing/app_b/test_main.py *} +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} Если Вы не знаете, как передать информацию в запросе, можете воспользоваться поисковиком (погуглить) и задать вопрос: "Как передать информацию в запросе с помощью `httpx`", можно даже спросить: "Как передать информацию в запросе с помощью `requests`", поскольку дизайн HTTPX основан на дизайне Requests. diff --git a/docs/ru/docs/virtual-environments.md b/docs/ru/docs/virtual-environments.md index 5153cd4864..43136298a3 100644 --- a/docs/ru/docs/virtual-environments.md +++ b/docs/ru/docs/virtual-environments.md @@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip +/// tip | Подсказка + +Иногда при попытке обновить pip вы можете получить ошибку **`No module named pip`**. + +Если это произошло, установите и обновите pip с помощью команды ниже: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +Эта команда установит pip, если он ещё не установлен, а также гарантирует, что установленная версия pip будет не старее, чем версия, доступная в `ensurepip`. + +/// + ## Добавление `.gitignore` { #add-gitignore } Если вы используете **Git** (а вам стоит его использовать), добавьте файл `.gitignore`, чтобы исключить из Git всё, что находится в вашей `.venv`. @@ -834,7 +854,7 @@ I solemnly swear 🐺 * Управлять **виртуальным окружением** ваших проектов * Устанавливать **пакеты** * Управлять **зависимостями и версиями** пакетов вашего проекта -* Обеспечивать наличие **точного** набора пакетов и версий к установке, включая их зависимости, чтобы вы были уверены, что сможете запускать проект в продакшне точно так же, как и на компьютере при разработке — это называется **locking** +* Обеспечивать наличие **точного** набора пакетов и версий к установке, включая их зависимости, чтобы вы были уверены, что сможете запускать проект в продакшн точно так же, как и на компьютере при разработке — это называется **locking** * И многое другое ## Заключение { #conclusion } From 1fcec88ad2df4541b6a81c4786c4d039dc745e1e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 11 Dec 2025 21:25:27 +0000 Subject: [PATCH 053/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5bb7698c8f..19e0cb5a14 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -19,6 +19,7 @@ hide: ### Translations +* 🌐 Sync Russian docs. PR [#14509](https://github.com/fastapi/fastapi/pull/14509) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync German docs. PR [#14488](https://github.com/fastapi/fastapi/pull/14488) by [@nilslindemann](https://github.com/nilslindemann). ### Internal From c0556ac3a50420d0ba298b5641517c61f3c1c2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 06:31:21 -0800 Subject: [PATCH 054/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20support=20for=20ta?= =?UTF-8?q?gged=20union=20with=20discriminator=20inside=20of=20`Annotated`?= =?UTF-8?q?=20with=20`Body()`=20(#14512)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/_compat/v2.py | 52 ++++- ...test_union_body_discriminator_annotated.py | 207 ++++++++++++++++++ 2 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 tests/test_union_body_discriminator_annotated.py diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index 46a30b3ee8..eb5c06edd9 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -18,7 +18,7 @@ from typing import ( from fastapi._compat import may_v1, shared from fastapi.openapi.constants import REF_TEMPLATE from fastapi.types import IncEx, ModelNameMap, UnionType -from pydantic import BaseModel, ConfigDict, TypeAdapter, create_model +from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation from pydantic import ValidationError as ValidationError @@ -50,6 +50,45 @@ UndefinedType = PydanticUndefinedType evaluate_forwardref = eval_type_lenient Validator = Any +# TODO: remove when dropping support for Pydantic < v2.12.3 +_Attrs = { + "default": ..., + "default_factory": None, + "alias": None, + "alias_priority": None, + "validation_alias": None, + "serialization_alias": None, + "title": None, + "field_title_generator": None, + "description": None, + "examples": None, + "exclude": None, + "exclude_if": None, + "discriminator": None, + "deprecated": None, + "json_schema_extra": None, + "frozen": None, + "validate_default": None, + "repr": True, + "init": None, + "init_var": None, + "kw_only": None, +} + + +# TODO: remove when dropping support for Pydantic < v2.12.3 +def asdict(field_info: FieldInfo) -> Dict[str, Any]: + attributes = {} + for attr in _Attrs: + value = getattr(field_info, attr, Undefined) + if value is not Undefined: + attributes[attr] = value + return { + "annotation": field_info.annotation, + "metadata": field_info.metadata, + "attributes": attributes, + } + class BaseConfig: pass @@ -95,10 +134,15 @@ class ModelField: warnings.simplefilter( "ignore", category=UnsupportedFieldAttributeWarning ) + # TODO: remove after dropping support for Python 3.8 and + # setting the min Pydantic to v2.12.3 that adds asdict() + field_dict = asdict(self.field_info) annotated_args = ( - self.field_info.annotation, - *self.field_info.metadata, - self.field_info, + field_dict["annotation"], + *field_dict["metadata"], + # this FieldInfo needs to be created again so that it doesn't include + # the old field info metadata and only the rest of the attributes + Field(**field_dict["attributes"]), ) self._type_adapter: TypeAdapter[Any] = TypeAdapter( Annotated[annotated_args], diff --git a/tests/test_union_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py new file mode 100644 index 0000000000..14145e6f60 --- /dev/null +++ b/tests/test_union_body_discriminator_annotated.py @@ -0,0 +1,207 @@ +# Ref: https://github.com/fastapi/fastapi/discussions/14495 + +from typing import Union + +import pytest +from fastapi import FastAPI +from fastapi.testclient import TestClient +from inline_snapshot import snapshot +from pydantic import BaseModel +from typing_extensions import Annotated + +from .utils import needs_pydanticv2 + + +@pytest.fixture(name="client") +def client_fixture() -> TestClient: + from fastapi import Body + from pydantic import Discriminator, Tag + + class Cat(BaseModel): + pet_type: str = "cat" + meows: int + + class Dog(BaseModel): + pet_type: str = "dog" + barks: float + + def get_pet_type(v): + assert isinstance(v, dict) + return v.get("pet_type", "") + + Pet = Annotated[ + Union[Annotated[Cat, Tag("cat")], Annotated[Dog, Tag("dog")]], + Discriminator(get_pet_type), + ] + + app = FastAPI() + + @app.post("/pet/assignment") + async def create_pet_assignment(pet: Pet = Body()): + return pet + + @app.post("/pet/annotated") + async def create_pet_annotated(pet: Annotated[Pet, Body()]): + return pet + + client = TestClient(app) + return client + + +@needs_pydanticv2 +def test_union_body_discriminator_assignment(client: TestClient) -> None: + response = client.post("/pet/assignment", json={"pet_type": "cat", "meows": 5}) + assert response.status_code == 200, response.text + assert response.json() == {"pet_type": "cat", "meows": 5} + + +@needs_pydanticv2 +def test_union_body_discriminator_annotated(client: TestClient) -> None: + response = client.post("/pet/annotated", json={"pet_type": "dog", "barks": 3.5}) + assert response.status_code == 200, response.text + assert response.json() == {"pet_type": "dog", "barks": 3.5} + + +@needs_pydanticv2 +def test_openapi_schema(client: TestClient) -> None: + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/pet/assignment": { + "post": { + "summary": "Create Pet Assignment", + "operationId": "create_pet_assignment_pet_assignment_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "anyOf": [ + {"$ref": "#/components/schemas/Cat"}, + {"$ref": "#/components/schemas/Dog"}, + ], + "title": "Pet", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/pet/annotated": { + "post": { + "summary": "Create Pet Annotated", + "operationId": "create_pet_annotated_pet_annotated_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + {"$ref": "#/components/schemas/Cat"}, + {"$ref": "#/components/schemas/Dog"}, + ], + "title": "Pet", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "Cat": { + "properties": { + "pet_type": { + "type": "string", + "title": "Pet Type", + "default": "cat", + }, + "meows": {"type": "integer", "title": "Meows"}, + }, + "type": "object", + "required": ["meows"], + "title": "Cat", + }, + "Dog": { + "properties": { + "pet_type": { + "type": "string", + "title": "Pet Type", + "default": "dog", + }, + "barks": {"type": "number", "title": "Barks"}, + }, + "type": "object", + "required": ["barks"], + "title": "Dog", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) From 80d1f732e5e10efe75e03427558271d46cd663e3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Dec 2025 14:31:45 +0000 Subject: [PATCH 055/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 19e0cb5a14..760a8c2d87 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix support for tagged union with discriminator inside of `Annotated` with `Body()`. PR [#14512](https://github.com/fastapi/fastapi/pull/14512) by [@tiangolo](https://github.com/tiangolo). + ### Refactors * ✅ Add set of tests for request parameters and alias. PR [#14358](https://github.com/fastapi/fastapi/pull/14358) by [@YuriiMotov](https://github.com/YuriiMotov). From 3fe6522aaed4d36d6a38175a4a8137ee0fc62451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 15:32:58 +0100 Subject: [PATCH 056/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?4.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 760a8c2d87..05c43b5f6e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.124.3 + ### Fixes * 🐛 Fix support for tagged union with discriminator inside of `Annotated` with `Body()`. PR [#14512](https://github.com/fastapi/fastapi/pull/14512) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 8de426ad42..eeb2e10031 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.124.2" +__version__ = "0.124.3" from starlette import status as status From d86c47477e4d91b5e1f07973b3437908558a8b4b Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Fri, 12 Dec 2025 15:56:57 +0100 Subject: [PATCH 057/140] =?UTF-8?q?=F0=9F=90=9B=20Fix=20parameter=20aliase?= =?UTF-8?q?s=20(#14371)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- fastapi/_compat/v2.py | 20 +++- fastapi/dependencies/utils.py | 29 ++--- fastapi/openapi/utils.py | 5 +- fastapi/params.py | 8 ++ .../test_body/test_list.py | 74 +++--------- .../test_body/test_optional_list.py | 43 ++----- .../test_body/test_optional_str.py | 43 ++----- .../test_body/test_required_str.py | 72 +++--------- .../test_cookie/test_optional_str.py | 39 ++----- .../test_cookie/test_required_str.py | 79 ++++--------- .../test_file/test_list.py | 78 ++++--------- .../test_file/test_optional.py | 57 ++-------- .../test_file/test_optional_list.py | 73 ++---------- .../test_file/test_required.py | 107 ++++-------------- .../test_form/test_list.py | 75 +++--------- .../test_form/test_optional_list.py | 43 ++----- .../test_form/test_optional_str.py | 43 ++----- .../test_form/test_required_str.py | 68 +++-------- .../test_header/test_list.py | 71 +++--------- .../test_header/test_optional_list.py | 43 ++----- .../test_header/test_optional_str.py | 41 ++----- .../test_header/test_required_str.py | 77 +++---------- .../test_path/test_required_str.py | 24 +--- .../test_query/test_list.py | 71 +++--------- .../test_query/test_optional_list.py | 43 ++----- .../test_query/test_optional_str.py | 39 ++----- .../test_query/test_required_str.py | 79 ++++--------- 27 files changed, 355 insertions(+), 1089 deletions(-) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index eb5c06edd9..a17d625568 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -110,6 +110,18 @@ class ModelField: a = self.field_info.alias return a if a is not None else self.name + @property + def validation_alias(self) -> Union[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]: + sa = self.field_info.serialization_alias + return sa or None + @property def required(self) -> bool: return self.field_info.is_required() @@ -243,12 +255,18 @@ def get_schema_from_model_field( if (separate_input_output_schemas or _has_computed_fields(field)) else "validation" ) + field_alias = ( + (field.validation_alias or field.alias) + if field.mode == "validation" + else (field.serialization_alias or field.alias) + ) + # This expects that GenerateJsonSchema was already used to generate the definitions json_schema = field_mapping[(field, override_mode or field.mode)] if "$ref" not in json_schema: # TODO remove when deprecating Pydantic v1 # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 - json_schema["title"] = field.field_info.title or field.alias.title().replace( + json_schema["title"] = field.field_info.title or field_alias.title().replace( "_", " " ) return json_schema diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 262dba6fdd..cc7e55b4b0 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -752,7 +752,7 @@ def _validate_value_with_model_field( def _get_multidict_value( field: ModelField, values: Mapping[str, Any], alias: Union[str, None] = None ) -> Any: - alias = alias or field.alias + alias = alias or get_validation_alias(field) if is_sequence_field(field) and isinstance(values, (ImmutableMultiDict, Headers)): value = values.getlist(alias) else: @@ -809,15 +809,13 @@ def request_params_to_args( field.field_info, "convert_underscores", default_convert_underscores ) if convert_underscores: - alias = ( - field.alias - if field.alias != field.name - else field.name.replace("_", "-") - ) + alias = get_validation_alias(field) + if alias == field.name: + alias = alias.replace("_", "-") value = _get_multidict_value(field, received_params, alias=alias) if value is not None: - params_to_process[field.alias] = value - processed_keys.add(alias or field.alias) + params_to_process[get_validation_alias(field)] = value + processed_keys.add(alias or get_validation_alias(field)) for key in received_params.keys(): if key not in processed_keys: @@ -847,7 +845,7 @@ def request_params_to_args( assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), ( "Params must be subclasses of Param" ) - loc = (field_info.in_.value, field.alias) + loc = (field_info.in_.value, get_validation_alias(field)) v_, errors_ = _validate_value_with_model_field( field=field, value=value, values=values, loc=loc ) @@ -936,8 +934,8 @@ async def _extract_form_body( tg.start_soon(process_fn, sub_value.read) value = serialize_sequence_value(field=field, value=results) if value is not None: - values[field.alias] = value - field_aliases = {field.alias for field in body_fields} + values[get_validation_alias(field)] = value + field_aliases = {get_validation_alias(field) for field in body_fields} for key in received_body.keys(): if key not in field_aliases: param_values = received_body.getlist(key) @@ -979,11 +977,11 @@ async def request_body_to_args( ) return {first_field.name: v_}, errors_ for field in body_fields: - loc = ("body", field.alias) + loc = ("body", get_validation_alias(field)) value: Optional[Any] = None if body_to_process is not None: try: - value = body_to_process.get(field.alias) + value = body_to_process.get(get_validation_alias(field)) # If the received body is a list, not a dict except AttributeError: errors.append(get_missing_field_error(loc)) @@ -1062,3 +1060,8 @@ def get_body_field( field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), ) return final_field + + +def get_validation_alias(field: ModelField) -> str: + va = getattr(field, "validation_alias", None) + return va or field.alias diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 06c14861a3..9fe2044f26 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -19,6 +19,7 @@ from fastapi.dependencies.utils import ( _get_flat_fields_from_params, get_flat_dependant, get_flat_params, + get_validation_alias, ) from fastapi.encoders import jsonable_encoder from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX @@ -141,7 +142,7 @@ def _get_openapi_operation_parameters( field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, ) - name = param.alias + name = get_validation_alias(param) convert_underscores = getattr( param.field_info, "convert_underscores", @@ -149,7 +150,7 @@ def _get_openapi_operation_parameters( ) if ( param_type == ParamTypes.header - and param.alias == param.name + and name == param.name and convert_underscores ): name = param.name.replace("_", "-") diff --git a/fastapi/params.py b/fastapi/params.py index 6d07df35e1..b6d0f08e31 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -115,6 +115,10 @@ class Param(FieldInfo): # type: ignore[misc] else: kwargs["deprecated"] = deprecated if PYDANTIC_V2: + if serialization_alias in (_Unset, None) and isinstance(alias, str): + serialization_alias = alias + if validation_alias in (_Unset, None): + validation_alias = alias kwargs.update( { "annotation": annotation, @@ -571,6 +575,10 @@ class Body(FieldInfo): # type: ignore[misc] else: kwargs["deprecated"] = deprecated if PYDANTIC_V2: + if serialization_alias in (_Unset, None) and isinstance(alias, str): + serialization_alias = alias + if validation_alias in (_Unset, None): + validation_alias = alias kwargs.update( { "annotation": annotation, diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index 884e1d08ab..18a2a2f308 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -3,7 +3,6 @@ from typing import List, Union import pytest from dirty_equals import IsDict, IsOneOf, IsPartialDict from fastapi import Body, FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -115,21 +114,13 @@ class BodyModelRequiredListAlias(BaseModel): @app.post("/model-required-list-alias", operation_id="model_required_list_alias") async def read_model_required_list_alias(p: BodyModelRequiredListAlias): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/required-list-alias", "/model-required-list-alias", ], ) @@ -253,7 +244,7 @@ class BodyModelRequiredListValidationAlias(BaseModel): async def read_model_required_list_validation_alias( p: BodyModelRequiredListValidationAlias, ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 @@ -284,10 +275,7 @@ def test_required_list_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) @@ -299,9 +287,7 @@ def test_required_list_validation_alias_missing(path: str, json: Union[dict, Non "detail": [ { "type": "missing", - "loc": IsOneOf( # /required-validation-alias fails here - ["body"], ["body", "p_val_alias"] - ), + "loc": IsOneOf(["body"], ["body", "p_val_alias"]), "msg": "Field required", "input": IsOneOf(None, {}), } @@ -313,19 +299,14 @@ def test_required_list_validation_alias_missing(path: str, json: Union[dict, Non @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) def test_required_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, json={"p": ["hello", "world"]}) - assert response.status_code == 422, ( - response.text # /required-list-validation-alias fails here - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -343,19 +324,14 @@ def test_required_list_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) def test_required_list_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": ["hello", "world"]}) - assert response.status_code == 200, ( - response.text # /required-list-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": ["hello", "world"]} @@ -386,7 +362,7 @@ class BodyModelRequiredListAliasAndValidationAlias(BaseModel): def read_model_required_list_alias_and_validation_alias( p: BodyModelRequiredListAliasAndValidationAlias, ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 @@ -420,10 +396,7 @@ def test_required_list_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) @@ -435,9 +408,7 @@ def test_required_list_alias_and_validation_alias_missing(path: str, json): "detail": [ { "type": "missing", - "loc": IsOneOf( # /required-list-alias-and-validation-alias fails here - ["body"], ["body", "p_val_alias"] - ), + "loc": IsOneOf(["body"], ["body", "p_val_alias"]), "msg": "Field required", "input": IsOneOf(None, {}), } @@ -449,10 +420,7 @@ def test_required_list_alias_and_validation_alias_missing(path: str, json): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) @@ -464,7 +432,7 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): "detail": [ { "type": "missing", - "loc": [ # /required-list-alias-and-validation-alias fails here + "loc": [ "body", "p_val_alias", ], @@ -479,10 +447,7 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) @@ -507,17 +472,12 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": ["hello", "world"]}) - assert response.status_code == 200, ( - response.text # /required-list-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": ["hello", "world"]} diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py index c86398ce9f..e2ecdc7f43 100644 --- a/tests/test_request_params/test_body/test_optional_list.py +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -3,7 +3,6 @@ from typing import List, Optional import pytest from dirty_equals import IsDict from fastapi import Body, FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -149,15 +148,7 @@ async def read_model_optional_list_alias(p: BodyModelOptionalListAlias): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - strict=False, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - ), - ), + "/optional-list-alias", "/model-optional-list-alias", ], ) @@ -383,10 +374,7 @@ def test_optional_list_validation_alias_missing_empty_dict(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-validation-alias", "/model-optional-list-validation-alias", ], ) @@ -394,17 +382,14 @@ def test_optional_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, json={"p": ["hello", "world"]}) assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-list-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-validation-alias", "/model-optional-list-validation-alias", ], ) @@ -412,9 +397,7 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": ["hello", "world"]}) assert response.status_code == 200, response.text - assert response.json() == { # /optional-list-validation-alias fails here - "p": ["hello", "world"] - } + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -561,10 +544,7 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-alias-and-validation-alias", "/model-optional-list-alias-and-validation-alias", ], ) @@ -572,19 +552,14 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_alias": ["hello", "world"]}) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-list-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-alias-and-validation-alias", "/model-optional-list-alias-and-validation-alias", ], ) @@ -593,7 +568,7 @@ def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str) response = client.post(path, json={"p_val_alias": ["hello", "world"]}) assert response.status_code == 200, response.text assert response.json() == { - "p": [ # /optional-list-alias-and-validation-alias fails here + "p": [ "hello", "world", ] diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py index 43ed367dde..a49f5a3675 100644 --- a/tests/test_request_params/test_body/test_optional_str.py +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -3,7 +3,6 @@ from typing import Optional import pytest from dirty_equals import IsDict from fastapi import Body, FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -144,15 +143,7 @@ async def read_model_optional_alias(p: BodyModelOptionalAlias): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - strict=False, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - ), - ), + "/optional-alias", "/model-optional-alias", ], ) @@ -364,10 +355,7 @@ def test_model_optional_validation_alias_missing_empty_dict(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -375,17 +363,14 @@ def test_optional_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, json={"p": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -393,7 +378,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -533,10 +518,7 @@ def test_optional_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -544,19 +526,14 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_alias": "hello"}) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -564,6 +541,4 @@ def test_optional_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": "hello"}) assert response.status_code == 200 - assert response.json() == { - "p": "hello" # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py index fba3fe1f60..18c660cad2 100644 --- a/tests/test_request_params/test_body/test_required_str.py +++ b/tests/test_request_params/test_body/test_required_str.py @@ -3,7 +3,6 @@ from typing import Any, Dict, Union import pytest from dirty_equals import IsDict, IsOneOf from fastapi import Body, FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -118,15 +117,7 @@ async def read_model_required_alias(p: BodyModelRequiredAlias): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - strict=False, - ), - ), + "/required-alias", "/model-required-alias", ], ) @@ -270,10 +261,7 @@ def test_required_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -287,9 +275,7 @@ def test_required_validation_alias_missing( "detail": [ { "type": "missing", - "loc": IsOneOf( # /required-validation-alias fails here - ["body", "p_val_alias"], ["body"] - ), + "loc": IsOneOf(["body", "p_val_alias"], ["body"]), "msg": "Field required", "input": IsOneOf(None, {}), } @@ -301,19 +287,14 @@ def test_required_validation_alias_missing( @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, json={"p": "hello"}) - assert response.status_code == 422, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -331,19 +312,14 @@ def test_required_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": "hello"}) - assert response.status_code == 200, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -405,10 +381,7 @@ def test_required_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -422,9 +395,7 @@ def test_required_alias_and_validation_alias_missing( "detail": [ { "type": "missing", - "loc": IsOneOf( # /required-alias-and-validation-alias fails here - ["body"], ["body", "p_val_alias"] - ), + "loc": IsOneOf(["body"], ["body", "p_val_alias"]), "msg": "Field required", "input": IsOneOf(None, {}), } @@ -436,10 +407,7 @@ def test_required_alias_and_validation_alias_missing( @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -454,7 +422,7 @@ def test_required_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {"p": "hello"}), @@ -467,19 +435,14 @@ def test_required_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_alias": "hello"}) - assert response.status_code == 422, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -497,18 +460,13 @@ def test_required_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, json={"p_val_alias": "hello"}) - assert response.status_code == 200, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py index 7298baacd1..1eec45689d 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -157,10 +157,7 @@ def test_optional_alias_by_name(path: str): "path", [ "/optional-alias", - pytest.param( - "/model-optional-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-optional-alias", ], ) def test_optional_alias_by_alias(path: str): @@ -168,7 +165,7 @@ def test_optional_alias_by_alias(path: str): client.cookies.set("p_alias", "hello") response = client.get(path) assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /model-optional-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -194,7 +191,6 @@ def read_model_optional_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -229,10 +225,7 @@ def test_optional_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -248,10 +241,7 @@ def test_optional_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -260,7 +250,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): client.cookies.set("p_val_alias", "hello") response = client.get(path) assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -288,7 +278,6 @@ def read_model_optional_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -345,10 +334,7 @@ def test_optional_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -357,19 +343,14 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): client.cookies.set("p_alias", "hello") response = client.get(path) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -378,6 +359,4 @@ def test_optional_alias_and_validation_alias_by_validation_alias(path: str): client.cookies.set("p_val_alias", "hello") response = client.get(path) assert response.status_code == 200 - assert response.json() == { - "p": "hello" # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py index 9c1442ccb7..6d0fa2ef29 100644 --- a/tests/test_request_params/test_cookie/test_required_str.py +++ b/tests/test_request_params/test_cookie/test_required_str.py @@ -1,7 +1,6 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi import Cookie, FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -103,7 +102,7 @@ class CookieModelRequiredAlias(BaseModel): @app.get("/model-required-alias") async def read_model_required_alias(p: Annotated[CookieModelRequiredAlias, Cookie()]): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( @@ -158,15 +157,7 @@ def test_required_alias_missing(path: str): "path", [ "/required-alias", - pytest.param( - "/model-required-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/model-required-alias", ], ) def test_required_alias_by_name(path: str): @@ -183,7 +174,7 @@ def test_required_alias_by_name(path: str): "msg": "Field required", "input": IsOneOf( None, - {"p": "hello"}, # /model-required-alias PDv2 fails here + {"p": "hello"}, ), } ] @@ -206,19 +197,14 @@ def test_required_alias_by_name(path: str): "path", [ "/required-alias", - pytest.param( - "/model-required-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-required-alias", ], ) def test_required_alias_by_alias(path: str): client = TestClient(app) client.cookies.set("p_alias", "hello") response = client.get(path) - assert response.status_code == 200, ( # /model-required-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -245,7 +231,6 @@ def read_model_required_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -265,10 +250,7 @@ def test_required_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -282,7 +264,7 @@ def test_required_validation_alias_missing(path: str): "type": "missing", "loc": [ "cookie", - "p_val_alias", # /required-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -295,10 +277,7 @@ def test_required_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -306,9 +285,7 @@ def test_required_validation_alias_by_name(path: str): client = TestClient(app) client.cookies.set("p", "hello") response = client.get(path) - assert response.status_code == 422, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -326,10 +303,7 @@ def test_required_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -337,9 +311,7 @@ def test_required_validation_alias_by_validation_alias(path: str): client = TestClient(app) client.cookies.set("p_val_alias", "hello") response = client.get(path) - assert response.status_code == 200, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -367,7 +339,6 @@ def read_model_required_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -390,10 +361,7 @@ def test_required_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -407,7 +375,7 @@ def test_required_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "cookie", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -417,7 +385,6 @@ def test_required_alias_and_validation_alias_missing(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -437,10 +404,10 @@ def test_required_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "cookie", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", - "input": IsOneOf( # /model-alias-and-validation-alias fails here + "input": IsOneOf( None, {"p": "hello"}, ), @@ -450,7 +417,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -462,9 +428,7 @@ def test_required_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) client.cookies.set("p_alias", "hello") response = client.get(path) - assert ( - response.status_code == 422 # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 422 assert response.json() == { "detail": [ @@ -472,7 +436,7 @@ def test_required_alias_and_validation_alias_by_alias(path: str): "type": "missing", "loc": ["cookie", "p_val_alias"], "msg": "Field required", - "input": IsOneOf( # /model-alias-and-validation-alias fails here + "input": IsOneOf( None, {"p_alias": "hello"}, ), @@ -485,10 +449,7 @@ def test_required_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -496,8 +457,6 @@ def test_required_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) client.cookies.set("p_val_alias", "hello") response = client.get(path) - assert response.status_code == 200, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index 8722ce5ab2..94a33967f3 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -3,7 +3,6 @@ from typing import List import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from typing_extensions import Annotated @@ -134,12 +133,6 @@ async def read_list_uploadfile_alias( return {"file_size": [file.size for file in p]} -@pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - strict=False, -) @pytest.mark.parametrize( "path", [ @@ -334,14 +327,8 @@ def test_list_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/list-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/list-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/list-bytes-validation-alias", + "/list-uploadfile-validation-alias", ], ) def test_list_validation_alias_missing(path: str): @@ -352,7 +339,7 @@ def test_list_validation_alias_missing(path: str): "detail": [ { "type": "missing", - "loc": [ # /list-*-validation-alias fail here + "loc": [ "body", "p_val_alias", ], @@ -367,24 +354,16 @@ def test_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/list-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/list-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/list-bytes-validation-alias", + "/list-uploadfile-validation-alias", ], ) def test_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) - assert response.status_code == 422, ( # /list-*-validation-alias fail here - response.text - ) + assert response.status_code == 422, response.text - assert response.json() == { # pragma: no cover + assert response.json() == { "detail": [ { "type": "missing", @@ -396,7 +375,6 @@ def test_list_validation_alias_by_name(path: str): } -@pytest.mark.xfail(raises=AssertionError, strict=False) @needs_pydanticv2 @pytest.mark.parametrize( "path", @@ -410,8 +388,8 @@ def test_list_validation_alias_by_validation_alias(path: str): response = client.post( path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] ) - assert response.status_code == 200, response.text # all 2 fail here - assert response.json() == {"file_size": [5, 5]} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"file_size": [5, 5]} # ===================================================================================== @@ -486,14 +464,8 @@ def test_list_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/list-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/list-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/list-bytes-alias-and-validation-alias", + "/list-uploadfile-alias-and-validation-alias", ], ) def test_list_alias_and_validation_alias_missing(path: str): @@ -506,7 +478,7 @@ def test_list_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /list-*-alias-and-validation-alias fail here + "p_val_alias", ], "msg": "Field required", "input": None, @@ -515,7 +487,6 @@ def test_list_alias_and_validation_alias_missing(path: str): } -@pytest.mark.xfail(raises=AssertionError, strict=False) @needs_pydanticv2 @pytest.mark.parametrize( "path", @@ -535,7 +506,7 @@ def test_list_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /list-*-alias-and-validation-alias fail here + "p_val_alias", ], "msg": "Field required", "input": None, @@ -548,24 +519,16 @@ def test_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/list-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/list-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/list-bytes-alias-and-validation-alias", + "/list-uploadfile-alias-and-validation-alias", ], ) def test_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")]) - assert response.status_code == 422, ( - response.text # /list-*-alias-and-validation-alias fails here - ) + assert response.status_code == 422, response.text - assert response.json() == { # pragma: no cover + assert response.json() == { "detail": [ { "type": "missing", @@ -577,7 +540,6 @@ def test_list_alias_and_validation_alias_by_alias(path: str): } -@pytest.mark.xfail(raises=AssertionError, strict=False) @needs_pydanticv2 @pytest.mark.parametrize( "path", @@ -591,7 +553,5 @@ def test_list_alias_and_validation_alias_by_validation_alias(path: str): response = client.post( path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] ) - assert response.status_code == 200, ( # all 2 fail here - response.text - ) - assert response.json() == {"file_size": [5, 5]} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"file_size": [5, 5]} diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index 14fc0a220a..2c1ca9530e 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -3,7 +3,6 @@ from typing import Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from typing_extensions import Annotated @@ -107,12 +106,6 @@ async def read_optional_uploadfile_alias( return {"file_size": p.size if p else None} -@pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - strict=False, -) @pytest.mark.parametrize( "path", [ @@ -266,44 +259,30 @@ def test_optional_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/optional-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-bytes-validation-alias", + "/optional-uploadfile-validation-alias", ], ) def test_optional_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, files=[("p", b"hello")]) assert response.status_code == 200, response.text - assert response.json() == { # /optional-*-validation-alias fail here - "file_size": None - } + assert response.json() == {"file_size": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/optional-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-bytes-validation-alias", + "/optional-uploadfile-validation-alias", ], ) def test_optional_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_val_alias", b"hello")]) assert response.status_code == 200, response.text - assert response.json() == {"file_size": 5} # /optional-*-validation-alias fail here + assert response.json() == {"file_size": 5} # ===================================================================================== @@ -403,14 +382,8 @@ def test_optional_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/optional-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-bytes-alias-and-validation-alias", + "/optional-uploadfile-alias-and-validation-alias", ], ) def test_optional_alias_and_validation_alias_by_alias(path: str): @@ -424,20 +397,12 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/optional-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-bytes-alias-and-validation-alias", + "/optional-uploadfile-alias-and-validation-alias", ], ) def test_optional_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_val_alias", b"hello")]) assert response.status_code == 200, response.text - assert response.json() == { - "file_size": 5 - } # /optional-*-alias-and-validation-alias fail here + assert response.json() == {"file_size": 5} diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py index f266642a66..20e36501f8 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -3,7 +3,6 @@ from typing import List, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from typing_extensions import Annotated @@ -87,15 +86,7 @@ def test_optional_list_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-bytes", - marks=pytest.mark.xfail( - raises=(TypeError, AssertionError), - condition=PYDANTIC_V2, - reason="Fails only with PDv2 due to #14297", - strict=False, - ), - ), + "/optional-list-bytes", "/optional-list-uploadfile", ], ) @@ -124,12 +115,6 @@ async def read_optional_list_uploadfile_alias( return {"file_size": [file.size for file in p] if p else None} -@pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - strict=False, -) @pytest.mark.parametrize( "path", [ @@ -202,15 +187,7 @@ def test_optional_list_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-bytes-alias", - marks=pytest.mark.xfail( - raises=(TypeError, AssertionError), - strict=False, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 model due to #14297", - ), - ), + "/optional-list-bytes-alias", "/optional-list-uploadfile-alias", ], ) @@ -302,30 +279,17 @@ def test_optional_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-bytes-validation-alias", - marks=pytest.mark.xfail( - raises=(TypeError, AssertionError), - strict=False, - reason="Fails due to #14297", - ), - ), - pytest.param( - "/optional-list-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-bytes-validation-alias", + "/optional-list-uploadfile-validation-alias", ], ) def test_optional_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, files=[("p", b"hello"), ("p", b"world")]) assert response.status_code == 200, response.text - assert response.json() == { # /optional-list-uploadfile-validation-alias fails here - "file_size": None - } + assert response.json() == {"file_size": None} -@pytest.mark.xfail(raises=AssertionError, strict=False) @needs_pydanticv2 @pytest.mark.parametrize( "path", @@ -340,9 +304,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] ) assert response.status_code == 200, response.text - assert response.json() == { - "file_size": [5, 5] # /optional-list-*-validation-alias fail here - } + assert response.json() == {"file_size": [5, 5]} # ===================================================================================== @@ -444,30 +406,17 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail( - raises=(TypeError, AssertionError), - strict=False, - reason="Fails due to #14297", - ), - ), - pytest.param( - "/optional-list-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-bytes-alias-and-validation-alias", + "/optional-list-uploadfile-alias-and-validation-alias", ], ) def test_optional_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_alias", b"hello"), ("p_alias", b"world")]) assert response.status_code == 200, response.text - assert ( # /optional-list-uploadfile-alias-and-validation-alias fails here - response.json() == {"file_size": None} - ) + assert response.json() == {"file_size": None} -@pytest.mark.xfail(raises=AssertionError, strict=False) @needs_pydanticv2 @pytest.mark.parametrize( "path", @@ -482,6 +431,4 @@ def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str) path, files=[("p_val_alias", b"hello"), ("p_val_alias", b"world")] ) assert response.status_code == 200, response.text - assert response.json() == { - "file_size": [5, 5] # /optional-list-*-alias-and-validation-alias fail here - } + assert response.json() == {"file_size": [5, 5]} diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py index e505973706..f041ac1ccf 100644 --- a/tests/test_request_params/test_file/test_required.py +++ b/tests/test_request_params/test_file/test_required.py @@ -1,7 +1,6 @@ import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from typing_extensions import Annotated @@ -112,12 +111,6 @@ async def read_required_uploadfile_alias( return {"file_size": p.size} -@pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - strict=False, -) @pytest.mark.parametrize( "path", [ @@ -278,14 +271,8 @@ def test_required_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-validation-alias", + "/required-uploadfile-validation-alias", ], ) def test_required_validation_alias_missing(path: str): @@ -296,7 +283,7 @@ def test_required_validation_alias_missing(path: str): "detail": [ { "type": "missing", - "loc": [ # /required-*-validation-alias fail here + "loc": [ "body", "p_val_alias", ], @@ -311,24 +298,16 @@ def test_required_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-validation-alias", + "/required-uploadfile-validation-alias", ], ) def test_required_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, files=[("p", b"hello")]) - assert response.status_code == 422, ( # /required-*-validation-alias fail here - response.text - ) + assert response.status_code == 422, response.text - assert response.json() == { # pragma: no cover + assert response.json() == { "detail": [ { "type": "missing", @@ -344,23 +323,15 @@ def test_required_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-validation-alias", + "/required-uploadfile-validation-alias", ], ) def test_required_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_val_alias", b"hello")]) - assert response.status_code == 200, ( # all 2 fail here - response.text - ) - assert response.json() == {"file_size": 5} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 5} # ===================================================================================== @@ -417,14 +388,8 @@ def test_required_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-alias-and-validation-alias", + "/required-uploadfile-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_missing(path: str): @@ -437,7 +402,7 @@ def test_required_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-*-alias-and-validation-alias fail here + "p_val_alias", ], "msg": "Field required", "input": None, @@ -450,14 +415,8 @@ def test_required_alias_and_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-alias-and-validation-alias", + "/required-uploadfile-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_name(path: str): @@ -471,7 +430,7 @@ def test_required_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-*-alias-and-validation-alias fail here + "p_val_alias", ], "msg": "Field required", "input": None, @@ -484,24 +443,16 @@ def test_required_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-alias-and-validation-alias", + "/required-uploadfile-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_alias", b"hello")]) - assert response.status_code == 422, ( - response.text # /required-*-alias-and-validation-alias fails here - ) + assert response.status_code == 422, response.text - assert response.json() == { # pragma: no cover + assert response.json() == { "detail": [ { "type": "missing", @@ -517,20 +468,12 @@ def test_required_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-bytes-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), - pytest.param( - "/required-uploadfile-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-bytes-alias-and-validation-alias", + "/required-uploadfile-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, files=[("p_val_alias", b"hello")]) - assert response.status_code == 200, ( # all 2 fail here - response.text - ) - assert response.json() == {"file_size": 5} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"file_size": 5} diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py index c57180f6aa..69a1b6a380 100644 --- a/tests/test_request_params/test_form/test_list.py +++ b/tests/test_request_params/test_form/test_list.py @@ -3,7 +3,6 @@ from typing import List import pytest from dirty_equals import IsDict, IsOneOf, IsPartialDict from fastapi import FastAPI, Form -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -114,21 +113,13 @@ class FormModelRequiredListAlias(BaseModel): async def read_model_required_list_alias( p: Annotated[FormModelRequiredListAlias, Form()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/required-list-alias", "/model-required-list-alias", ], ) @@ -187,15 +178,7 @@ def test_required_list_alias_missing(path: str): "path", [ "/required-list-alias", - pytest.param( - "/model-required-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/model-required-list-alias", ], ) def test_required_list_alias_by_name(path: str): @@ -209,9 +192,7 @@ def test_required_list_alias_by_name(path: str): "type": "missing", "loc": ["body", "p_alias"], "msg": "Field required", - "input": IsOneOf( # /model-required-list-alias with PDv2 fails here - None, {"p": ["hello", "world"]} - ), + "input": IsOneOf(None, {"p": ["hello", "world"]}), } ] } @@ -264,7 +245,7 @@ class FormModelRequiredListValidationAlias(BaseModel): async def read_model_required_list_validation_alias( p: Annotated[FormModelRequiredListValidationAlias, Form()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 @@ -294,10 +275,7 @@ def test_required_list_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) @@ -311,7 +289,7 @@ def test_required_list_validation_alias_missing(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-list-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -324,19 +302,14 @@ def test_required_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) def test_required_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, data={"p": ["hello", "world"]}) - assert response.status_code == 422, ( - response.text # /required-list-validation-alias fails here - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -351,7 +324,6 @@ def test_required_list_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -359,9 +331,9 @@ def test_required_list_validation_alias_by_name(path: str): def test_required_list_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": ["hello", "world"]}) - assert response.status_code == 200, response.text # both fail here + assert response.status_code == 200, response.text - assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -389,7 +361,7 @@ class FormModelRequiredListAliasAndValidationAlias(BaseModel): def read_model_required_list_alias_and_validation_alias( p: Annotated[FormModelRequiredListAliasAndValidationAlias, Form()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 @@ -422,10 +394,7 @@ def test_required_list_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) @@ -439,7 +408,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "body", - # /required-list-alias-and-validation-alias fails here "p_val_alias", ], "msg": "Field required", @@ -450,7 +418,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -468,13 +435,11 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "body", - # /required-list-alias-and-validation-alias fails here "p_val_alias", ], "msg": "Field required", "input": IsOneOf( None, - # /model-required-list-alias-and-validation-alias fails here {"p": ["hello", "world"]}, ), } @@ -486,19 +451,14 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) def test_required_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_alias": ["hello", "world"]}) - assert ( # /required-list-alias-and-validation-alias fails here - response.status_code == 422 - ) + assert response.status_code == 422 assert response.json() == { "detail": [ { @@ -512,7 +472,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -523,5 +482,5 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": ["hello", "world"]}) - assert response.status_code == 200, response.text # both fail here - assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py index 288a0cfe44..1f779a7ed9 100644 --- a/tests/test_request_params/test_form/test_optional_list.py +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -3,7 +3,6 @@ from typing import List, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -115,15 +114,7 @@ async def read_model_optional_list_alias( @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - strict=False, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - ), - ), + "/optional-list-alias", "/model-optional-list-alias", ], ) @@ -276,10 +267,7 @@ def test_optional_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-validation-alias", "/model-optional-list-validation-alias", ], ) @@ -287,11 +275,10 @@ def test_optional_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, data={"p": ["hello", "world"]}) assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-list-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -299,12 +286,8 @@ def test_optional_list_validation_alias_by_name(path: str): def test_optional_list_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": ["hello", "world"]}) - assert response.status_code == 200, ( - response.text # /model-optional-list-validation-alias fails here - ) - assert response.json() == { # /optional-list-validation-alias fails here - "p": ["hello", "world"] - } + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -415,10 +398,7 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-alias-and-validation-alias", "/model-optional-list-alias-and-validation-alias", ], ) @@ -426,13 +406,10 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_alias": ["hello", "world"]}) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-list-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -443,11 +420,9 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": ["hello", "world"]}) - assert response.status_code == 200, ( - response.text # /model-optional-list-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == { - "p": [ # /optional-list-alias-and-validation-alias fails here + "p": [ "hello", "world", ] diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py index 66c003a952..9698659452 100644 --- a/tests/test_request_params/test_form/test_optional_str.py +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -3,7 +3,6 @@ from typing import Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -108,15 +107,7 @@ async def read_model_optional_alias(p: Annotated[FormModelOptionalAlias, Form()] @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - strict=False, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - ), - ), + "/optional-alias", "/model-optional-alias", ], ) @@ -252,10 +243,7 @@ def test_optional_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -263,17 +251,14 @@ def test_optional_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, data={"p": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -281,7 +266,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -383,10 +368,7 @@ def test_optional_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -394,19 +376,14 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_alias": "hello"}) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -414,6 +391,4 @@ def test_optional_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": "hello"}) assert response.status_code == 200 - assert response.json() == { - "p": "hello" # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py index fcbce015d5..c901e7b445 100644 --- a/tests/test_request_params/test_form/test_required_str.py +++ b/tests/test_request_params/test_form/test_required_str.py @@ -1,7 +1,6 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI, Form -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -113,15 +112,7 @@ async def read_model_required_alias(p: Annotated[FormModelRequiredAlias, Form()] @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2", - strict=False, - ), - ), + "/required-alias", "/model-required-alias", ], ) @@ -263,10 +254,7 @@ def test_required_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -280,7 +268,7 @@ def test_required_validation_alias_missing(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -293,19 +281,14 @@ def test_required_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_name(path: str): client = TestClient(app) response = client.post(path, data={"p": "hello"}) - assert response.status_code == 422, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -323,19 +306,14 @@ def test_required_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": "hello"}) - assert response.status_code == 200, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -394,10 +372,7 @@ def test_required_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -411,7 +386,7 @@ def test_required_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -424,10 +399,7 @@ def test_required_alias_and_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -442,7 +414,7 @@ def test_required_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "body", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {"p": "hello"}), @@ -455,19 +427,14 @@ def test_required_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_alias": "hello"}) - assert response.status_code == 422, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -485,18 +452,13 @@ def test_required_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.post(path, data={"p_val_alias": "hello"}) - assert response.status_code == 200, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py index 1bd3628b89..4a5f4bb647 100644 --- a/tests/test_request_params/test_header/test_list.py +++ b/tests/test_request_params/test_header/test_list.py @@ -3,7 +3,6 @@ from typing import List import pytest from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict from fastapi import FastAPI, Header -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -109,7 +108,7 @@ class HeaderModelRequiredListAlias(BaseModel): async def read_model_required_list_alias( p: Annotated[HeaderModelRequiredListAlias, Header()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( @@ -168,15 +167,7 @@ def test_required_list_alias_missing(path: str): "path", [ "/required-list-alias", - pytest.param( - "/model-required-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/model-required-list-alias", ], ) def test_required_list_alias_by_name(path: str): @@ -190,9 +181,7 @@ def test_required_list_alias_by_name(path: str): "type": "missing", "loc": ["header", "p_alias"], "msg": "Field required", - "input": IsOneOf( # /model-required-list-alias with PDv2 fails here - None, IsPartialDict({"p": ["hello", "world"]}) - ), + "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), } ] } @@ -214,18 +203,13 @@ def test_required_list_alias_by_name(path: str): "path", [ "/required-list-alias", - pytest.param( - "/model-required-list-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-required-list-alias", ], ) def test_required_list_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) - assert response.status_code == 200, ( # /model-required-list-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": ["hello", "world"]} @@ -248,11 +232,10 @@ class HeaderModelRequiredListValidationAlias(BaseModel): async def read_model_required_list_validation_alias( p: Annotated[HeaderModelRequiredListValidationAlias, Header()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -276,10 +259,7 @@ def test_required_list_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) @@ -293,7 +273,7 @@ def test_required_list_validation_alias_missing(path: str): "type": "missing", "loc": [ "header", - "p_val_alias", # /required-list-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": AnyThing, @@ -306,17 +286,14 @@ def test_required_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) def test_required_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(path, headers=[("p", "hello"), ("p", "world")]) - assert response.status_code == 422 # /required-list-validation-alias fails here + assert response.status_code == 422 assert response.json() == { "detail": [ @@ -331,7 +308,6 @@ def test_required_list_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -341,9 +317,9 @@ def test_required_list_validation_alias_by_validation_alias(path: str): response = client.get( path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] ) - assert response.status_code == 200, response.text # both fail here + assert response.status_code == 200, response.text - assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -365,11 +341,10 @@ class HeaderModelRequiredListAliasAndValidationAlias(BaseModel): def read_model_required_list_alias_and_validation_alias( p: Annotated[HeaderModelRequiredListAliasAndValidationAlias, Header()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -396,10 +371,7 @@ def test_required_list_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) @@ -413,7 +385,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "header", - # /required-list-alias-and-validation-alias fails here "p_val_alias", ], "msg": "Field required", @@ -424,7 +395,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -442,13 +412,11 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "header", - # /required-list-alias-and-validation-alias fails here "p_val_alias", ], "msg": "Field required", "input": IsOneOf( None, - # /model-required-list-alias-and-validation-alias fails here IsPartialDict({"p": ["hello", "world"]}), ), } @@ -457,7 +425,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -468,9 +435,7 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): def test_required_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) - assert ( # /required-list-alias-and-validation-alias fails here - response.status_code == 422 - ) + assert response.status_code == 422 assert response.json() == { "detail": [ { @@ -479,7 +444,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): "msg": "Field required", "input": IsOneOf( None, - # /model-required-list-alias-and-validation-alias fails here IsPartialDict({"p_alias": ["hello", "world"]}), ), } @@ -488,7 +452,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -501,5 +464,5 @@ def test_required_list_alias_and_validation_alias_by_validation_alias(path: str) response = client.get( path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] ) - assert response.status_code == 200, response.text # both fail here - assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py index 328f039ba3..e81025c02b 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -171,19 +171,14 @@ def test_optional_list_alias_by_name(path: str): "path", [ "/optional-list-alias", - pytest.param( - "/model-optional-list-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-optional-list-alias", ], ) def test_optional_list_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) assert response.status_code == 200 - assert response.json() == { - "p": ["hello", "world"] # /model-optional-list-alias fails here - } + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -209,7 +204,6 @@ def read_model_optional_list_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -247,10 +241,7 @@ def test_optional_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-validation-alias", "/model-optional-list-validation-alias", ], ) @@ -258,11 +249,10 @@ def test_optional_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(path, headers=[("p", "hello"), ("p", "world")]) assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-list-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -272,12 +262,8 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): response = client.get( path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] ) - assert response.status_code == 200, ( - response.text # /model-optional-list-validation-alias fails here - ) - assert response.json() == { # /optional-list-validation-alias fails here - "p": ["hello", "world"] - } + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -307,7 +293,6 @@ def read_model_optional_list_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -366,10 +351,7 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-alias-and-validation-alias", "/model-optional-list-alias-and-validation-alias", ], ) @@ -377,13 +359,10 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers=[("p_alias", "hello"), ("p_alias", "world")]) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-list-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -396,11 +375,9 @@ def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str) response = client.get( path, headers=[("p_val_alias", "hello"), ("p_val_alias", "world")] ) - assert response.status_code == 200, ( - response.text # /model-optional-list-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == { - "p": [ # /optional-list-alias-and-validation-alias fails here + "p": [ "hello", "world", ] diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py index d63e0a2b8c..5ae9f26701 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -155,17 +155,14 @@ def test_optional_alias_by_name(path: str): "path", [ "/optional-alias", - pytest.param( - "/model-optional-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-optional-alias", ], ) def test_optional_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_alias": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /model-optional-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -191,7 +188,6 @@ def read_model_optional_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -226,10 +222,7 @@ def test_optional_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -237,17 +230,14 @@ def test_optional_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(path, headers={"p": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -255,7 +245,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_val_alias": "hello"}) assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -283,7 +273,6 @@ def read_model_optional_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -339,10 +328,7 @@ def test_optional_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -350,19 +336,14 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_alias": "hello"}) assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -370,6 +351,4 @@ def test_optional_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_val_alias": "hello"}) assert response.status_code == 200 - assert response.json() == { - "p": "hello" # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py index 6eb4fd6f6a..d57c66919d 100644 --- a/tests/test_request_params/test_header/test_required_str.py +++ b/tests/test_request_params/test_header/test_required_str.py @@ -1,7 +1,6 @@ import pytest from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict from fastapi import FastAPI, Header -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -102,7 +101,7 @@ class HeaderModelRequiredAlias(BaseModel): @app.get("/model-required-alias") async def read_model_required_alias(p: Annotated[HeaderModelRequiredAlias, Header()]): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( @@ -157,15 +156,7 @@ def test_required_alias_missing(path: str): "path", [ "/required-alias", - pytest.param( - "/model-required-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/model-required-alias", ], ) def test_required_alias_by_name(path: str): @@ -201,18 +192,13 @@ def test_required_alias_by_name(path: str): "path", [ "/required-alias", - pytest.param( - "/model-required-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-required-alias", ], ) def test_required_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_alias": "hello"}) - assert response.status_code == 200, ( # /model-required-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -239,7 +225,6 @@ def read_model_required_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -259,10 +244,7 @@ def test_required_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -276,7 +258,7 @@ def test_required_validation_alias_missing(path: str): "type": "missing", "loc": [ "header", - "p_val_alias", # /required-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": AnyThing, @@ -289,19 +271,14 @@ def test_required_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(path, headers={"p": "hello"}) - assert response.status_code == 422, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -319,19 +296,14 @@ def test_required_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_val_alias": "hello"}) - assert response.status_code == 200, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -359,7 +331,6 @@ def read_model_required_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -382,10 +353,7 @@ def test_required_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -399,7 +367,7 @@ def test_required_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "header", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": AnyThing, @@ -409,7 +377,6 @@ def test_required_alias_and_validation_alias_missing(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -428,10 +395,10 @@ def test_required_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "header", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", - "input": IsOneOf( # /model-alias-and-validation-alias fails here + "input": IsOneOf( None, IsPartialDict({"p": "hello"}), ), @@ -441,7 +408,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -452,9 +418,7 @@ def test_required_alias_and_validation_alias_by_name(path: str): def test_required_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_alias": "hello"}) - assert ( - response.status_code == 422 # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 422 assert response.json() == { "detail": [ @@ -462,7 +426,7 @@ def test_required_alias_and_validation_alias_by_alias(path: str): "type": "missing", "loc": ["header", "p_val_alias"], "msg": "Field required", - "input": IsOneOf( # /model-alias-and-validation-alias fails here + "input": IsOneOf( None, IsPartialDict({"p_alias": "hello"}), ), @@ -475,18 +439,13 @@ def test_required_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(path, headers={"p_val_alias": "hello"}) - assert response.status_code == 200, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py index 8e2e60004c..6417199674 100644 --- a/tests/test_request_params/test_path/test_required_str.py +++ b/tests/test_request_params/test_path/test_required_str.py @@ -22,14 +22,14 @@ async def read_required_alias(p: Annotated[str, Path(alias="p_alias")]): def read_required_validation_alias( p: Annotated[str, Path(validation_alias="p_val_alias")], ): - return {"p": p} # pragma: no cover + return {"p": p} @app.get("/required-alias-and-validation-alias/{p_val_alias}") def read_required_alias_and_validation_alias( p: Annotated[str, Path(alias="p_alias", validation_alias="p_val_alias")], ): - return {"p": p} # pragma: no cover + return {"p": p} @pytest.mark.parametrize( @@ -44,20 +44,14 @@ def read_required_alias_and_validation_alias( "p_val_alias", "P Val Alias", id="required-validation-alias", - marks=( - needs_pydanticv2, - pytest.mark.xfail(raises=AssertionError, strict=False), - ), + marks=needs_pydanticv2, ), pytest.param( "/required-alias-and-validation-alias/{p_val_alias}", "p_val_alias", "P Val Alias", id="required-alias-and-validation-alias", - marks=( - needs_pydanticv2, - pytest.mark.xfail(raises=AssertionError, strict=False), - ), + marks=needs_pydanticv2, ), ], ) @@ -80,18 +74,12 @@ def test_schema(path: str, expected_name: str, expected_title: str): pytest.param( "/required-validation-alias", id="required-validation-alias", - marks=( - needs_pydanticv2, - pytest.mark.xfail(raises=AssertionError, strict=False), - ), + marks=needs_pydanticv2, ), pytest.param( "/required-alias-and-validation-alias", id="required-alias-and-validation-alias", - marks=( - needs_pydanticv2, - pytest.mark.xfail(raises=AssertionError, strict=False), - ), + marks=needs_pydanticv2, ), ], ) diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py index 4edd192e06..71cbca51fb 100644 --- a/tests/test_request_params/test_query/test_list.py +++ b/tests/test_request_params/test_query/test_list.py @@ -3,7 +3,6 @@ from typing import List import pytest from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI, Query -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -109,7 +108,7 @@ class QueryModelRequiredListAlias(BaseModel): async def read_model_required_list_alias( p: Annotated[QueryModelRequiredListAlias, Query()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( @@ -168,15 +167,7 @@ def test_required_list_alias_missing(path: str): "path", [ "/required-list-alias", - pytest.param( - "/model-required-list-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/model-required-list-alias", ], ) def test_required_list_alias_by_name(path: str): @@ -190,9 +181,7 @@ def test_required_list_alias_by_name(path: str): "type": "missing", "loc": ["query", "p_alias"], "msg": "Field required", - "input": IsOneOf( # /model-required-list-alias with PDv2 fails here - None, {"p": ["hello", "world"]} - ), + "input": IsOneOf(None, {"p": ["hello", "world"]}), } ] } @@ -214,18 +203,13 @@ def test_required_list_alias_by_name(path: str): "path", [ "/required-list-alias", - pytest.param( - "/model-required-list-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-required-list-alias", ], ) def test_required_list_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello&p_alias=world") - assert response.status_code == 200, ( # /model-required-list-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": ["hello", "world"]} @@ -248,11 +232,10 @@ class QueryModelRequiredListValidationAlias(BaseModel): async def read_model_required_list_validation_alias( p: Annotated[QueryModelRequiredListValidationAlias, Query()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -276,10 +259,7 @@ def test_required_list_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) @@ -293,7 +273,7 @@ def test_required_list_validation_alias_missing(path: str): "type": "missing", "loc": [ "query", - "p_val_alias", # /required-list-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -306,17 +286,14 @@ def test_required_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-validation-alias", "/model-required-list-validation-alias", ], ) def test_required_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(f"{path}?p=hello&p=world") - assert response.status_code == 422 # /required-list-validation-alias fails here + assert response.status_code == 422 assert response.json() == { "detail": [ @@ -331,7 +308,6 @@ def test_required_list_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -339,9 +315,9 @@ def test_required_list_validation_alias_by_name(path: str): def test_required_list_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") - assert response.status_code == 200, response.text # both fail here + assert response.status_code == 200, response.text - assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -363,11 +339,10 @@ class QueryModelRequiredListAliasAndValidationAlias(BaseModel): def read_model_required_list_alias_and_validation_alias( p: Annotated[QueryModelRequiredListAliasAndValidationAlias, Query()], ): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -394,10 +369,7 @@ def test_required_list_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-list-alias-and-validation-alias", "/model-required-list-alias-and-validation-alias", ], ) @@ -411,7 +383,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "query", - # /required-list-alias-and-validation-alias fails here "p_val_alias", ], "msg": "Field required", @@ -422,7 +393,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -440,13 +410,11 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "query", - # /required-list-alias-and-validation-alias fails here "p_val_alias", ], "msg": "Field required", "input": IsOneOf( None, - # /model-required-list-alias-and-validation-alias fails here { "p": [ "hello", @@ -460,7 +428,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -471,9 +438,7 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): def test_required_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello&p_alias=world") - assert ( # /required-list-alias-and-validation-alias fails here - response.status_code == 422 - ) + assert response.status_code == 422 assert response.json() == { "detail": [ { @@ -482,7 +447,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): "msg": "Field required", "input": IsOneOf( None, - # /model-required-list-alias-and-validation-alias fails here {"p_alias": ["hello", "world"]}, ), } @@ -491,7 +455,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -502,5 +465,5 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") - assert response.status_code == 200, response.text # both fail here - assert response.json() == {"p": ["hello", "world"]} # pragma: no cover + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py index 76f960554c..5609213364 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -171,19 +171,14 @@ def test_optional_list_alias_by_name(path: str): "path", [ "/optional-list-alias", - pytest.param( - "/model-optional-list-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-optional-list-alias", ], ) def test_optional_list_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello&p_alias=world") assert response.status_code == 200 - assert response.json() == { - "p": ["hello", "world"] # /model-optional-list-alias fails here - } + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -209,7 +204,6 @@ def read_model_optional_list_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -247,10 +241,7 @@ def test_optional_list_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-validation-alias", "/model-optional-list-validation-alias", ], ) @@ -258,11 +249,10 @@ def test_optional_list_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(f"{path}?p=hello&p=world") assert response.status_code == 200 - assert response.json() == {"p": None} # /optional-list-validation-alias fails here + assert response.json() == {"p": None} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -270,12 +260,8 @@ def test_optional_list_validation_alias_by_name(path: str): def test_optional_list_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") - assert response.status_code == 200, ( - response.text # /model-optional-list-validation-alias fails here - ) - assert response.json() == { # /optional-list-validation-alias fails here - "p": ["hello", "world"] - } + assert response.status_code == 200, response.text + assert response.json() == {"p": ["hello", "world"]} # ===================================================================================== @@ -305,7 +291,6 @@ def read_model_optional_list_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -364,10 +349,7 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-list-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-list-alias-and-validation-alias", "/model-optional-list-alias-and-validation-alias", ], ) @@ -375,13 +357,10 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello&p_alias=world") assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-list-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -392,11 +371,9 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello&p_val_alias=world") - assert response.status_code == 200, ( - response.text # /model-optional-list-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == { - "p": [ # /optional-list-alias-and-validation-alias fails here + "p": [ "hello", "world", ] diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py index 77da9bee61..25e4ea42e6 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -155,17 +155,14 @@ def test_optional_alias_by_name(path: str): "path", [ "/optional-alias", - pytest.param( - "/model-optional-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-optional-alias", ], ) def test_optional_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello") assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /model-optional-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -191,7 +188,6 @@ def read_model_optional_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -226,10 +222,7 @@ def test_optional_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -244,10 +237,7 @@ def test_optional_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-validation-alias", "/model-optional-validation-alias", ], ) @@ -255,7 +245,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello") assert response.status_code == 200 - assert response.json() == {"p": "hello"} # /optional-validation-alias fails here + assert response.json() == {"p": "hello"} # ===================================================================================== @@ -283,7 +273,6 @@ def read_model_optional_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -339,10 +328,7 @@ def test_optional_alias_and_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -350,19 +336,14 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello") assert response.status_code == 200 - assert response.json() == { - "p": None # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": None} @needs_pydanticv2 @pytest.mark.parametrize( "path", [ - pytest.param( - "/optional-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/optional-alias-and-validation-alias", "/model-optional-alias-and-validation-alias", ], ) @@ -370,6 +351,4 @@ def test_optional_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello") assert response.status_code == 200 - assert response.json() == { - "p": "hello" # /optional-alias-and-validation-alias fails here - } + assert response.json() == {"p": "hello"} diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py index aa3a27683c..aa0731e2c6 100644 --- a/tests/test_request_params/test_query/test_required_str.py +++ b/tests/test_request_params/test_query/test_required_str.py @@ -1,7 +1,6 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI, Query -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field from typing_extensions import Annotated @@ -102,7 +101,7 @@ class QueryModelRequiredAlias(BaseModel): @app.get("/model-required-alias") async def read_model_required_alias(p: Annotated[QueryModelRequiredAlias, Query()]): - return {"p": p.p} # pragma: no cover + return {"p": p.p} @pytest.mark.parametrize( @@ -157,15 +156,7 @@ def test_required_alias_missing(path: str): "path", [ "/required-alias", - pytest.param( - "/model-required-alias", - marks=pytest.mark.xfail( - raises=AssertionError, - condition=PYDANTIC_V2, - reason="Fails only with PDv2 models", - strict=False, - ), - ), + "/model-required-alias", ], ) def test_required_alias_by_name(path: str): @@ -181,7 +172,7 @@ def test_required_alias_by_name(path: str): "msg": "Field required", "input": IsOneOf( None, - {"p": "hello"}, # /model-required-alias PDv2 fails here + {"p": "hello"}, ), } ] @@ -204,18 +195,13 @@ def test_required_alias_by_name(path: str): "path", [ "/required-alias", - pytest.param( - "/model-required-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/model-required-alias", ], ) def test_required_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello") - assert response.status_code == 200, ( # /model-required-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -242,7 +228,6 @@ def read_model_required_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -262,10 +247,7 @@ def test_required_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) @@ -279,7 +261,7 @@ def test_required_validation_alias_missing(path: str): "type": "missing", "loc": [ "query", - "p_val_alias", # /required-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -292,19 +274,14 @@ def test_required_validation_alias_missing(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_name(path: str): client = TestClient(app) response = client.get(f"{path}?p=hello") - assert response.status_code == 422, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 422, response.text assert response.json() == { "detail": [ @@ -322,19 +299,14 @@ def test_required_validation_alias_by_name(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-validation-alias", "/model-required-validation-alias", ], ) def test_required_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello") - assert response.status_code == 200, ( # /required-validation-alias fails here - response.text - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} @@ -362,7 +334,6 @@ def read_model_required_alias_and_validation_alias( @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -385,10 +356,7 @@ def test_required_alias_and_validation_alias_schema(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) @@ -402,7 +370,7 @@ def test_required_alias_and_validation_alias_missing(path: str): "type": "missing", "loc": [ "query", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", "input": IsOneOf(None, {}), @@ -412,7 +380,6 @@ def test_required_alias_and_validation_alias_missing(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -431,10 +398,10 @@ def test_required_alias_and_validation_alias_by_name(path: str): "type": "missing", "loc": [ "query", - "p_val_alias", # /required-alias-and-validation-alias fails here + "p_val_alias", ], "msg": "Field required", - "input": IsOneOf( # /model-alias-and-validation-alias fails here + "input": IsOneOf( None, {"p": "hello"}, ), @@ -444,7 +411,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): @needs_pydanticv2 -@pytest.mark.xfail(raises=AssertionError, strict=False) @pytest.mark.parametrize( "path", [ @@ -455,9 +421,7 @@ def test_required_alias_and_validation_alias_by_name(path: str): def test_required_alias_and_validation_alias_by_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_alias=hello") - assert ( - response.status_code == 422 # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 422 assert response.json() == { "detail": [ @@ -465,7 +429,7 @@ def test_required_alias_and_validation_alias_by_alias(path: str): "type": "missing", "loc": ["query", "p_val_alias"], "msg": "Field required", - "input": IsOneOf( # /model-alias-and-validation-alias fails here + "input": IsOneOf( None, {"p_alias": "hello"}, ), @@ -478,18 +442,13 @@ def test_required_alias_and_validation_alias_by_alias(path: str): @pytest.mark.parametrize( "path", [ - pytest.param( - "/required-alias-and-validation-alias", - marks=pytest.mark.xfail(raises=AssertionError, strict=False), - ), + "/required-alias-and-validation-alias", "/model-required-alias-and-validation-alias", ], ) def test_required_alias_and_validation_alias_by_validation_alias(path: str): client = TestClient(app) response = client.get(f"{path}?p_val_alias=hello") - assert response.status_code == 200, ( - response.text # /required-alias-and-validation-alias fails here - ) + assert response.status_code == 200, response.text assert response.json() == {"p": "hello"} From 89157a803c4c75332566a5ca499afd5358c42049 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Dec 2025 14:57:20 +0000 Subject: [PATCH 058/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 05c43b5f6e..f800036041 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Fixes + +* 🐛 Fix parameter aliases. PR [#14371](https://github.com/fastapi/fastapi/pull/14371) by [@YuriiMotov](https://github.com/YuriiMotov). + ## 0.124.3 ### Fixes From b1d9769f97295a238df8c2d318ad65dd6a40c6ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 15:59:12 +0100 Subject: [PATCH 059/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?4.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f800036041..02fae0e62e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.124.4 + ### Fixes * 🐛 Fix parameter aliases. PR [#14371](https://github.com/fastapi/fastapi/pull/14371) by [@YuriiMotov](https://github.com/YuriiMotov). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index eeb2e10031..e02969c55f 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.124.3" +__version__ = "0.124.4" from starlette import status as status From 435d839c72d8fcef93ef4914a276df0b47728c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 08:54:13 -0800 Subject: [PATCH 060/140] =?UTF-8?q?=E2=9E=95=20Add=20requirements=20for=20?= =?UTF-8?q?translations=20(#14515)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements-translations.txt | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements-translations.txt b/requirements-translations.txt index a62ba3ac1c..76bfae1670 100644 --- a/requirements-translations.txt +++ b/requirements-translations.txt @@ -1,2 +1,3 @@ pydantic-ai==0.4.10 GitPython==3.1.45 +pygithub==2.8.1 diff --git a/requirements.txt b/requirements.txt index 5d9f97b754..1cff1a5a9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ -e .[all] -r requirements-tests.txt -r requirements-docs.txt +-r requirements-translations.txt pre-commit >=4.5.0,<5.0.0 # For generating screenshots playwright From 1163dbd17f23c9af85261ff9ff688d4f1c3968ed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Dec 2025 16:54:50 +0000 Subject: [PATCH 061/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 02fae0e62e..f3761cf14e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* ➕ Add requirements for translations. PR [#14515](https://github.com/fastapi/fastapi/pull/14515) by [@tiangolo](https://github.com/tiangolo). + ## 0.124.4 ### Fixes From 59917ab67947919ce0c26b0ca66cf5a36ccc0ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 12 Dec 2025 08:56:39 -0800 Subject: [PATCH 062/140] =?UTF-8?q?=F0=9F=8C=90=20Remove=20translations=20?= =?UTF-8?q?for=20removed=20docs=20(#14516)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ko/docs/advanced/middlewares.md | 96 ---------------------------- docs/ko/docs/openapi-webhooks.md | 55 ---------------- docs/ko/docs/security/index.md | 19 ------ 3 files changed, 170 deletions(-) delete mode 100644 docs/ko/docs/advanced/middlewares.md delete mode 100644 docs/ko/docs/openapi-webhooks.md delete mode 100644 docs/ko/docs/security/index.md diff --git a/docs/ko/docs/advanced/middlewares.md b/docs/ko/docs/advanced/middlewares.md deleted file mode 100644 index 5778528a8e..0000000000 --- a/docs/ko/docs/advanced/middlewares.md +++ /dev/null @@ -1,96 +0,0 @@ -# 고급 미들웨어 - -메인 튜토리얼에서 [Custom Middleware](../tutorial/middleware.md){.internal-link target=_blank}를 응용프로그램에 추가하는 방법을 읽으셨습니다. - -그리고 [CORS with the `CORSMiddleware`](){.internal-link target=_blank}하는 방법도 보셨습니다. - -이 섹션에서는 다른 미들웨어들을 사용하는 방법을 알아보겠습니다. - -## ASGI 미들웨어 추가하기 - -**FastAPI**는 Starlette을 기반으로 하고 있으며, ASGI 사양을 구현하므로 ASGI 미들웨어를 사용할 수 있습니다. - -미들웨어가 FastAPI나 Starlette용으로 만들어지지 않아도 ASGI 사양을 준수하는 한 동작할 수 있습니다. - -일반적으로 ASGI 미들웨어는 첫 번째 인수로 ASGI 앱을 받는 클래스들입니다. - -따라서 타사 ASGI 미들웨어 문서에서 일반적으로 다음과 같이 사용하도록 안내할 것입니다. - -```Python -from unicorn import UnicornMiddleware - -app = SomeASGIApp() - -new_app = UnicornMiddleware(app, some_config="rainbow") -``` - -하지만 내부 미들웨어가 서버 오류를 처리하고 사용자 정의 예외 처리기가 제대로 작동하도록 하는 더 간단한 방법을 제공하는 FastAPI(실제로는 Starlette)가 있습니다. - -이를 위해 `app.add_middleware()`를 사용합니다(CORS의 예에서와 같이). - -```Python -from fastapi import FastAPI -from unicorn import UnicornMiddleware - -app = FastAPI() - -app.add_middleware(UnicornMiddleware, some_config="rainbow") -``` - -`app.add_middleware()`는 첫 번째 인수로 미들웨어 클래스와 미들웨어에 전달할 추가 인수를 받습니다. - -## 통합 미들웨어 - -**FastAPI**에는 일반적인 사용 사례를 위한 여러 미들웨어가 포함되어 있으며, 사용 방법은 다음에서 살펴보겠습니다. - -/// note | 기술 세부 사항 - -다음 예제에서는 `from starlette.middleware.something import SomethingMiddleware`를 사용할 수도 있습니다. - -**FastAPI**는 개발자의 편의를 위해 `fastapi.middleware`에 여러 미들웨어를 제공합니다. 그러나 사용 가능한 대부분의 미들웨어는 Starlette에서 직접 제공합니다. - -/// - -## `HTTPSRedirectMiddleware` - -들어오는 모든 요청이 `https` 또는 `wss`여야 합니다. - -`http` 또는 `ws`로 들어오는 모든 요청은 대신 보안 체계로 리디렉션됩니다. - -{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} - -## `TrustedHostMiddleware` - -HTTP 호스트 헤더 공격을 방지하기 위해 모든 수신 요청에 올바르게 설정된 `Host` 헤더를 갖도록 강제합니다. - -{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} - -다음 인수가 지원됩니다: - -* `allowed_hosts` - 호스트 이름으로 허용해야 하는 도메인 이름 목록입니다. 일치하는 하위 도메인에 대해 `*.example.com`과 같은 와일드카드 도메인이 지원됩니다. 모든 호스트 이름을 허용하려면 `allowed_hosts=[“*”]`를 사용하거나 미들웨어를 생략하세요. - -수신 요청의 유효성이 올바르게 확인되지 않으면 `400`이라는 응답이 전송됩니다. - -## `GZipMiddleware` - -`Accept-Encoding` 헤더에 `“gzip”`이 포함된 모든 요청에 대해 GZip 응답을 처리합니다. - -미들웨어는 표준 응답과 스트리밍 응답을 모두 처리합니다. - -{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} - -지원되는 인수는 다음과 같습니다: - -* `minimum_size` - 이 최소 크기(바이트)보다 작은 응답은 GZip하지 않습니다. 기본값은 `500`입니다. -* `compresslevel` - GZip 압축 중에 사용됩니다. 1에서 9 사이의 정수입니다. 기본값은 `9`입니다. 값이 낮을수록 압축 속도는 빨라지지만 파일 크기는 커지고, 값이 높을수록 압축 속도는 느려지지만 파일 크기는 작아집니다. - -## 기타 미들웨어 - -다른 많은 ASGI 미들웨어가 있습니다. - -예를 들어: - -유비콘의 `ProxyHeadersMiddleware`> -MessagePack - -사용 가능한 다른 미들웨어를 확인하려면 스타렛의 미들웨어 문서ASGI Awesome List를 참조하세요. diff --git a/docs/ko/docs/openapi-webhooks.md b/docs/ko/docs/openapi-webhooks.md deleted file mode 100644 index 96339aa961..0000000000 --- a/docs/ko/docs/openapi-webhooks.md +++ /dev/null @@ -1,55 +0,0 @@ -# OpenAPI 웹훅(Webhooks) - -API **사용자**에게 특정 **이벤트**가 발생할 때 *그들*의 앱(시스템)에 요청을 보내 **알림**을 전달할 수 있다는 것을 알리고 싶은 경우가 있습니다. - -즉, 일반적으로 사용자가 API에 요청을 보내는 것과는 반대로, **API**(또는 앱)가 **사용자의 시스템**(그들의 API나 앱)으로 **요청을 보내는** 상황을 의미합니다. - -이를 흔히 **웹훅(Webhook)**이라고 부릅니다. - -## 웹훅 스텝 - -**코드에서** 웹훅으로 보낼 메시지, 즉 요청의 **바디(body)**를 정의하는 것이 일반적인 프로세스입니다. - -앱에서 해당 요청이나 이벤트를 전송할 **시점**을 정의합니다. - -**사용자**는 앱이 해당 요청을 보낼 **URL**을 정의합니다. (예: 웹 대시보드에서 설정) - -웹훅의 URL을 등록하는 방법과 이러한 요청을 실제로 전송하는 코드에 대한 모든 로직은 여러분에게 달려 있습니다. 원하는대로 **고유의 코드**를 작성하면 됩니다. - -## **FastAPI**와 OpenAPI로 웹훅 문서화하기 - -**FastAPI**를 사용하여 OpenAPI와 함께 웹훅의 이름, 앱이 보낼 수 있는 HTTP 작업 유형(예: `POST`, `PUT` 등), 그리고 보낼 요청의 **바디**를 정의할 수 있습니다. - -이를 통해 사용자가 **웹훅** 요청을 수신할 **API 구현**을 훨씬 쉽게 할 수 있으며, 경우에 따라 사용자 API 코드의 일부를 자동 생성할 수도 있습니다. - -/// info - -웹훅은 OpenAPI 3.1.0 이상에서 지원되며, FastAPI `0.99.0` 이상 버전에서 사용할 수 있습니다. - -/// - -## 웹훅이 포함된 앱 만들기 - -**FastAPI** 애플리케이션을 만들 때, `webhooks` 속성을 사용하여 *웹훅*을 정의할 수 있습니다. 이는 `@app.webhooks.post()`와 같은 방식으로 *경로(path) 작업*을 정의하는 것과 비슷합니다. - -{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} - -이렇게 정의한 웹훅은 **OpenAPI** 스키마와 자동 **문서화 UI**에 표시됩니다. - -/// info - -`app.webhooks` 객체는 사실 `APIRouter`일 뿐이며, 여러 파일로 앱을 구성할 때 사용하는 것과 동일한 타입입니다. - -/// - -웹훅에서는 실제 **경로(path)** (예: `/items/`)를 선언하지 않는 점에 유의해야 합니다. 여기서 전달하는 텍스트는 **식별자**로, 웹훅의 이름(이벤트 이름)입니다. 예를 들어, `@app.webhooks.post("new-subscription")`에서 웹훅 이름은 `new-subscription`입니다. - -이는 실제 **URL 경로**는 **사용자**가 다른 방법(예: 웹 대시보드)을 통해 지정하도록 기대되기 때문입니다. - -### 문서 확인하기 - -이제 앱을 시작하고 http://127.0.0.1:8000/docs로 이동해 봅시다. - -문서에서 기존 *경로 작업*뿐만 아니라 **웹훅**도 표시된 것을 확인할 수 있습니다: - - diff --git a/docs/ko/docs/security/index.md b/docs/ko/docs/security/index.md deleted file mode 100644 index 5a6c733f05..0000000000 --- a/docs/ko/docs/security/index.md +++ /dev/null @@ -1,19 +0,0 @@ -# 고급 보안 - -## 추가 기능 - -[자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서에서 다룬 내용 외에도 보안 처리를 위한 몇 가지 추가 기능이 있습니다. - -/// tip - -다음 섹션은 **반드시 "고급"** 기능은 아닙니다. - -그리고 여러분의 사용 사례에 따라, 적합한 해결책이 그 중 하나에 있을 가능성이 있습니다. - -/// - -## 먼저 자습서 읽기 - -다음 섹션은 이미 [자습서 - 사용자 가이드: 보안](../../tutorial/security/index.md){.internal-link target=_blank} 문서를 읽었다고 가정합니다. - -이 섹션들은 모두 동일한 개념을 바탕으로 하며, 추가 기능을 제공합니다. From 61ffa3eb82ba9c8adb9f4a6366647e6d0e204dae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 12 Dec 2025 16:57:03 +0000 Subject: [PATCH 063/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f3761cf14e..d523f9f7fa 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* 🌐 Remove translations for removed docs. PR [#14516](https://github.com/fastapi/fastapi/pull/14516) by [@tiangolo](https://github.com/tiangolo). + ### Internal * ➕ Add requirements for translations. PR [#14515](https://github.com/fastapi/fastapi/pull/14515) by [@tiangolo](https://github.com/tiangolo). From c54834838628966ac14c45e2a57c31383e0c8a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 04:34:01 -0800 Subject: [PATCH 064/140] =?UTF-8?q?=F0=9F=91=B7=20Update=20github-actions?= =?UTF-8?q?=20user=20for=20GitHub=20Actions=20workflows=20(#14528)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/contributors.py | 5 +++-- scripts/people.py | 5 +++-- scripts/sponsors.py | 5 +++-- scripts/topic_repos.py | 5 +++-- scripts/translate.py | 5 +++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/scripts/contributors.py b/scripts/contributors.py index 251558de7f..af1434d795 100644 --- a/scripts/contributors.py +++ b/scripts/contributors.py @@ -282,9 +282,10 @@ def main() -> None: return logging.info("Setting up GitHub Actions git user") - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True) subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True + ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], + check=True, ) branch_name = f"fastapi-people-contributors-{secrets.token_hex(4)}" logging.info(f"Creating a new branch {branch_name}") diff --git a/scripts/people.py b/scripts/people.py index 7418b45956..65e009944c 100644 --- a/scripts/people.py +++ b/scripts/people.py @@ -378,9 +378,10 @@ def main() -> None: return logging.info("Setting up GitHub Actions git user") - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True) subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True + ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], + check=True, ) branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}" logging.info(f"Creating a new branch {branch_name}") diff --git a/scripts/sponsors.py b/scripts/sponsors.py index 45e02bd621..fdcabc737b 100644 --- a/scripts/sponsors.py +++ b/scripts/sponsors.py @@ -190,9 +190,10 @@ def main() -> None: return logging.info("Setting up GitHub Actions git user") - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True) subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True + ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], + check=True, ) branch_name = f"fastapi-people-sponsors-{secrets.token_hex(4)}" logging.info(f"Creating a new branch {branch_name}") diff --git a/scripts/topic_repos.py b/scripts/topic_repos.py index bc14977513..b7afc0864a 100644 --- a/scripts/topic_repos.py +++ b/scripts/topic_repos.py @@ -56,9 +56,10 @@ def main() -> None: return repos_path.write_text(new_repos_content, encoding="utf-8") logging.info("Setting up GitHub Actions git user") - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True) subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True + ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], + check=True, ) branch_name = f"fastapi-topic-repos-{secrets.token_hex(4)}" logging.info(f"Creating a new branch {branch_name}") diff --git a/scripts/translate.py b/scripts/translate.py index ede101e8fc..7b8d090e52 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -947,9 +947,10 @@ def make_pr( if not repo.is_dirty(untracked_files=True): print("Repository is clean, no changes to commit") return - subprocess.run(["git", "config", "user.name", "github-actions"], check=True) + subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True) subprocess.run( - ["git", "config", "user.email", "github-actions@github.com"], check=True + ["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"], + check=True, ) branch_name = "translate" if language: From 4414cd849d6537bae5a99f112c35d2e779a7dfbc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 12:34:25 +0000 Subject: [PATCH 065/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d523f9f7fa..fda58e763d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Internal +* 👷 Update github-actions user for GitHub Actions workflows. PR [#14528](https://github.com/fastapi/fastapi/pull/14528) by [@tiangolo](https://github.com/tiangolo). * ➕ Add requirements for translations. PR [#14515](https://github.com/fastapi/fastapi/pull/14515) by [@tiangolo](https://github.com/tiangolo). ## 0.124.4 From 2e7aaea52483dbc5af3c61b6b6ec146e2a7efc01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 04:40:50 -0800 Subject: [PATCH 066/140] =?UTF-8?q?=F0=9F=91=B7=20Update=20LLM=20translati?= =?UTF-8?q?on=20CI,=20add=20language=20matrix=20and=20extra=20commands,=20?= =?UTF-8?q?prepare=20for=20scheduled=20run=20(#14529)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/translate.yml | 46 +++++++++++++++++++---- scripts/translate.py | 65 +++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 7 deletions(-) diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index 6506b8e288..62e3994c4e 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -16,7 +16,7 @@ on: - update-outdated - add-missing - update-and-add - - remove-all-removable + - remove-removable language: description: Language to translate to as a letter code (e.g. "es" for Spanish) type: string @@ -32,9 +32,42 @@ env: UV_SYSTEM_PYTHON: 1 jobs: - job: - if: github.repository_owner == 'fastapi' + langs: runs-on: ubuntu-latest + outputs: + langs: ${{ steps.show-langs.outputs.langs }} + commands: ${{ steps.show-langs.outputs.commands }} + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Setup uv + uses: astral-sh/setup-uv@v7 + with: + cache-dependency-glob: | + requirements**.txt + pyproject.toml + - name: Install Dependencies + run: uv pip install -r requirements-github-actions.txt -r requirements-translations.txt + - name: Export Language Codes + id: show-langs + run: | + echo "langs=$(python ./scripts/translate.py llm-translatable-json)" >> $GITHUB_OUTPUT + echo "commands=$(python ./scripts/translate.py commands-json)" >> $GITHUB_OUTPUT + env: + LANGUAGE: ${{ github.event.inputs.language }} + COMMAND: ${{ github.event.inputs.command }} + + translate: + if: github.repository_owner == 'fastapi' + needs: langs + runs-on: ubuntu-latest + strategy: + matrix: + lang: ${{ fromJson(needs.langs.outputs.langs) }} + command: ${{ fromJson(needs.langs.outputs.commands) }} permissions: contents: write steps: @@ -50,8 +83,6 @@ jobs: - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" - enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml @@ -68,10 +99,11 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - name: FastAPI Translate run: | - python ./scripts/translate.py ${{ github.event.inputs.command }} + python ./scripts/translate.py ${{ matrix.command }} python ./scripts/translate.py make-pr env: GITHUB_TOKEN: ${{ secrets.FASTAPI_TRANSLATIONS }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - LANGUAGE: ${{ github.event.inputs.language }} + LANGUAGE: ${{ matrix.lang }} EN_PATH: ${{ github.event.inputs.en_path }} + COMMAND: ${{ matrix.command }} diff --git a/scripts/translate.py b/scripts/translate.py index 7b8d090e52..33a4bd6ef7 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -1,3 +1,4 @@ +import json import secrets import subprocess from collections.abc import Iterable @@ -828,6 +829,65 @@ def translate_lang(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> print(f"Done translating: {p}") +def get_llm_translatable() -> list[str]: + translatable_langs = [] + langs = get_langs() + for lang in langs: + if lang == "en": + continue + lang_prompt_path = Path(f"docs/{lang}/llm-prompt.md") + if lang_prompt_path.exists(): + translatable_langs.append(lang) + return translatable_langs + + +@app.command() +def list_llm_translatable() -> list[str]: + translatable_langs = get_llm_translatable() + print("LLM translatable languages:", translatable_langs) + return translatable_langs + + +@app.command() +def llm_translatable_json( + language: Annotated[str | None, typer.Option(envvar="LANGUAGE")] = None, +) -> None: + translatable_langs = get_llm_translatable() + if language: + if language in translatable_langs: + print(json.dumps([language])) + return + else: + raise typer.Exit(code=1) + print(json.dumps(translatable_langs)) + + +@app.command() +def commands_json( + command: Annotated[str | None, typer.Option(envvar="COMMAND")] = None, +) -> None: + available_commands = [ + "translate-page", + "translate-lang", + "update-outdated", + "add-missing", + "update-and-add", + "remove-removable", + ] + default_commands = [ + "remove-removable", + "update-outdated", + "add-missing", + ] + if command: + if command in available_commands: + print(json.dumps([command])) + return + else: + raise typer.Exit(code=1) + print(json.dumps(default_commands)) + + @app.command() def list_removable(language: str) -> list[Path]: removable_paths: list[Path] = [] @@ -939,6 +999,7 @@ def update_and_add(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> def make_pr( *, language: Annotated[str | None, typer.Option(envvar="LANGUAGE")] = None, + command: Annotated[str | None, typer.Option(envvar="COMMAND")] = None, github_token: Annotated[str, typer.Option(envvar="GITHUB_TOKEN")], github_repository: Annotated[str, typer.Option(envvar="GITHUB_REPOSITORY")], ) -> None: @@ -955,6 +1016,8 @@ def make_pr( branch_name = "translate" if language: branch_name += f"-{language}" + if command: + branch_name += f"-{command}" branch_name += f"-{secrets.token_hex(4)}" print(f"Creating a new branch {branch_name}") subprocess.run(["git", "checkout", "-b", branch_name], check=True) @@ -965,6 +1028,8 @@ def make_pr( message = "🌐 Update translations" if language: message += f" for {language}" + if command: + message += f" ({command})" subprocess.run(["git", "commit", "-m", message], check=True) print("Pushing branch") subprocess.run(["git", "push", "origin", branch_name], check=True) From f9397e93b5c2e11cc56591f5bcc7e560e9aa1ada Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 12:41:12 +0000 Subject: [PATCH 067/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index fda58e763d..e0ff1a4816 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Internal +* 👷 Update LLM translation CI, add language matrix and extra commands, prepare for scheduled run. PR [#14529](https://github.com/fastapi/fastapi/pull/14529) by [@tiangolo](https://github.com/tiangolo). * 👷 Update github-actions user for GitHub Actions workflows. PR [#14528](https://github.com/fastapi/fastapi/pull/14528) by [@tiangolo](https://github.com/tiangolo). * ➕ Add requirements for translations. PR [#14515](https://github.com/fastapi/fastapi/pull/14515) by [@tiangolo](https://github.com/tiangolo). From 5da1cb07927575ca3fa036f82fd220ca84510106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 04:53:28 -0800 Subject: [PATCH 068/140] =?UTF-8?q?=F0=9F=91=B7=20Fix=20Typer=20command=20?= =?UTF-8?q?for=20CI=20LLM=20translations=20(#14530)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/translate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/translate.py b/scripts/translate.py index 33a4bd6ef7..764bc704ea 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -914,7 +914,7 @@ def list_all_removable() -> list[Path]: @app.command() -def remove_removable(language: str) -> None: +def remove_removable(language: Annotated[str, typer.Option(envvar="LANGUAGE")]) -> None: removable_paths = list_removable(language) for path in removable_paths: path.unlink() From 41d1b84bd509b58ef8b567fce48b7c4d430a2555 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 12:53:54 +0000 Subject: [PATCH 069/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e0ff1a4816..e44af6584d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Internal +* 👷 Fix Typer command for CI LLM translations. PR [#14530](https://github.com/fastapi/fastapi/pull/14530) by [@tiangolo](https://github.com/tiangolo). * 👷 Update LLM translation CI, add language matrix and extra commands, prepare for scheduled run. PR [#14529](https://github.com/fastapi/fastapi/pull/14529) by [@tiangolo](https://github.com/tiangolo). * 👷 Update github-actions user for GitHub Actions workflows. PR [#14528](https://github.com/fastapi/fastapi/pull/14528) by [@tiangolo](https://github.com/tiangolo). * ➕ Add requirements for translations. PR [#14515](https://github.com/fastapi/fastapi/pull/14515) by [@tiangolo](https://github.com/tiangolo). From 886b367a8c8df26c722c901cac5372a0a798fd82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 07:34:37 -0800 Subject: [PATCH 070/140] =?UTF-8?q?=F0=9F=91=B7=20Fix=20checkout=20GitHub?= =?UTF-8?q?=20Action=20fetch-depth=20for=20LLM=20translations,=20enable=20?= =?UTF-8?q?cron=20monthly=20(#14531)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/translate.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/translate.yml b/.github/workflows/translate.yml index 62e3994c4e..e681762ca3 100644 --- a/.github/workflows/translate.yml +++ b/.github/workflows/translate.yml @@ -1,6 +1,9 @@ name: Translate on: + schedule: + - cron: "0 5 15 * *" # Run at 05:00 on the 15 of every month + workflow_dispatch: inputs: debug_enabled: @@ -76,6 +79,8 @@ jobs: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v6 with: From 532ba725a5c9861623ced98a836d042f3bcc19cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 15:35:03 +0000 Subject: [PATCH 071/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e44af6584d..0bbbdc3f54 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -13,6 +13,7 @@ hide: ### Internal +* 👷 Fix checkout GitHub Action fetch-depth for LLM translations, enable cron monthly. PR [#14531](https://github.com/fastapi/fastapi/pull/14531) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix Typer command for CI LLM translations. PR [#14530](https://github.com/fastapi/fastapi/pull/14530) by [@tiangolo](https://github.com/tiangolo). * 👷 Update LLM translation CI, add language matrix and extra commands, prepare for scheduled run. PR [#14529](https://github.com/fastapi/fastapi/pull/14529) by [@tiangolo](https://github.com/tiangolo). * 👷 Update github-actions user for GitHub Actions workflows. PR [#14528](https://github.com/fastapi/fastapi/pull/14528) by [@tiangolo](https://github.com/tiangolo). From cbbb11f4dfa8b65494986b0891808f72c9f29ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 08:16:35 -0800 Subject: [PATCH 072/140] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20es=20(add-missing)=20(#14533)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/es/docs/_llm-test.md | 503 ++++++++++++++++++ docs/es/docs/deployment/fastapicloud.md | 65 +++ .../authentication-error-status-code.md | 17 + ...migrate-from-pydantic-v1-to-pydantic-v2.md | 133 +++++ 4 files changed, 718 insertions(+) create mode 100644 docs/es/docs/_llm-test.md create mode 100644 docs/es/docs/deployment/fastapicloud.md create mode 100644 docs/es/docs/how-to/authentication-error-status-code.md create mode 100644 docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md diff --git a/docs/es/docs/_llm-test.md b/docs/es/docs/_llm-test.md new file mode 100644 index 0000000000..2ab1d9314b --- /dev/null +++ b/docs/es/docs/_llm-test.md @@ -0,0 +1,503 @@ +# Archivo de prueba de LLM { #llm-test-file } + +Este documento prueba si el LLM, que traduce la documentación, entiende el `general_prompt` en `scripts/translate.py` y el prompt específico del idioma en `docs/{language code}/llm-prompt.md`. El prompt específico del idioma se agrega al final de `general_prompt`. + +Las pruebas añadidas aquí serán vistas por todas las personas que diseñan prompts específicos del idioma. + +Úsalo de la siguiente manera: + +* Ten un prompt específico del idioma – `docs/{language code}/llm-prompt.md`. +* Haz una traducción fresca de este documento a tu idioma destino (mira, por ejemplo, el comando `translate-page` de `translate.py`). Esto creará la traducción en `docs/{language code}/docs/_llm-test.md`. +* Comprueba si todo está bien en la traducción. +* Si es necesario, mejora tu prompt específico del idioma, el prompt general, o el documento en inglés. +* Luego corrige manualmente los problemas restantes en la traducción para que sea una buena traducción. +* Vuelve a traducir, teniendo la buena traducción en su lugar. El resultado ideal sería que el LLM ya no hiciera cambios a la traducción. Eso significa que el prompt general y tu prompt específico del idioma están tan bien como pueden estar (a veces hará algunos cambios aparentemente aleatorios; la razón es que los LLMs no son algoritmos deterministas). + +Las pruebas: + +## Fragmentos de código { #code-snippets } + +//// tab | Prueba + +Este es un fragmento de código: `foo`. Y este es otro fragmento de código: `bar`. Y otro más: `baz quux`. + +//// + +//// tab | Información + +El contenido de los fragmentos de código debe dejarse tal cual. + +Consulta la sección `### Content of code snippets` en el prompt general en `scripts/translate.py`. + +//// + +## Comillas { #quotes } + +//// tab | Prueba + +Ayer, mi amigo escribió: "Si escribes 'incorrectly' correctamente, lo habrás escrito incorrectamente". A lo que respondí: "Correcto, pero 'incorrectly' está incorrecto, no '"incorrectly"'". + +/// note | Nota + +El LLM probablemente traducirá esto mal. Lo interesante es si mantiene la traducción corregida al volver a traducir. + +/// + +//// + +//// tab | Información + +La persona que diseña el prompt puede elegir si quiere convertir comillas neutras a comillas tipográficas. También está bien dejarlas como están. + +Consulta por ejemplo la sección `### Quotes` en `docs/de/llm-prompt.md`. + +//// + +## Comillas en fragmentos de código { #quotes-in-code-snippets } + +//// tab | Prueba + +`pip install "foo[bar]"` + +Ejemplos de literales de string en fragmentos de código: `"this"`, `'that'`. + +Un ejemplo difícil de literales de string en fragmentos de código: `f"I like {'oranges' if orange else "apples"}"` + +Hardcore: `Yesterday, my friend wrote: "If you spell incorrectly correctly, you have spelled it incorrectly". To which I answered: "Correct, but 'incorrectly' is incorrectly not '"incorrectly"'"` + +//// + +//// tab | Información + +... Sin embargo, las comillas dentro de fragmentos de código deben quedarse tal cual. + +//// + +## bloques de código { #code-blocks } + +//// tab | Prueba + +Un ejemplo de código Bash... + +```bash +# Imprime un saludo al universo +echo "Hello universe" +``` + +...y un ejemplo de código de consola... + +```console +$ fastapi run main.py + FastAPI Starting server + Searching for package file structure +``` + +...y otro ejemplo de código de consola... + +```console +// Crea un directorio "Code" +$ mkdir code +// Cambia a ese directorio +$ cd code +``` + +...y un ejemplo de código Python... + +```Python +wont_work() # Esto no va a funcionar 😱 +works(foo="bar") # Esto funciona 🎉 +``` + +...y eso es todo. + +//// + +//// tab | Información + +El código en bloques de código no debe modificarse, con la excepción de los comentarios. + +Consulta la sección `### Content of code blocks` en el prompt general en `scripts/translate.py`. + +//// + +## Pestañas y cajas coloreadas { #tabs-and-colored-boxes } + +//// tab | Prueba + +/// info | Información +Algo de texto +/// + +/// note | Nota +Algo de texto +/// + +/// note | Detalles técnicos +Algo de texto +/// + +/// check | Revisa +Algo de texto +/// + +/// tip | Consejo +Algo de texto +/// + +/// warning | Advertencia +Algo de texto +/// + +/// danger | Peligro +Algo de texto +/// + +//// + +//// tab | Información + +Las pestañas y los bloques `Info`/`Note`/`Warning`/etc. deben tener la traducción de su título añadida después de una barra vertical (`|`). + +Consulta las secciones `### Special blocks` y `### Tab blocks` en el prompt general en `scripts/translate.py`. + +//// + +## Enlaces web e internos { #web-and-internal-links } + +//// tab | Prueba + +El texto del enlace debe traducirse, la dirección del enlace debe permanecer sin cambios: + +* [Enlace al encabezado de arriba](#code-snippets) +* [Enlace interno](index.md#installation){.internal-link target=_blank} +* Enlace externo +* Enlace a un estilo +* Enlace a un script +* Enlace a una imagen + +El texto del enlace debe traducirse, la dirección del enlace debe apuntar a la traducción: + +* Enlace a FastAPI + +//// + +//// tab | Información + +Los enlaces deben traducirse, pero su dirección debe permanecer sin cambios. Una excepción son los enlaces absolutos a páginas de la documentación de FastAPI. En ese caso deben enlazar a la traducción. + +Consulta la sección `### Links` en el prompt general en `scripts/translate.py`. + +//// + +## Elementos HTML "abbr" { #html-abbr-elements } + +//// tab | Prueba + +Aquí algunas cosas envueltas en elementos HTML "abbr" (algunas son inventadas): + +### El abbr da una frase completa { #the-abbr-gives-a-full-phrase } + +* GTD +* lt +* XWT +* PSGI + +### El abbr da una explicación { #the-abbr-gives-an-explanation } + +* clúster +* Deep Learning + +### El abbr da una frase completa y una explicación { #the-abbr-gives-a-full-phrase-and-an-explanation } + +* MDN +* I/O. + +//// + +//// tab | Información + +Los atributos "title" de los elementos "abbr" se traducen siguiendo instrucciones específicas. + +Las traducciones pueden añadir sus propios elementos "abbr" que el LLM no debe eliminar. P. ej., para explicar palabras en inglés. + +Consulta la sección `### HTML abbr elements` en el prompt general en `scripts/translate.py`. + +//// + +## Encabezados { #headings } + +//// tab | Prueba + +### Desarrolla una webapp - un tutorial { #develop-a-webapp-a-tutorial } + +Hola. + +### Anotaciones de tipos y -anotaciones { #type-hints-and-annotations } + +Hola de nuevo. + +### Superclases y subclases { #super-and-subclasses } + +Hola de nuevo. + +//// + +//// tab | Información + +La única regla estricta para los encabezados es que el LLM deje la parte del hash dentro de llaves sin cambios, lo que asegura que los enlaces no se rompan. + +Consulta la sección `### Headings` en el prompt general en `scripts/translate.py`. + +Para instrucciones específicas del idioma, mira p. ej. la sección `### Headings` en `docs/de/llm-prompt.md`. + +//// + +## Términos usados en la documentación { #terms-used-in-the-docs } + +//// tab | Prueba + +* tú +* tu + +* p. ej. +* etc. + +* `foo` como un `int` +* `bar` como un `str` +* `baz` como una `list` + +* el Tutorial - Guía de usuario +* la Guía de usuario avanzada +* la documentación de SQLModel +* la documentación de la API +* la documentación automática + +* Ciencia de datos +* Deep Learning +* Machine Learning +* Inyección de dependencias +* autenticación HTTP Basic +* HTTP Digest +* formato ISO +* el estándar JSON Schema +* el JSON Schema +* la definición del esquema +* Flujo de contraseña +* Móvil + +* obsoleto +* diseñado +* inválido +* sobre la marcha +* estándar +* por defecto +* sensible a mayúsculas/minúsculas +* insensible a mayúsculas/minúsculas + +* servir la aplicación +* servir la página + +* la app +* la aplicación + +* la request +* la response +* la response de error + +* la path operation +* el decorador de path operation +* la path operation function + +* el body +* el request body +* el response body +* el body JSON +* el body del formulario +* el body de archivo +* el cuerpo de la función + +* el parámetro +* el parámetro del body +* el parámetro del path +* el parámetro de query +* el parámetro de cookie +* el parámetro de header +* el parámetro del formulario +* el parámetro de la función + +* el evento +* el evento de inicio +* el inicio del servidor +* el evento de apagado +* el evento de lifespan + +* el manejador +* el manejador de eventos +* el manejador de excepciones +* manejar + +* el modelo +* el modelo de Pydantic +* el modelo de datos +* el modelo de base de datos +* el modelo de formulario +* el objeto del modelo + +* la clase +* la clase base +* la clase padre +* la subclase +* la clase hija +* la clase hermana +* el método de clase + +* el header +* los headers +* el header de autorización +* el header `Authorization` +* el header Forwarded + +* el sistema de inyección de dependencias +* la dependencia +* el dependable +* el dependiente + +* limitado por I/O +* limitado por CPU +* concurrencia +* paralelismo +* multiprocesamiento + +* la variable de entorno +* la variable de entorno +* el `PATH` +* la variable `PATH` + +* la autenticación +* el proveedor de autenticación +* la autorización +* el formulario de autorización +* el proveedor de autorización +* el usuario se autentica +* el sistema autentica al usuario + +* la CLI +* la interfaz de línea de comandos + +* el servidor +* el cliente + +* el proveedor en la nube +* el servicio en la nube + +* el desarrollo +* las etapas de desarrollo + +* el dict +* el diccionario +* la enumeración +* el enum +* el miembro del enum + +* el codificador +* el decodificador +* codificar +* decodificar + +* la excepción +* lanzar + +* la expresión +* el statement + +* el frontend +* el backend + +* la discusión de GitHub +* el issue de GitHub + +* el rendimiento +* la optimización de rendimiento + +* el tipo de retorno +* el valor de retorno + +* la seguridad +* el esquema de seguridad + +* la tarea +* la tarea en segundo plano +* la función de tarea + +* la plantilla +* el motor de plantillas + +* la anotación de tipos +* la anotación de tipos + +* el worker del servidor +* el worker de Uvicorn +* el Gunicorn Worker +* el worker process +* la worker class +* la carga de trabajo + +* el despliegue +* desplegar + +* el SDK +* el kit de desarrollo de software + +* el `APIRouter` +* el `requirements.txt` +* el Bearer Token +* el cambio incompatible +* el bug +* el botón +* el invocable +* el código +* el commit +* el context manager +* la corrutina +* la sesión de base de datos +* el disco +* el dominio +* el motor +* el X falso +* el método HTTP GET +* el ítem +* el paquete +* el lifespan +* el bloqueo +* el middleware +* la aplicación móvil +* el módulo +* el montaje +* la red +* el origen +* el override +* el payload +* el procesador +* la propiedad +* el proxy +* el pull request +* la query +* la RAM +* la máquina remota +* el código de estado +* el string +* la etiqueta +* el framework web +* el comodín +* devolver +* validar + +//// + +//// tab | Información + +Esta es una lista no completa y no normativa de términos (mayormente) técnicos vistos en la documentación. Puede ayudar a la persona que diseña el prompt a identificar para qué términos el LLM necesita una mano. Por ejemplo cuando sigue revirtiendo una buena traducción a una traducción subóptima. O cuando tiene problemas conjugando/declinando un término en tu idioma. + +Mira p. ej. la sección `### List of English terms and their preferred German translations` en `docs/de/llm-prompt.md`. + +//// diff --git a/docs/es/docs/deployment/fastapicloud.md b/docs/es/docs/deployment/fastapicloud.md new file mode 100644 index 0000000000..af3e7ce680 --- /dev/null +++ b/docs/es/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +Puedes desplegar tu app de FastAPI en FastAPI Cloud con un solo comando; ve y únete a la lista de espera si aún no lo has hecho. 🚀 + +## Iniciar sesión { #login } + +Asegúrate de que ya tienes una cuenta de **FastAPI Cloud** (te invitamos desde la lista de espera 😉). + +Luego inicia sesión: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## Desplegar { #deploy } + +Ahora despliega tu app, con un solo comando: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +¡Eso es todo! Ahora puedes acceder a tu app en esa URL. ✨ + +## Acerca de FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** está creado por el mismo autor y equipo detrás de **FastAPI**. + +Agiliza el proceso de **crear**, **desplegar** y **acceder** a una API con el mínimo esfuerzo. + +Aporta la misma **experiencia de desarrollador** de crear apps con FastAPI al **desplegarlas** en la nube. 🎉 + +También se encargará de la mayoría de las cosas que necesitas al desplegar una app, como: + +* HTTPS +* Replicación, con autoescalado basado en requests +* etc. + +FastAPI Cloud es el sponsor principal y proveedor de financiación de los proyectos open source de *FastAPI and friends*. ✨ + +## Desplegar en otros proveedores de la nube { #deploy-to-other-cloud-providers } + +FastAPI es open source y está basado en estándares. Puedes desplegar apps de FastAPI en cualquier proveedor de la nube que elijas. + +Sigue las guías de tu proveedor de la nube para desplegar apps de FastAPI con ellos. 🤓 + +## Despliega tu propio servidor { #deploy-your-own-server } + +También te enseñaré más adelante en esta guía de **Despliegue** todos los detalles, para que puedas entender qué está pasando, qué tiene que ocurrir o cómo desplegar apps de FastAPI por tu cuenta, también con tus propios servidores. 🤓 diff --git a/docs/es/docs/how-to/authentication-error-status-code.md b/docs/es/docs/how-to/authentication-error-status-code.md new file mode 100644 index 0000000000..9fff6c93d3 --- /dev/null +++ b/docs/es/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# Usar los códigos de estado antiguos 403 para errores de autenticación { #use-old-403-authentication-error-status-codes } + +Antes de FastAPI versión `0.122.0`, cuando las utilidades de seguridad integradas devolvían un error al cliente después de una autenticación fallida, usaban el código de estado HTTP `403 Forbidden`. + +A partir de FastAPI versión `0.122.0`, usan el código de estado HTTP `401 Unauthorized`, más apropiado, y devuelven un `WWW-Authenticate` header adecuado en la response, siguiendo las especificaciones HTTP, RFC 7235, RFC 9110. + +Pero si por alguna razón tus clientes dependen del comportamiento anterior, puedes volver a él sobrescribiendo el método `make_not_authenticated_error` en tus clases de seguridad. + +Por ejemplo, puedes crear una subclase de `HTTPBearer` que devuelva un error `403 Forbidden` en lugar del `401 Unauthorized` por defecto: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} + +/// tip | Consejo + +Ten en cuenta que la función devuelve la instance de la excepción, no la lanza. El lanzamiento se hace en el resto del código interno. + +/// diff --git a/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md new file mode 100644 index 0000000000..deda9f2e59 --- /dev/null +++ b/docs/es/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -0,0 +1,133 @@ +# Migra de Pydantic v1 a Pydantic v2 { #migrate-from-pydantic-v1-to-pydantic-v2 } + +Si tienes una app de FastAPI antigua, podrías estar usando Pydantic versión 1. + +FastAPI ha tenido compatibilidad con Pydantic v1 o v2 desde la versión 0.100.0. + +Si tenías instalado Pydantic v2, lo usaba. Si en cambio tenías Pydantic v1, usaba ese. + +Pydantic v1 está deprecado y su soporte se eliminará en las próximas versiones de FastAPI, deberías migrar a Pydantic v2. Así obtendrás las funcionalidades, mejoras y correcciones más recientes. + +/// warning | Advertencia + +Además, el equipo de Pydantic dejó de dar soporte a Pydantic v1 para las versiones más recientes de Python, comenzando con Python 3.14. + +Si quieres usar las funcionalidades más recientes de Python, tendrás que asegurarte de usar Pydantic v2. + +/// + +Si tienes una app de FastAPI antigua con Pydantic v1, aquí te muestro cómo migrarla a Pydantic v2 y las nuevas funcionalidades en FastAPI 0.119.0 para ayudarte con una migración gradual. + +## Guía oficial { #official-guide } + +Pydantic tiene una Guía de migración oficial de v1 a v2. + +También incluye qué cambió, cómo las validaciones ahora son más correctas y estrictas, posibles consideraciones, etc. + +Puedes leerla para entender mejor qué cambió. + +## Tests { #tests } + +Asegúrate de tener [tests](../tutorial/testing.md){.internal-link target=_blank} para tu app y de ejecutarlos en integración continua (CI). + +Así podrás hacer la actualización y asegurarte de que todo sigue funcionando como esperas. + +## `bump-pydantic` { #bump-pydantic } + +En muchos casos, cuando usas modelos de Pydantic normales sin personalizaciones, podrás automatizar gran parte del proceso de migración de Pydantic v1 a Pydantic v2. + +Puedes usar `bump-pydantic` del mismo equipo de Pydantic. + +Esta herramienta te ayudará a cambiar automáticamente la mayor parte del código que necesita cambiarse. + +Después de esto, puedes ejecutar los tests y revisa si todo funciona. Si es así, ya terminaste. 😎 + +## Pydantic v1 en v2 { #pydantic-v1-in-v2 } + +Pydantic v2 incluye todo lo de Pydantic v1 como un submódulo `pydantic.v1`. + +Esto significa que puedes instalar la versión más reciente de Pydantic v2 e importar y usar los componentes viejos de Pydantic v1 desde ese submódulo, como si tuvieras instalado el Pydantic v1 antiguo. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *} + +### Compatibilidad de FastAPI con Pydantic v1 en v2 { #fastapi-support-for-pydantic-v1-in-v2 } + +Desde FastAPI 0.119.0, también hay compatibilidad parcial para Pydantic v1 desde dentro de Pydantic v2, para facilitar la migración a v2. + +Así que podrías actualizar Pydantic a la última versión 2 y cambiar los imports para usar el submódulo `pydantic.v1`, y en muchos casos simplemente funcionaría. + +{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *} + +/// warning | Advertencia + +Ten en cuenta que, como el equipo de Pydantic ya no da soporte a Pydantic v1 en versiones recientes de Python, empezando por Python 3.14, usar `pydantic.v1` tampoco está soportado en Python 3.14 y superiores. + +/// + +### Pydantic v1 y v2 en la misma app { #pydantic-v1-and-v2-on-the-same-app } + +No está soportado por Pydantic tener un modelo de Pydantic v2 con sus propios campos definidos como modelos de Pydantic v1 o viceversa. + +```mermaid +graph TB + subgraph "❌ Not Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V1Field["Pydantic v1 Model"] + end + subgraph V1["Pydantic v1 Model"] + V2Field["Pydantic v2 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +...pero puedes tener modelos separados usando Pydantic v1 y v2 en la misma app. + +```mermaid +graph TB + subgraph "✅ Supported" + direction TB + subgraph V2["Pydantic v2 Model"] + V2Field["Pydantic v2 Model"] + end + subgraph V1["Pydantic v1 Model"] + V1Field["Pydantic v1 Model"] + end + end + + style V2 fill:#f9fff3 + style V1 fill:#fff6f0 + style V1Field fill:#fff6f0 + style V2Field fill:#f9fff3 +``` + +En algunos casos, incluso es posible tener modelos de Pydantic v1 y v2 en la misma path operation de tu app de FastAPI: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *} + +En el ejemplo anterior, el modelo de entrada es un modelo de Pydantic v1 y el modelo de salida (definido en `response_model=ItemV2`) es un modelo de Pydantic v2. + +### Parámetros de Pydantic v1 { #pydantic-v1-parameters } + +Si necesitas usar algunas de las herramientas específicas de FastAPI para parámetros como `Body`, `Query`, `Form`, etc. con modelos de Pydantic v1, puedes importarlas de `fastapi.temp_pydantic_v1_params` mientras terminas la migración a Pydantic v2: + +{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *} + +### Migra por pasos { #migrate-in-steps } + +/// tip | Consejo + +Primero prueba con `bump-pydantic`; si tus tests pasan y eso funciona, entonces terminaste con un solo comando. ✨ + +/// + +Si `bump-pydantic` no funciona para tu caso, puedes usar la compatibilidad de modelos Pydantic v1 y v2 en la misma app para hacer la migración a Pydantic v2 de forma gradual. + +Podrías primero actualizar Pydantic para usar la última versión 2 y cambiar los imports para usar `pydantic.v1` para todos tus modelos. + +Luego puedes empezar a migrar tus modelos de Pydantic v1 a v2 por grupos, en pasos graduales. 🚶 From f43cba7e7848cbae194e0a4f50b92f48b4b1a88b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 16:16:56 +0000 Subject: [PATCH 073/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 0bbbdc3f54..1625b4e2e8 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Update translations for es (add-missing). PR [#14533](https://github.com/fastapi/fastapi/pull/14533) by [@tiangolo](https://github.com/tiangolo). * 🌐 Remove translations for removed docs. PR [#14516](https://github.com/fastapi/fastapi/pull/14516) by [@tiangolo](https://github.com/tiangolo). ### Internal From da83d795464f479885c68029fb6233a79bc6df05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 08:33:45 -0800 Subject: [PATCH 074/140] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20es=20(update-outdated)=20(#14532)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/es/docs/about/index.md | 2 +- docs/es/docs/advanced/additional-responses.md | 20 +- .../docs/advanced/additional-status-codes.md | 6 +- .../es/docs/advanced/advanced-dependencies.md | 110 ++++++++- docs/es/docs/advanced/async-tests.md | 18 +- docs/es/docs/advanced/behind-a-proxy.md | 145 +++++++++-- docs/es/docs/advanced/custom-response.md | 46 ++-- docs/es/docs/advanced/dataclasses.md | 18 +- docs/es/docs/advanced/generate-clients.md | 167 +++++-------- docs/es/docs/advanced/index.md | 21 +- docs/es/docs/advanced/openapi-callbacks.md | 46 ++-- docs/es/docs/advanced/openapi-webhooks.md | 10 +- .../path-operation-advanced-configuration.md | 46 ++-- .../advanced/response-change-status-code.md | 8 +- docs/es/docs/advanced/response-directly.md | 14 +- .../docs/advanced/security/http-basic-auth.md | 18 +- docs/es/docs/advanced/security/index.md | 8 +- docs/es/docs/advanced/settings.md | 46 ++-- docs/es/docs/advanced/sub-applications.md | 14 +- docs/es/docs/advanced/testing-dependencies.md | 8 +- docs/es/docs/advanced/testing-events.md | 11 +- docs/es/docs/advanced/wsgi.md | 6 +- docs/es/docs/benchmarks.md | 4 +- docs/es/docs/deployment/cloud.md | 19 +- docs/es/docs/deployment/concepts.md | 62 ++--- docs/es/docs/deployment/docker.md | 88 +++---- docs/es/docs/deployment/https.md | 64 +++-- docs/es/docs/deployment/index.md | 8 +- docs/es/docs/deployment/server-workers.md | 85 +++---- docs/es/docs/deployment/versions.md | 14 +- docs/es/docs/environment-variables.md | 14 +- docs/es/docs/how-to/conditional-openapi.md | 12 +- docs/es/docs/how-to/configure-swagger-ui.md | 14 +- docs/es/docs/how-to/custom-docs-ui-assets.md | 42 ++-- .../docs/how-to/custom-request-and-route.md | 26 +- docs/es/docs/how-to/extending-openapi.md | 18 +- docs/es/docs/how-to/general.md | 20 +- docs/es/docs/how-to/graphql.md | 12 +- docs/es/docs/how-to/index.md | 4 +- .../docs/how-to/separate-openapi-schemas.md | 24 +- docs/es/docs/how-to/testing-database.md | 2 +- docs/es/docs/index.md | 141 ++++++++--- docs/es/docs/learn/index.md | 2 +- docs/es/docs/project-generation.md | 12 +- docs/es/docs/python-types.md | 46 ++-- docs/es/docs/resources/index.md | 4 +- docs/es/docs/tutorial/bigger-applications.md | 126 +++------- docs/es/docs/tutorial/body-fields.md | 10 +- docs/es/docs/tutorial/body-multiple-params.md | 18 +- docs/es/docs/tutorial/body-nested-models.md | 32 +-- docs/es/docs/tutorial/body-updates.md | 14 +- docs/es/docs/tutorial/body.md | 34 ++- docs/es/docs/tutorial/cookie-param-models.md | 12 +- docs/es/docs/tutorial/cookie-params.md | 18 +- docs/es/docs/tutorial/cors.md | 21 +- docs/es/docs/tutorial/debugging.md | 10 +- .../dependencies/classes-as-dependencies.md | 14 +- ...pendencies-in-path-operation-decorators.md | 16 +- .../dependencies/dependencies-with-yield.md | 101 ++++---- .../dependencies/global-dependencies.md | 4 +- docs/es/docs/tutorial/dependencies/index.md | 30 +-- .../tutorial/dependencies/sub-dependencies.md | 12 +- docs/es/docs/tutorial/encoder.md | 6 +- docs/es/docs/tutorial/extra-data-types.md | 6 +- docs/es/docs/tutorial/extra-models.md | 36 +-- docs/es/docs/tutorial/first-steps.md | 171 ++++++++----- docs/es/docs/tutorial/handling-errors.md | 65 +++-- docs/es/docs/tutorial/header-param-models.md | 26 +- docs/es/docs/tutorial/header-params.md | 12 +- docs/es/docs/tutorial/index.md | 75 +++--- docs/es/docs/tutorial/metadata.md | 20 +- .../tutorial/path-operation-configuration.md | 18 +- docs/es/docs/tutorial/query-param-models.md | 10 +- .../tutorial/query-params-str-validations.md | 225 +++++++++--------- docs/es/docs/tutorial/request-files.md | 24 +- docs/es/docs/tutorial/request-form-models.md | 10 +- .../docs/tutorial/request-forms-and-files.md | 8 +- docs/es/docs/tutorial/request-forms.md | 22 +- docs/es/docs/tutorial/response-model.md | 52 ++-- docs/es/docs/tutorial/response-status-code.md | 22 +- docs/es/docs/tutorial/schema-extra-example.md | 30 +-- docs/es/docs/tutorial/security/first-steps.md | 20 +- .../tutorial/security/get-current-user.md | 18 +- docs/es/docs/tutorial/security/oauth2-jwt.md | 52 ++-- .../docs/tutorial/security/simple-oauth2.md | 36 +-- docs/es/docs/tutorial/sql-databases.md | 61 +++-- docs/es/docs/tutorial/testing.md | 74 +----- docs/es/docs/virtual-environments.md | 68 ++++-- docs/es/llm-prompt.md | 1 + 89 files changed, 1694 insertions(+), 1471 deletions(-) diff --git a/docs/es/docs/about/index.md b/docs/es/docs/about/index.md index e83400a8dc..fa152c62db 100644 --- a/docs/es/docs/about/index.md +++ b/docs/es/docs/about/index.md @@ -1,3 +1,3 @@ -# Acerca de +# Acerca de { #about } Acerca de FastAPI, su diseño, inspiración y más. 🤓 diff --git a/docs/es/docs/advanced/additional-responses.md b/docs/es/docs/advanced/additional-responses.md index 7788bccd97..08f0a080b5 100644 --- a/docs/es/docs/advanced/additional-responses.md +++ b/docs/es/docs/advanced/additional-responses.md @@ -1,4 +1,4 @@ -# Responses Adicionales en OpenAPI +# Responses Adicionales en OpenAPI { #additional-responses-in-openapi } /// warning | Advertencia @@ -14,7 +14,7 @@ Esos responses adicionales se incluirán en el esquema de OpenAPI, por lo que ta Pero para esos responses adicionales tienes que asegurarte de devolver un `Response` como `JSONResponse` directamente, con tu código de estado y contenido. -## Response Adicional con `model` +## Response Adicional con `model` { #additional-response-with-model } Puedes pasar a tus *decoradores de path operation* un parámetro `responses`. @@ -169,13 +169,13 @@ Los esquemas se referencian a otro lugar dentro del esquema de OpenAPI: } ``` -## Media types adicionales para el response principal +## Media types adicionales para el response principal { #additional-media-types-for-the-main-response } Puedes usar este mismo parámetro `responses` para agregar diferentes media type para el mismo response principal. Por ejemplo, puedes agregar un media type adicional de `image/png`, declarando que tu *path operation* puede devolver un objeto JSON (con media type `application/json`) o una imagen PNG: -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} /// note | Nota @@ -191,13 +191,13 @@ Pero si has especificado una clase de response personalizada con `None` como su /// -## Combinando información +## Combinando información { #combining-information } También puedes combinar información de response de múltiples lugares, incluyendo los parámetros `response_model`, `status_code`, y `responses`. -Puedes declarar un `response_model`, usando el código de estado predeterminado `200` (o uno personalizado si lo necesitas), y luego declarar información adicional para ese mismo response en `responses`, directamente en el esquema de OpenAPI. +Puedes declarar un `response_model`, usando el código de estado por defecto `200` (o uno personalizado si lo necesitas), y luego declarar información adicional para ese mismo response en `responses`, directamente en el esquema de OpenAPI. -**FastAPI** manterá la información adicional de `responses` y la combinará con el JSON Schema de tu modelo. +**FastAPI** mantendrá la información adicional de `responses` y la combinará con el JSON Schema de tu modelo. Por ejemplo, puedes declarar un response con un código de estado `404` que usa un modelo Pydantic y tiene una `description` personalizada. @@ -209,7 +209,7 @@ Todo se combinará e incluirá en tu OpenAPI, y se mostrará en la documentació -## Combina responses predefinidos y personalizados +## Combina responses predefinidos y personalizados { #combine-predefined-responses-and-custom-ones } Es posible que desees tener algunos responses predefinidos que se apliquen a muchas *path operations*, pero que quieras combinarlos con responses personalizados necesarios por cada *path operation*. @@ -237,9 +237,9 @@ Puedes usar esa técnica para reutilizar algunos responses predefinidos en tus * Por ejemplo: -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} -## Más información sobre responses OpenAPI +## Más información sobre responses OpenAPI { #more-information-about-openapi-responses } Para ver exactamente qué puedes incluir en los responses, puedes revisar estas secciones en la especificación OpenAPI: diff --git a/docs/es/docs/advanced/additional-status-codes.md b/docs/es/docs/advanced/additional-status-codes.md index df7737aacc..9adfa65cf3 100644 --- a/docs/es/docs/advanced/additional-status-codes.md +++ b/docs/es/docs/advanced/additional-status-codes.md @@ -1,10 +1,10 @@ -# Códigos de Estado Adicionales +# Códigos de Estado Adicionales { #additional-status-codes } Por defecto, **FastAPI** devolverá los responses usando un `JSONResponse`, colocando el contenido que devuelves desde tu *path operation* dentro de ese `JSONResponse`. Usará el código de estado por defecto o el que configures en tu *path operation*. -## Códigos de estado adicionales +## Códigos de estado adicionales { #additional-status-codes_1 } Si quieres devolver códigos de estado adicionales aparte del principal, puedes hacerlo devolviendo un `Response` directamente, como un `JSONResponse`, y configurando el código de estado adicional directamente. @@ -34,7 +34,7 @@ También podrías usar `from starlette.responses import JSONResponse`. /// -## OpenAPI y documentación de API +## OpenAPI y documentación de API { #openapi-and-api-docs } Si devuelves códigos de estado adicionales y responses directamente, no se incluirán en el esquema de OpenAPI (la documentación de la API), porque FastAPI no tiene una forma de saber de antemano qué vas a devolver. diff --git a/docs/es/docs/advanced/advanced-dependencies.md b/docs/es/docs/advanced/advanced-dependencies.md index dd3c63a37b..622a2caa26 100644 --- a/docs/es/docs/advanced/advanced-dependencies.md +++ b/docs/es/docs/advanced/advanced-dependencies.md @@ -1,6 +1,6 @@ -# Dependencias Avanzadas +# Dependencias Avanzadas { #advanced-dependencies } -## Dependencias con parámetros +## Dependencias con parámetros { #parameterized-dependencies } Todas las dependencias que hemos visto son una función o clase fija. @@ -10,7 +10,7 @@ Imaginemos que queremos tener una dependencia que revise si el parámetro de que Pero queremos poder parametrizar ese contenido fijo. -## Una *instance* "callable" +## Una *instance* "callable" { #a-callable-instance } En Python hay una forma de hacer que una instance de una clase sea un "callable". @@ -22,7 +22,7 @@ Para hacer eso, declaramos un método `__call__`: En este caso, este `__call__` es lo que **FastAPI** usará para comprobar parámetros adicionales y sub-dependencias, y es lo que llamará para pasar un valor al parámetro en tu *path operation function* más adelante. -## Parametrizar la instance +## Parametrizar la instance { #parameterize-the-instance } Y ahora, podemos usar `__init__` para declarar los parámetros de la instance que podemos usar para "parametrizar" la dependencia: @@ -30,7 +30,7 @@ Y ahora, podemos usar `__init__` para declarar los parámetros de la instance qu En este caso, **FastAPI** nunca tocará ni se preocupará por `__init__`, lo usaremos directamente en nuestro código. -## Crear una instance +## Crear una instance { #create-an-instance } Podríamos crear una instance de esta clase con: @@ -38,7 +38,7 @@ Podríamos crear una instance de esta clase con: Y de esa manera podemos "parametrizar" nuestra dependencia, que ahora tiene `"bar"` dentro de ella, como el atributo `checker.fixed_content`. -## Usar la instance como una dependencia +## Usar la instance como una dependencia { #use-the-instance-as-a-dependency } Luego, podríamos usar este `checker` en un `Depends(checker)`, en lugar de `Depends(FixedContentQueryChecker)`, porque la dependencia es la instance, `checker`, no la clase en sí. @@ -63,3 +63,101 @@ En los capítulos sobre seguridad, hay funciones utilitarias que se implementan Si entendiste todo esto, ya sabes cómo funcionan por debajo esas herramientas de utilidad para seguridad. /// + +## Dependencias con `yield`, `HTTPException`, `except` y Tareas en segundo plano { #dependencies-with-yield-httpexception-except-and-background-tasks } + +/// warning | Advertencia + +Muy probablemente no necesites estos detalles técnicos. + +Estos detalles son útiles principalmente si tenías una aplicación de FastAPI anterior a la 0.121.0 y estás enfrentando problemas con dependencias con `yield`. + +/// + +Las dependencias con `yield` han evolucionado con el tiempo para cubrir diferentes casos de uso y arreglar algunos problemas; aquí tienes un resumen de lo que ha cambiado. + +### Dependencias con `yield` y `scope` { #dependencies-with-yield-and-scope } + +En la versión 0.121.0, FastAPI agregó soporte para `Depends(scope="function")` para dependencias con `yield`. + +Usando `Depends(scope="function")`, el código de salida después de `yield` se ejecuta justo después de que la *path operation function* termina, antes de que la response se envíe de vuelta al cliente. + +Y al usar `Depends(scope="request")` (el valor por defecto), el código de salida después de `yield` se ejecuta después de que la response es enviada. + +Puedes leer más al respecto en la documentación de [Dependencias con `yield` - Salida temprana y `scope`](../tutorial/dependencies/dependencies-with-yield.md#early-exit-and-scope). + +### Dependencias con `yield` y `StreamingResponse`, detalles técnicos { #dependencies-with-yield-and-streamingresponse-technical-details } + +Antes de FastAPI 0.118.0, si usabas una dependencia con `yield`, ejecutaba el código de salida después de que la *path operation function* retornaba pero justo antes de enviar la response. + +La intención era evitar retener recursos por más tiempo del necesario, esperando a que la response viajara por la red. + +Este cambio también significaba que si retornabas un `StreamingResponse`, el código de salida de la dependencia con `yield` ya se habría ejecutado. + +Por ejemplo, si tenías una sesión de base de datos en una dependencia con `yield`, el `StreamingResponse` no podría usar esa sesión mientras hace streaming de datos porque la sesión ya se habría cerrado en el código de salida después de `yield`. + +Este comportamiento se revirtió en la 0.118.0, para hacer que el código de salida después de `yield` se ejecute después de que la response sea enviada. + +/// info | Información + +Como verás abajo, esto es muy similar al comportamiento anterior a la versión 0.106.0, pero con varias mejoras y arreglos de bugs para casos límite. + +/// + +#### Casos de uso con salida temprana del código { #use-cases-with-early-exit-code } + +Hay algunos casos de uso con condiciones específicas que podrían beneficiarse del comportamiento antiguo de ejecutar el código de salida de dependencias con `yield` antes de enviar la response. + +Por ejemplo, imagina que tienes código que usa una sesión de base de datos en una dependencia con `yield` solo para verificar un usuario, pero la sesión de base de datos no se vuelve a usar en la *path operation function*, solo en la dependencia, y la response tarda mucho en enviarse, como un `StreamingResponse` que envía datos lentamente, pero que por alguna razón no usa la base de datos. + +En este caso, la sesión de base de datos se mantendría hasta que la response termine de enviarse, pero si no la usas, entonces no sería necesario mantenerla. + +Así es como se vería: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py *} + +El código de salida, el cierre automático de la `Session` en: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[19:21] *} + +...se ejecutaría después de que la response termine de enviar los datos lentos: + +{* ../../docs_src/dependencies/tutorial013_an_py310.py ln[30:38] hl[31:33] *} + +Pero como `generate_stream()` no usa la sesión de base de datos, no es realmente necesario mantener la sesión abierta mientras se envía la response. + +Si tienes este caso de uso específico usando SQLModel (o SQLAlchemy), podrías cerrar explícitamente la sesión después de que ya no la necesites: + +{* ../../docs_src/dependencies/tutorial014_an_py310.py ln[24:28] hl[28] *} + +De esa manera la sesión liberaría la conexión a la base de datos, para que otras requests puedan usarla. + +Si tienes un caso de uso diferente que necesite salir temprano desde una dependencia con `yield`, por favor crea una Pregunta de Discusión en GitHub con tu caso de uso específico y por qué te beneficiaría tener cierre temprano para dependencias con `yield`. + +Si hay casos de uso convincentes para el cierre temprano en dependencias con `yield`, consideraría agregar una nueva forma de optar por el cierre temprano. + +### Dependencias con `yield` y `except`, detalles técnicos { #dependencies-with-yield-and-except-technical-details } + +Antes de FastAPI 0.110.0, si usabas una dependencia con `yield`, y luego capturabas una excepción con `except` en esa dependencia, y no volvías a elevar la excepción, la excepción se elevaría/remitiría automáticamente a cualquier manejador de excepciones o al manejador de error interno del servidor. + +Esto cambió en la versión 0.110.0 para arreglar consumo de memoria no manejado por excepciones reenviadas sin un manejador (errores internos del servidor), y para hacerlo consistente con el comportamiento del código Python normal. + +### Tareas en segundo plano y dependencias con `yield`, detalles técnicos { #background-tasks-and-dependencies-with-yield-technical-details } + +Antes de FastAPI 0.106.0, elevar excepciones después de `yield` no era posible, el código de salida en dependencias con `yield` se ejecutaba después de que la response era enviada, por lo que [Manejadores de Excepciones](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} ya habrían corrido. + +Esto se diseñó así principalmente para permitir usar los mismos objetos devueltos con `yield` por las dependencias dentro de tareas en segundo plano, porque el código de salida se ejecutaría después de que las tareas en segundo plano terminaran. + +Esto cambió en FastAPI 0.106.0 con la intención de no retener recursos mientras se espera a que la response viaje por la red. + +/// tip | Consejo + +Adicionalmente, una tarea en segundo plano normalmente es un conjunto independiente de lógica que debería manejarse por separado, con sus propios recursos (por ejemplo, su propia conexión a la base de datos). + +Así, probablemente tendrás un código más limpio. + +/// + +Si solías depender de este comportamiento, ahora deberías crear los recursos para las tareas en segundo plano dentro de la propia tarea en segundo plano, y usar internamente solo datos que no dependan de los recursos de dependencias con `yield`. + +Por ejemplo, en lugar de usar la misma sesión de base de datos, crearías una nueva sesión de base de datos dentro de la tarea en segundo plano, y obtendrías los objetos de la base de datos usando esta nueva sesión. Y entonces, en lugar de pasar el objeto de la base de datos como parámetro a la función de la tarea en segundo plano, pasarías el ID de ese objeto y luego obtendrías el objeto de nuevo dentro de la función de la tarea en segundo plano. diff --git a/docs/es/docs/advanced/async-tests.md b/docs/es/docs/advanced/async-tests.md index f89db15335..3a663d50ea 100644 --- a/docs/es/docs/advanced/async-tests.md +++ b/docs/es/docs/advanced/async-tests.md @@ -1,4 +1,4 @@ -# Tests Asíncronos +# Tests Asíncronos { #async-tests } Ya has visto cómo probar tus aplicaciones de **FastAPI** usando el `TestClient` proporcionado. Hasta ahora, solo has visto cómo escribir tests sincrónicos, sin usar funciones `async`. @@ -6,11 +6,11 @@ Poder usar funciones asíncronas en tus tests puede ser útil, por ejemplo, cuan Veamos cómo podemos hacer que esto funcione. -## pytest.mark.anyio +## pytest.mark.anyio { #pytest-mark-anyio } Si queremos llamar funciones asíncronas en nuestros tests, nuestras funciones de test tienen que ser asíncronas. AnyIO proporciona un plugin útil para esto, que nos permite especificar que algunas funciones de test deben ser llamadas de manera asíncrona. -## HTTPX +## HTTPX { #httpx } Incluso si tu aplicación de **FastAPI** usa funciones `def` normales en lugar de `async def`, sigue siendo una aplicación `async` por debajo. @@ -18,7 +18,7 @@ El `TestClient` hace algo de magia interna para llamar a la aplicación FastAPI El `TestClient` está basado en HTTPX, y afortunadamente, podemos usarlo directamente para probar la API. -## Ejemplo +## Ejemplo { #example } Para un ejemplo simple, consideremos una estructura de archivos similar a la descrita en [Aplicaciones Más Grandes](../tutorial/bigger-applications.md){.internal-link target=_blank} y [Testing](../tutorial/testing.md){.internal-link target=_blank}: @@ -38,7 +38,7 @@ El archivo `test_main.py` tendría los tests para `main.py`, podría verse así {* ../../docs_src/async_tests/test_main.py *} -## Ejecútalo +## Ejecútalo { #run-it } Puedes ejecutar tus tests como de costumbre vía: @@ -52,7 +52,7 @@ $ pytest -## En Detalle +## En Detalle { #in-detail } El marcador `@pytest.mark.anyio` le dice a pytest que esta función de test debe ser llamada asíncronamente: @@ -60,7 +60,7 @@ El marcador `@pytest.mark.anyio` le dice a pytest que esta función de test debe /// tip | Consejo -Note que la función de test ahora es `async def` en lugar de solo `def` como antes al usar el `TestClient`. +Nota que la función de test ahora es `async def` en lugar de solo `def` como antes al usar el `TestClient`. /// @@ -88,12 +88,12 @@ Si tu aplicación depende de eventos de lifespan, el `AsyncClient` no activará /// -## Otras Llamadas a Funciones Asíncronas +## Otras Llamadas a Funciones Asíncronas { #other-asynchronous-function-calls } Al ser la función de test asíncrona, ahora también puedes llamar (y `await`) otras funciones `async` además de enviar requests a tu aplicación FastAPI en tus tests, exactamente como las llamarías en cualquier otro lugar de tu código. /// tip | Consejo -Si encuentras un `RuntimeError: Task attached to a different loop` al integrar llamadas a funciones asíncronas en tus tests (por ejemplo, cuando usas MotorClient de MongoDB), recuerda crear instances de objetos que necesiten un loop de eventos solo dentro de funciones async, por ejemplo, en un callback `'@app.on_event("startup")`. +Si encuentras un `RuntimeError: Task attached to a different loop` al integrar llamadas a funciones asíncronas en tus tests (por ejemplo, cuando usas MotorClient de MongoDB), recuerda crear instances de objetos que necesiten un loop de eventos solo dentro de funciones async, por ejemplo, en un callback `@app.on_event("startup")`. /// diff --git a/docs/es/docs/advanced/behind-a-proxy.md b/docs/es/docs/advanced/behind-a-proxy.md index 8c590cbe82..22f51a8fa2 100644 --- a/docs/es/docs/advanced/behind-a-proxy.md +++ b/docs/es/docs/advanced/behind-a-proxy.md @@ -1,6 +1,105 @@ -# Detrás de un Proxy +# Detrás de un Proxy { #behind-a-proxy } -En algunas situaciones, podrías necesitar usar un **proxy** como Traefik o Nginx con una configuración que añade un prefijo de path extra que no es visto por tu aplicación. +En muchas situaciones, usarías un **proxy** como Traefik o Nginx delante de tu app de FastAPI. + +Estos proxies podrían manejar certificados HTTPS y otras cosas. + +## Headers reenviados por el Proxy { #proxy-forwarded-headers } + +Un **proxy** delante de tu aplicación normalmente establecería algunos headers sobre la marcha antes de enviar los requests a tu **server** para que el servidor sepa que el request fue **reenviado** por el proxy, informándole la URL original (pública), incluyendo el dominio, que está usando HTTPS, etc. + +El programa **server** (por ejemplo **Uvicorn** a través de **FastAPI CLI**) es capaz de interpretar esos headers, y luego pasar esa información a tu aplicación. + +Pero por seguridad, como el server no sabe que está detrás de un proxy confiable, no interpretará esos headers. + +/// note | Detalles Técnicos + +Los headers del proxy son: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +### Habilitar headers reenviados por el Proxy { #enable-proxy-forwarded-headers } + +Puedes iniciar FastAPI CLI con la *Opción de CLI* `--forwarded-allow-ips` y pasar las direcciones IP que deberían ser confiables para leer esos headers reenviados. + +Si lo estableces a `--forwarded-allow-ips="*"`, confiaría en todas las IPs entrantes. + +Si tu **server** está detrás de un **proxy** confiable y solo el proxy le habla, esto haría que acepte cualquiera que sea la IP de ese **proxy**. + +
+ +```console +$ fastapi run --forwarded-allow-ips="*" + +INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) +``` + +
+ +### Redirecciones con HTTPS { #redirects-with-https } + +Por ejemplo, digamos que defines una *path operation* `/items/`: + +{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} + +Si el cliente intenta ir a `/items`, por defecto, sería redirigido a `/items/`. + +Pero antes de configurar la *Opción de CLI* `--forwarded-allow-ips` podría redirigir a `http://localhost:8000/items/`. + +Pero quizá tu aplicación está alojada en `https://mysuperapp.com`, y la redirección debería ser a `https://mysuperapp.com/items/`. + +Al configurar `--proxy-headers` ahora FastAPI podrá redirigir a la ubicación correcta. 😎 + +``` +https://mysuperapp.com/items/ +``` + +/// tip | Consejo + +Si quieres aprender más sobre HTTPS, revisa la guía [Acerca de HTTPS](../deployment/https.md){.internal-link target=_blank}. + +/// + +### Cómo funcionan los headers reenviados por el Proxy { #how-proxy-forwarded-headers-work } + +Aquí tienes una representación visual de cómo el **proxy** añade headers reenviados entre el cliente y el **application server**: + +```mermaid +sequenceDiagram + participant Client as Cliente + participant Proxy as Proxy/Load Balancer + participant Server as Servidor de FastAPI + + Client->>Proxy: HTTPS Request
Host: mysuperapp.com
Path: /items + + Note over Proxy: El proxy añade headers reenviados + + Proxy->>Server: HTTP Request
X-Forwarded-For: [client IP]
X-Forwarded-Proto: https
X-Forwarded-Host: mysuperapp.com
Path: /items + + Note over Server: El servidor interpreta los headers
(si --forwarded-allow-ips está configurado) + + Server->>Proxy: HTTP Response
con URLs HTTPS correctas + + Proxy->>Client: HTTPS Response +``` + +El **proxy** intercepta el request original del cliente y añade los *headers* especiales de reenvío (`X-Forwarded-*`) antes de pasar el request al **application server**. + +Estos headers preservan información sobre el request original que de otro modo se perdería: + +* **X-Forwarded-For**: La IP original del cliente +* **X-Forwarded-Proto**: El protocolo original (`https`) +* **X-Forwarded-Host**: El host original (`mysuperapp.com`) + +Cuando **FastAPI CLI** está configurado con `--forwarded-allow-ips`, confía en estos headers y los usa, por ejemplo para generar las URLs correctas en redirecciones. + +## Proxy con un prefijo de path eliminado { #proxy-with-a-stripped-path-prefix } + +Podrías tener un proxy que añada un prefijo de path a tu aplicación. En estos casos, puedes usar `root_path` para configurar tu aplicación. @@ -10,8 +109,6 @@ El `root_path` se usa para manejar estos casos específicos. Y también se usa internamente al montar subaplicaciones. -## Proxy con un prefijo de path eliminado - Tener un proxy con un prefijo de path eliminado, en este caso, significa que podrías declarar un path en `/app` en tu código, pero luego añades una capa encima (el proxy) que situaría tu aplicación **FastAPI** bajo un path como `/api/v1`. En este caso, el path original `/app` realmente sería servido en `/api/v1/app`. @@ -66,14 +163,14 @@ La UI de los docs también necesitaría el esquema de OpenAPI para declarar que En este ejemplo, el "Proxy" podría ser algo como **Traefik**. Y el servidor sería algo como FastAPI CLI con **Uvicorn**, ejecutando tu aplicación de FastAPI. -### Proporcionando el `root_path` +### Proporcionando el `root_path` { #providing-the-root-path } Para lograr esto, puedes usar la opción de línea de comandos `--root-path` como:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -90,7 +187,7 @@ Y la opción de línea de comandos `--root-path` proporciona ese `root_path`. /// -### Revisar el `root_path` actual +### Revisar el `root_path` actual { #checking-the-current-root-path } Puedes obtener el `root_path` actual utilizado por tu aplicación para cada request, es parte del diccionario `scope` (que es parte de la especificación ASGI). @@ -103,7 +200,7 @@ Luego, si inicias Uvicorn con:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` @@ -119,7 +216,7 @@ El response sería algo como: } ``` -### Configurar el `root_path` en la app de FastAPI +### Configurar el `root_path` en la app de FastAPI { #setting-the-root-path-in-the-fastapi-app } Alternativamente, si no tienes una forma de proporcionar una opción de línea de comandos como `--root-path` o su equivalente, puedes configurar el parámetro `root_path` al crear tu app de FastAPI: @@ -127,11 +224,11 @@ Alternativamente, si no tienes una forma de proporcionar una opción de línea d Pasar el `root_path` a `FastAPI` sería el equivalente a pasar la opción de línea de comandos `--root-path` a Uvicorn o Hypercorn. -### Acerca de `root_path` +### Acerca de `root_path` { #about-root-path } Ten en cuenta que el servidor (Uvicorn) no usará ese `root_path` para nada, a excepción de pasárselo a la app. -Pero si vas con tu navegador a http://127.0.0.1:8000/app verás el response normal: +Pero si vas con tu navegador a http://127.0.0.1:8000/app verás el response normal: ```JSON { @@ -144,15 +241,15 @@ Así que no se esperará que sea accedido en `http://127.0.0.1:8000/api/v1/app`. Uvicorn esperará que el proxy acceda a Uvicorn en `http://127.0.0.1:8000/app`, y luego será responsabilidad del proxy añadir el prefijo extra `/api/v1` encima. -## Sobre proxies con un prefijo de path eliminado +## Sobre proxies con un prefijo de path eliminado { #about-proxies-with-a-stripped-path-prefix } Ten en cuenta que un proxy con prefijo de path eliminado es solo una de las formas de configurarlo. -Probablemente en muchos casos, el valor predeterminado será que el proxy no tenga un prefijo de path eliminado. +Probablemente en muchos casos, el valor por defecto será que el proxy no tenga un prefijo de path eliminado. En un caso así (sin un prefijo de path eliminado), el proxy escucharía algo como `https://myawesomeapp.com`, y luego si el navegador va a `https://myawesomeapp.com/api/v1/app` y tu servidor (por ejemplo, Uvicorn) escucha en `http://127.0.0.1:8000`, el proxy (sin un prefijo de path eliminado) accedería a Uvicorn en el mismo path: `http://127.0.0.1:8000/api/v1/app`. -## Probando localmente con Traefik +## Probando localmente con Traefik { #testing-locally-with-traefik } Puedes ejecutar fácilmente el experimento localmente con un prefijo de path eliminado usando Traefik. @@ -224,14 +321,14 @@ Y ahora inicia tu app, utilizando la opción `--root-path`:
```console -$ fastapi run main.py --root-path /api/v1 +$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ```
-### Revisa los responses +### Revisa los responses { #check-the-responses } Ahora, si vas a la URL con el puerto para Uvicorn: http://127.0.0.1:8000/app, verás el response normal: @@ -267,7 +364,7 @@ Y la versión sin el prefijo de path (`http://127.0.0.1:8000/app`), proporcionad Eso demuestra cómo el Proxy (Traefik) usa el prefijo de path y cómo el servidor (Uvicorn) usa el `root_path` de la opción `--root-path`. -### Revisa la UI de los docs +### Revisa la UI de los docs { #check-the-docs-ui } Pero aquí está la parte divertida. ✨ @@ -287,7 +384,7 @@ Justo como queríamos. ✔️ Esto es porque FastAPI usa este `root_path` para crear el `server` por defecto en OpenAPI con la URL proporcionada por `root_path`. -## Servidores adicionales +## Servidores adicionales { #additional-servers } /// warning | Advertencia @@ -346,7 +443,15 @@ La UI de los docs interactuará con el server que selecciones. /// -### Desactivar el server automático de `root_path` +/// note | Detalles Técnicos + +La propiedad `servers` en la especificación de OpenAPI es opcional. + +Si no especificas el parámetro `servers` y `root_path` es igual a `/`, la propiedad `servers` en el esquema de OpenAPI generado se omitirá por completo por defecto, lo cual es equivalente a un único server con un valor `url` de `/`. + +/// + +### Desactivar el server automático de `root_path` { #disable-automatic-server-from-root-path } Si no quieres que **FastAPI** incluya un server automático usando el `root_path`, puedes usar el parámetro `root_path_in_servers=False`: @@ -354,7 +459,7 @@ Si no quieres que **FastAPI** incluya un server automático usando el `root_path y entonces no lo incluirá en el esquema de OpenAPI. -## Montando una sub-aplicación +## Montando una sub-aplicación { #mounting-a-sub-application } Si necesitas montar una sub-aplicación (como se describe en [Aplicaciones secundarias - Monturas](sub-applications.md){.internal-link target=_blank}) mientras usas un proxy con `root_path`, puedes hacerlo normalmente, como esperarías. diff --git a/docs/es/docs/advanced/custom-response.md b/docs/es/docs/advanced/custom-response.md index f7bd81bcc7..6ecbbd5032 100644 --- a/docs/es/docs/advanced/custom-response.md +++ b/docs/es/docs/advanced/custom-response.md @@ -1,4 +1,4 @@ -# Response Personalizado - HTML, Stream, Archivo, otros +# Response Personalizado - HTML, Stream, Archivo, otros { #custom-response-html-stream-file-others } Por defecto, **FastAPI** devolverá los responses usando `JSONResponse`. @@ -18,7 +18,7 @@ Si usas una clase de response sin media type, FastAPI esperará que tu response /// -## Usa `ORJSONResponse` +## Usa `ORJSONResponse` { #use-orjsonresponse } Por ejemplo, si estás exprimendo el rendimiento, puedes instalar y usar `orjson` y establecer el response como `ORJSONResponse`. @@ -48,7 +48,7 @@ El `ORJSONResponse` solo está disponible en FastAPI, no en Starlette. /// -## Response HTML +## Response HTML { #html-response } Para devolver un response con HTML directamente desde **FastAPI**, usa `HTMLResponse`. @@ -67,7 +67,7 @@ Y se documentará así en OpenAPI. /// -### Devuelve una `Response` +### Devuelve una `Response` { #return-a-response } Como se ve en [Devolver una Response directamente](response-directly.md){.internal-link target=_blank}, también puedes sobrescribir el response directamente en tu *path operation*, devolviéndolo. @@ -87,13 +87,13 @@ Por supuesto, el `Content-Type` header real, el código de estado, etc., provend /// -### Documenta en OpenAPI y sobrescribe `Response` +### Documenta en OpenAPI y sobrescribe `Response` { #document-in-openapi-and-override-response } Si quieres sobrescribir el response desde dentro de la función pero al mismo tiempo documentar el "media type" en OpenAPI, puedes usar el parámetro `response_class` Y devolver un objeto `Response`. El `response_class` solo se usará para documentar el OpenAPI *path operation*, pero tu `Response` se usará tal cual. -#### Devuelve un `HTMLResponse` directamente +#### Devuelve un `HTMLResponse` directamente { #return-an-htmlresponse-directly } Por ejemplo, podría ser algo así: @@ -101,13 +101,13 @@ Por ejemplo, podría ser algo así: En este ejemplo, la función `generate_html_response()` ya genera y devuelve una `Response` en lugar de devolver el HTML en un `str`. -Al devolver el resultado de llamar a `generate_html_response()`, ya estás devolviendo una `Response` que sobrescribirá el comportamiento predeterminado de **FastAPI**. +Al devolver el resultado de llamar a `generate_html_response()`, ya estás devolviendo una `Response` que sobrescribirá el comportamiento por defecto de **FastAPI**. Pero como pasaste `HTMLResponse` en el `response_class` también, **FastAPI** sabrá cómo documentarlo en OpenAPI y la documentación interactiva como HTML con `text/html`: -## Responses disponibles +## Responses disponibles { #available-responses } Aquí hay algunos de los responses disponibles. @@ -121,7 +121,7 @@ También podrías usar `from starlette.responses import HTMLResponse`. /// -### `Response` +### `Response` { #response } La clase principal `Response`, todos los otros responses heredan de ella. @@ -138,23 +138,23 @@ FastAPI (de hecho Starlette) incluirá automáticamente un header Content-Length {* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} -### `HTMLResponse` +### `HTMLResponse` { #htmlresponse } Toma algún texto o bytes y devuelve un response HTML, como leíste arriba. -### `PlainTextResponse` +### `PlainTextResponse` { #plaintextresponse } Toma algún texto o bytes y devuelve un response de texto plano. {* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} -### `JSONResponse` +### `JSONResponse` { #jsonresponse } Toma algunos datos y devuelve un response codificado como `application/json`. -Este es el response predeterminado usado en **FastAPI**, como leíste arriba. +Este es el response usado por defecto en **FastAPI**, como leíste arriba. -### `ORJSONResponse` +### `ORJSONResponse` { #orjsonresponse } Un response JSON rápido alternativo usando `orjson`, como leíste arriba. @@ -164,7 +164,7 @@ Esto requiere instalar `orjson`, por ejemplo, con `pip install orjson`. /// -### `UJSONResponse` +### `UJSONResponse` { #ujsonresponse } Un response JSON alternativo usando `ujson`. @@ -188,7 +188,7 @@ Es posible que `ORJSONResponse` sea una alternativa más rápida. /// -### `RedirectResponse` +### `RedirectResponse` { #redirectresponse } Devuelve una redirección HTTP. Usa un código de estado 307 (Redirección Temporal) por defecto. @@ -204,7 +204,7 @@ O puedes usarlo en el parámetro `response_class`: Si haces eso, entonces puedes devolver la URL directamente desde tu *path operation function*. -En este caso, el `status_code` utilizado será el predeterminado para `RedirectResponse`, que es `307`. +En este caso, el `status_code` utilizado será el por defecto para `RedirectResponse`, que es `307`. --- @@ -212,13 +212,13 @@ También puedes usar el parámetro `status_code` combinado con el parámetro `re {* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} -### `StreamingResponse` +### `StreamingResponse` { #streamingresponse } Toma un generador `async` o un generador/iterador normal y transmite el cuerpo del response. {* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} -#### Usando `StreamingResponse` con objetos similares a archivos +#### Usando `StreamingResponse` con objetos similares a archivos { #using-streamingresponse-with-file-like-objects } Si tienes un objeto similar a un archivo (por ejemplo, el objeto devuelto por `open()`), puedes crear una función generadora para iterar sobre ese objeto similar a un archivo. @@ -242,7 +242,7 @@ Nota que aquí como estamos usando `open()` estándar que no admite `async` y `a /// -### `FileResponse` +### `FileResponse` { #fileresponse } Transmite un archivo asincrónicamente como response. @@ -263,7 +263,7 @@ También puedes usar el parámetro `response_class`: En este caso, puedes devolver la path del archivo directamente desde tu *path operation* function. -## Clase de response personalizada +## Clase de response personalizada { #custom-response-class } Puedes crear tu propia clase de response personalizada, heredando de `Response` y usándola. @@ -291,7 +291,7 @@ Ahora en lugar de devolver: Por supuesto, probablemente encontrarás formas mucho mejores de aprovechar esto que formatear JSON. 😉 -## Clase de response predeterminada +## Clase de response por defecto { #default-response-class } Al crear una instance de la clase **FastAPI** o un `APIRouter`, puedes especificar qué clase de response usar por defecto. @@ -307,6 +307,6 @@ Todavía puedes sobrescribir `response_class` en *path operations* como antes. /// -## Documentación adicional +## Documentación adicional { #additional-documentation } También puedes declarar el media type y muchos otros detalles en OpenAPI usando `responses`: [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/es/docs/advanced/dataclasses.md b/docs/es/docs/advanced/dataclasses.md index 0ca1fd3b62..8d96171c7e 100644 --- a/docs/es/docs/advanced/dataclasses.md +++ b/docs/es/docs/advanced/dataclasses.md @@ -1,10 +1,10 @@ -# Usando Dataclasses +# Usando Dataclasses { #using-dataclasses } FastAPI está construido sobre **Pydantic**, y te he estado mostrando cómo usar modelos de Pydantic para declarar requests y responses. Pero FastAPI también soporta el uso de `dataclasses` de la misma manera: -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} Esto sigue siendo soportado gracias a **Pydantic**, ya que tiene soporte interno para `dataclasses`. @@ -28,11 +28,11 @@ Pero si tienes un montón de dataclasses por ahí, este es un buen truco para us /// -## Dataclasses en `response_model` +## Dataclasses en `response_model` { #dataclasses-in-response-model } También puedes usar `dataclasses` en el parámetro `response_model`: -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} El dataclass será automáticamente convertido a un dataclass de Pydantic. @@ -40,7 +40,7 @@ De esta manera, su esquema aparecerá en la interfaz de usuario de la documentac -## Dataclasses en Estructuras de Datos Anidadas +## Dataclasses en Estructuras de Datos Anidadas { #dataclasses-in-nested-data-structures } También puedes combinar `dataclasses` con otras anotaciones de tipos para crear estructuras de datos anidadas. @@ -48,7 +48,7 @@ En algunos casos, todavía podrías tener que usar la versión de `dataclasses` En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `pydantic.dataclasses`, que es un reemplazo directo: -{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} +{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Todavía importamos `field` de los `dataclasses` estándar. @@ -64,7 +64,7 @@ En ese caso, simplemente puedes intercambiar los `dataclasses` estándar con `py 6. Aquí estamos regresando un diccionario que contiene `items`, que es una lista de dataclasses. - FastAPI todavía es capaz de serializar los datos a JSON. + FastAPI todavía es capaz de serializar los datos a JSON. 7. Aquí el `response_model` está usando una anotación de tipo de una lista de dataclasses `Author`. @@ -84,12 +84,12 @@ Puedes combinar `dataclasses` con otras anotaciones de tipos en muchas combinaci Revisa las anotaciones en el código arriba para ver más detalles específicos. -## Aprende Más +## Aprende Más { #learn-more } También puedes combinar `dataclasses` con otros modelos de Pydantic, heredar de ellos, incluirlos en tus propios modelos, etc. Para saber más, revisa la documentación de Pydantic sobre dataclasses. -## Versión +## Versión { #version } Esto está disponible desde la versión `0.67.0` de FastAPI. 🔖 diff --git a/docs/es/docs/advanced/generate-clients.md b/docs/es/docs/advanced/generate-clients.md index b664bceacf..861b58b508 100644 --- a/docs/es/docs/advanced/generate-clients.md +++ b/docs/es/docs/advanced/generate-clients.md @@ -1,115 +1,76 @@ -# Genera Clientes +# Generando SDKs { #generating-sdks } -Como **FastAPI** está basado en la especificación OpenAPI, obtienes compatibilidad automática con muchas herramientas, incluyendo la documentación automática de la API (proporcionada por Swagger UI). +Como **FastAPI** está basado en la especificación **OpenAPI**, sus APIs se pueden describir en un formato estándar que muchas herramientas entienden. -Una ventaja particular que no es necesariamente obvia es que puedes **generar clientes** (a veces llamados **SDKs** ) para tu API, para muchos **lenguajes de programación** diferentes. +Esto facilita generar **documentación** actualizada, paquetes de cliente (**SDKs**) en múltiples lenguajes y **escribir pruebas** o **flujos de automatización** que se mantengan sincronizados con tu código. -## Generadores de Clientes OpenAPI +En esta guía, aprenderás a generar un **SDK de TypeScript** para tu backend con FastAPI. -Hay muchas herramientas para generar clientes desde **OpenAPI**. +## Generadores de SDKs de código abierto { #open-source-sdk-generators } -Una herramienta común es OpenAPI Generator. +Una opción versátil es el OpenAPI Generator, que soporta **muchos lenguajes de programación** y puede generar SDKs a partir de tu especificación OpenAPI. -Si estás construyendo un **frontend**, una alternativa muy interesante es openapi-ts. +Para **clientes de TypeScript**, Hey API es una solución diseñada específicamente, que ofrece una experiencia optimizada para el ecosistema de TypeScript. -## Generadores de Clientes y SDKs - Sponsor +Puedes descubrir más generadores de SDK en OpenAPI.Tools. -También hay algunos generadores de Clientes y SDKs **respaldados por empresas** basados en OpenAPI (FastAPI), en algunos casos pueden ofrecerte **funcionalidades adicionales** además de SDKs/clientes generados de alta calidad. +/// tip | Consejo -Algunos de ellos también ✨ [**sponsorean FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el **desarrollo** continuo y saludable de FastAPI y su **ecosistema**. +FastAPI genera automáticamente especificaciones **OpenAPI 3.1**, así que cualquier herramienta que uses debe soportar esta versión. -Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren proporcionarte un **buen servicio** sino también asegurarse de que tengas un **buen y saludable framework**, FastAPI. 🙇 +/// + +## Generadores de SDKs de sponsors de FastAPI { #sdk-generators-from-fastapi-sponsors } + +Esta sección destaca soluciones **respaldadas por empresas** y **venture-backed** de compañías que sponsorean FastAPI. Estos productos ofrecen **funcionalidades adicionales** e **integraciones** además de SDKs generados de alta calidad. + +Al ✨ [**sponsorear FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, estas compañías ayudan a asegurar que el framework y su **ecosistema** se mantengan saludables y **sustentables**. + +Su sponsorship también demuestra un fuerte compromiso con la **comunidad** de FastAPI (tú), mostrando que no solo les importa ofrecer un **gran servicio**, sino también apoyar un **framework robusto y próspero**, FastAPI. 🙇 Por ejemplo, podrías querer probar: * Speakeasy -* Stainless -* liblab +* Stainless +* liblab -También hay varias otras empresas que ofrecen servicios similares que puedes buscar y encontrar en línea. 🤓 +Algunas de estas soluciones también pueden ser open source u ofrecer niveles gratuitos, así que puedes probarlas sin un compromiso financiero. Hay otros generadores de SDK comerciales disponibles y se pueden encontrar en línea. 🤓 -## Genera un Cliente Frontend en TypeScript +## Crea un SDK de TypeScript { #create-a-typescript-sdk } Empecemos con una aplicación simple de FastAPI: {* ../../docs_src/generate_clients/tutorial001_py39.py hl[7:9,12:13,16:17,21] *} -Nota que las *path operations* definen los modelos que usan para el payload de la petición y el payload del response, usando los modelos `Item` y `ResponseMessage`. +Nota que las *path operations* definen los modelos que usan para el payload del request y el payload del response, usando los modelos `Item` y `ResponseMessage`. -### Documentación de la API +### Documentación de la API { #api-docs } -Si vas a la documentación de la API, verás que tiene los **esquemas** para los datos que se enviarán en las peticiones y se recibirán en los responses: +Si vas a `/docs`, verás que tiene los **esquemas** para los datos a enviar en requests y recibir en responses: -Puedes ver esos esquemas porque fueron declarados con los modelos en la aplicación. +Puedes ver esos esquemas porque fueron declarados con los modelos en la app. -Esa información está disponible en el **JSON Schema** de OpenAPI de la aplicación, y luego se muestra en la documentación de la API (por Swagger UI). +Esa información está disponible en el **OpenAPI schema** de la app, y luego se muestra en la documentación de la API. Y esa misma información de los modelos que está incluida en OpenAPI es lo que puede usarse para **generar el código del cliente**. -### Genera un Cliente en TypeScript +### Hey API { #hey-api } -Ahora que tenemos la aplicación con los modelos, podemos generar el código del cliente para el frontend. +Una vez que tenemos una app de FastAPI con los modelos, podemos usar Hey API para generar un cliente de TypeScript. La forma más rápida de hacerlo es con npx. -#### Instalar `openapi-ts` - -Puedes instalar `openapi-ts` en tu código de frontend con: - -
- -```console -$ npm install @hey-api/openapi-ts --save-dev - ----> 100% +```sh +npx @hey-api/openapi-ts -i http://localhost:8000/openapi.json -o src/client ``` -
+Esto generará un SDK de TypeScript en `./src/client`. -#### Generar el Código del Cliente +Puedes aprender cómo instalar `@hey-api/openapi-ts` y leer sobre el output generado en su sitio web. -Para generar el código del cliente puedes usar la aplicación de línea de comandos `openapi-ts` que ahora estaría instalada. +### Usar el SDK { #using-the-sdk } -Como está instalada en el proyecto local, probablemente no podrías llamar a ese comando directamente, pero podrías ponerlo en tu archivo `package.json`. - -Podría verse como esto: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} -``` - -Después de tener ese script de NPM `generate-client` allí, puedes ejecutarlo con: - -
- -```console -$ npm run generate-client - -frontend-app@1.0.0 generate-client /home/user/code/frontend-app -> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios -``` - -
- -Ese comando generará código en `./src/client` y usará `axios` (el paquete HTTP de frontend) internamente. - -### Prueba el Código del Cliente - -Ahora puedes importar y usar el código del cliente, podría verse así, nota que tienes autocompletado para los métodos: +Ahora puedes importar y usar el código del cliente. Podría verse así, nota que tienes autocompletado para los métodos: @@ -131,17 +92,17 @@ El objeto de response también tendrá autocompletado: -## App de FastAPI con Tags +## App de FastAPI con tags { #fastapi-app-with-tags } -En muchos casos tu aplicación de FastAPI será más grande, y probablemente usarás tags para separar diferentes grupos de *path operations*. +En muchos casos tu app de FastAPI será más grande, y probablemente usarás tags para separar diferentes grupos de *path operations*. -Por ejemplo, podrías tener una sección para **items** y otra sección para **usuarios**, y podrían estar separadas por tags: +Por ejemplo, podrías tener una sección para **items** y otra sección para **users**, y podrían estar separadas por tags: {* ../../docs_src/generate_clients/tutorial002_py39.py hl[21,26,34] *} -### Genera un Cliente TypeScript con Tags +### Genera un Cliente TypeScript con tags { #generate-a-typescript-client-with-tags } -Si generas un cliente para una aplicación de FastAPI usando tags, normalmente también separará el código del cliente basándose en los tags. +Si generas un cliente para una app de FastAPI usando tags, normalmente también separará el código del cliente basándose en los tags. De esta manera podrás tener las cosas ordenadas y agrupadas correctamente para el código del cliente: @@ -152,7 +113,7 @@ En este caso tienes: * `ItemsService` * `UsersService` -### Nombres de los Métodos del Cliente +### Nombres de los métodos del cliente { #client-method-names } Ahora mismo los nombres de los métodos generados como `createItemItemsPost` no se ven muy limpios: @@ -166,15 +127,15 @@ OpenAPI requiere que cada operation ID sea único a través de todas las *path o Pero te mostraré cómo mejorar eso a continuación. 🤓 -## Operation IDs Personalizados y Mejores Nombres de Métodos +## Operation IDs personalizados y mejores nombres de métodos { #custom-operation-ids-and-better-method-names } Puedes **modificar** la forma en que estos operation IDs son **generados** para hacerlos más simples y tener **nombres de métodos más simples** en los clientes. En este caso tendrás que asegurarte de que cada operation ID sea **único** de alguna otra manera. -Por ejemplo, podrías asegurarte de que cada *path operation* tenga un tag, y luego generar el operation ID basado en el **tag** y el nombre de la *path operation* **name** (el nombre de la función). +Por ejemplo, podrías asegurarte de que cada *path operation* tenga un tag, y luego generar el operation ID basado en el **tag** y el **name** de la *path operation* (el nombre de la función). -### Función Personalizada para Generar ID Único +### Función personalizada para generar ID único { #custom-generate-unique-id-function } FastAPI usa un **ID único** para cada *path operation*, se usa para el **operation ID** y también para los nombres de cualquier modelo personalizado necesario, para requests o responses. @@ -186,15 +147,15 @@ Puedes entonces pasar esa función personalizada a **FastAPI** como el parámetr {* ../../docs_src/generate_clients/tutorial003_py39.py hl[6:7,10] *} -### Generar un Cliente TypeScript con Operation IDs Personalizados +### Genera un Cliente TypeScript con operation IDs personalizados { #generate-a-typescript-client-with-custom-operation-ids } -Ahora si generas el cliente de nuevo, verás que tiene los nombres de métodos mejorados: +Ahora, si generas el cliente de nuevo, verás que tiene los nombres de métodos mejorados: Como ves, los nombres de métodos ahora tienen el tag y luego el nombre de la función, ahora no incluyen información del path de la URL y la operación HTTP. -### Preprocesa la Especificación OpenAPI para el Generador de Clientes +### Preprocesa la especificación OpenAPI para el generador de clientes { #preprocess-the-openapi-specification-for-the-client-generator } El código generado aún tiene algo de **información duplicada**. @@ -218,44 +179,30 @@ Podríamos descargar el JSON de OpenAPI a un archivo `openapi.json` y luego podr Con eso, los operation IDs serían renombrados de cosas como `items-get_items` a solo `get_items`, de esa manera el generador del cliente puede generar nombres de métodos más simples. -### Generar un Cliente TypeScript con el OpenAPI Preprocesado +### Genera un Cliente TypeScript con el OpenAPI preprocesado { #generate-a-typescript-client-with-the-preprocessed-openapi } -Ahora como el resultado final está en un archivo `openapi.json`, modificarías el `package.json` para usar ese archivo local, por ejemplo: +Como el resultado final ahora está en un archivo `openapi.json`, necesitas actualizar la ubicación de la entrada: -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} +```sh +npx @hey-api/openapi-ts -i ./openapi.json -o src/client ``` Después de generar el nuevo cliente, ahora tendrías nombres de métodos **limpios**, con todo el **autocompletado**, **errores en línea**, etc: -## Beneficios +## Beneficios { #benefits } -Cuando usas los clientes generados automáticamente obtendrás **autocompletado** para: +Cuando uses los clientes generados automáticamente obtendrás **autocompletado** para: * Métodos. -* Payloads de peticiones en el cuerpo, parámetros de query, etc. -* Payloads de responses. +* Payloads de request en el body, parámetros de query, etc. +* Payloads de response. También tendrás **errores en línea** para todo. Y cada vez que actualices el código del backend, y **regeneres** el frontend, tendrás las nuevas *path operations* disponibles como métodos, las antiguas eliminadas, y cualquier otro cambio se reflejará en el código generado. 🤓 -Esto también significa que si algo cambió será **reflejado** automáticamente en el código del cliente. Y si haces **build** del cliente, te dará error si tienes algún **desajuste** en los datos utilizados. +Esto también significa que si algo cambió será **reflejado** automáticamente en el código del cliente. Y si haces **build** del cliente, dará error si tienes algún **desajuste** en los datos utilizados. Así que, **detectarás muchos errores** muy temprano en el ciclo de desarrollo en lugar de tener que esperar a que los errores se muestren a tus usuarios finales en producción para luego intentar depurar dónde está el problema. ✨ diff --git a/docs/es/docs/advanced/index.md b/docs/es/docs/advanced/index.md index 0626a1563a..f3f4bb85ce 100644 --- a/docs/es/docs/advanced/index.md +++ b/docs/es/docs/advanced/index.md @@ -1,6 +1,6 @@ -# Guía avanzada del usuario +# Guía avanzada del usuario { #advanced-user-guide } -## Funcionalidades adicionales +## Funcionalidades adicionales { #additional-features } El [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} principal debería ser suficiente para darte un recorrido por todas las funcionalidades principales de **FastAPI**. @@ -14,23 +14,8 @@ Y es posible que para tu caso de uso, la solución esté en una de ellas. /// -## Lee primero el Tutorial +## Lee primero el Tutorial { #read-the-tutorial-first } Aún podrías usar la mayoría de las funcionalidades en **FastAPI** con el conocimiento del [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} principal. Y las siguientes secciones asumen que ya lo leíste y que conoces esas ideas principales. - -## Cursos externos - -Aunque el [Tutorial - Guía del usuario](../tutorial/index.md){.internal-link target=_blank} y esta **Guía avanzada del usuario** están escritos como un tutorial guiado (como un libro) y deberían ser suficientes para que **aprendas FastAPI**, podrías querer complementarlo con cursos adicionales. - -O podría ser que simplemente prefieras tomar otros cursos porque se adaptan mejor a tu estilo de aprendizaje. - -Algunos proveedores de cursos ✨ [**sponsorean FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el desarrollo continuo y saludable de FastAPI y su **ecosistema**. - -Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren brindarte una **buena experiencia de aprendizaje** sino que también quieren asegurarse de que tengas un **buen y saludable framework**, FastAPI. 🙇 - -Podrías querer probar sus cursos: - -* Talk Python Training -* Desarrollo guiado por pruebas diff --git a/docs/es/docs/advanced/openapi-callbacks.md b/docs/es/docs/advanced/openapi-callbacks.md index 60d5cb3cc9..caaa70fa89 100644 --- a/docs/es/docs/advanced/openapi-callbacks.md +++ b/docs/es/docs/advanced/openapi-callbacks.md @@ -1,18 +1,18 @@ -# OpenAPI Callbacks +# Callbacks de OpenAPI { #openapi-callbacks } Podrías crear una API con una *path operation* que podría desencadenar un request a una *API externa* creada por alguien más (probablemente el mismo desarrollador que estaría *usando* tu API). -El proceso que ocurre cuando tu aplicación API llama a la *API externa* se llama un "callback". Porque el software que escribió el desarrollador externo envía un request a tu API y luego tu API *responde*, enviando un request a una *API externa* (que probablemente fue creada por el mismo desarrollador). +El proceso que ocurre cuando tu aplicación API llama a la *API externa* se llama un "callback". Porque el software que escribió el desarrollador externo envía un request a tu API y luego tu API hace un *callback*, enviando un request a una *API externa* (que probablemente fue creada por el mismo desarrollador). En este caso, podrías querer documentar cómo esa API externa *debería* verse. Qué *path operation* debería tener, qué cuerpo debería esperar, qué response debería devolver, etc. -## Una aplicación con callbacks +## Una aplicación con callbacks { #an-app-with-callbacks } Veamos todo esto con un ejemplo. Imagina que desarrollas una aplicación que permite crear facturas. -Estas facturas tendrán un `id`, `title` (opcional), `customer`, y `total`. +Estas facturas tendrán un `id`, `title` (opcional), `customer` y `total`. El usuario de tu API (un desarrollador externo) creará una factura en tu API con un request POST. @@ -23,15 +23,15 @@ Luego tu API (imaginemos): * Enviará una notificación de vuelta al usuario de la API (el desarrollador externo). * Esto se hará enviando un request POST (desde *tu API*) a alguna *API externa* proporcionada por ese desarrollador externo (este es el "callback"). -## La aplicación normal de **FastAPI** +## La aplicación normal de **FastAPI** { #the-normal-fastapi-app } -Primero veamos cómo sería la aplicación API normal antes de agregar el callback. +Primero veamos cómo se vería la aplicación API normal antes de agregar el callback. Tendrá una *path operation* que recibirá un cuerpo `Invoice`, y un parámetro de query `callback_url` que contendrá la URL para el callback. Esta parte es bastante normal, probablemente ya estés familiarizado con la mayor parte del código: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} /// tip | Consejo @@ -39,9 +39,9 @@ El parámetro de query `callback_url` utiliza un tipo expresión OpenAPI 3 (ver más abajo) donde puede usar variables con parámetros y partes del request original enviado a *tu API*. -### La expresión del path del callback +### La expresión del path del callback { #the-callback-path-expression } El *path* del callback puede tener una expresión OpenAPI 3 que puede contener partes del request original enviado a *tu API*. @@ -134,7 +134,7 @@ con un JSON body de: } ``` -luego *tu API* procesará la factura, y en algún momento después, enviará un request de callback al `callback_url` (la *API externa*): +luego *tu API* procesará la factura y, en algún momento después, enviará un request de callback al `callback_url` (la *API externa*): ``` https://www.external.org/events/invoices/2expen51ve @@ -163,13 +163,13 @@ Observa cómo la URL del callback utilizada contiene la URL recibida como parám /// -### Agregar el router de callback +### Agrega el router de callback { #add-the-callback-router } -En este punto tienes las *path operation(s)* del callback necesarias (las que el *desarrollador externo* debería implementar en la *API externa*) en el router de callback que creaste antes. +En este punto tienes las *path operation(s)* del callback necesarias (las que el *desarrollador externo* debería implementar en la *API externa*) en el router de callback que creaste arriba. Ahora usa el parámetro `callbacks` en el *decorador de path operation de tu API* para pasar el atributo `.routes` (que en realidad es solo un `list` de rutas/*path operations*) de ese router de callback: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} /// tip | Consejo @@ -177,7 +177,7 @@ Observa que no estás pasando el router en sí (`invoices_callback_router`) a `c /// -### Revisa la documentación +### Revisa la documentación { #check-the-docs } Ahora puedes iniciar tu aplicación e ir a http://127.0.0.1:8000/docs. diff --git a/docs/es/docs/advanced/openapi-webhooks.md b/docs/es/docs/advanced/openapi-webhooks.md index 01235b3ab9..2dfa74b210 100644 --- a/docs/es/docs/advanced/openapi-webhooks.md +++ b/docs/es/docs/advanced/openapi-webhooks.md @@ -1,4 +1,4 @@ -# Webhooks de OpenAPI +# Webhooks de OpenAPI { #openapi-webhooks } Hay casos donde quieres decirle a los **usuarios** de tu API que tu aplicación podría llamar a *su* aplicación (enviando una request) con algunos datos, normalmente para **notificar** de algún tipo de **evento**. @@ -6,7 +6,7 @@ Esto significa que en lugar del proceso normal de tus usuarios enviando requests Esto normalmente se llama un **webhook**. -## Pasos de los webhooks +## Pasos de los webhooks { #webhooks-steps } El proceso normalmente es que **tú defines** en tu código cuál es el mensaje que enviarás, el **body de la request**. @@ -16,7 +16,7 @@ Y **tus usuarios** definen de alguna manera (por ejemplo en un panel web en alg Toda la **lógica** sobre cómo registrar los URLs para webhooks y el código para realmente enviar esas requests depende de ti. Lo escribes como quieras en **tu propio código**. -## Documentando webhooks con **FastAPI** y OpenAPI +## Documentando webhooks con **FastAPI** y OpenAPI { #documenting-webhooks-with-fastapi-and-openapi } Con **FastAPI**, usando OpenAPI, puedes definir los nombres de estos webhooks, los tipos de operaciones HTTP que tu aplicación puede enviar (por ejemplo, `POST`, `PUT`, etc.) y los **bodies** de las requests que tu aplicación enviaría. @@ -28,7 +28,7 @@ Los webhooks están disponibles en OpenAPI 3.1.0 y superiores, soportados por Fa /// -## Una aplicación con webhooks +## Una aplicación con webhooks { #an-app-with-webhooks } Cuando creas una aplicación de **FastAPI**, hay un atributo `webhooks` que puedes usar para definir *webhooks*, de la misma manera que definirías *path operations*, por ejemplo con `@app.webhooks.post()`. @@ -46,7 +46,7 @@ Nota que con los webhooks en realidad no estás declarando un *path* (como `/ite Esto es porque se espera que **tus usuarios** definan el actual **URL path** donde quieren recibir la request del webhook de alguna otra manera (por ejemplo, un panel web). -### Revisa la documentación +### Revisa la documentación { #check-the-docs } Ahora puedes iniciar tu app e ir a http://127.0.0.1:8000/docs. diff --git a/docs/es/docs/advanced/path-operation-advanced-configuration.md b/docs/es/docs/advanced/path-operation-advanced-configuration.md index 2b20819aa3..98965ee9e2 100644 --- a/docs/es/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/es/docs/advanced/path-operation-advanced-configuration.md @@ -1,6 +1,6 @@ -# Configuración Avanzada de Path Operation +# Configuración Avanzada de Path Operation { #path-operation-advanced-configuration } -## operationId de OpenAPI +## operationId de OpenAPI { #openapi-operationid } /// warning | Advertencia @@ -14,7 +14,7 @@ Tienes que asegurarte de que sea único para cada operación. {* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} -### Usar el nombre de la *función de path operation* como el operationId +### Usar el nombre de la *path operation function* como el operationId { #using-the-path-operation-function-name-as-the-operationid } Si quieres usar los nombres de las funciones de tus APIs como `operationId`s, puedes iterar sobre todas ellas y sobrescribir el `operation_id` de cada *path operation* usando su `APIRoute.name`. @@ -30,29 +30,29 @@ Si llamas manualmente a `app.openapi()`, deberías actualizar los `operationId`s /// warning | Advertencia -Si haces esto, tienes que asegurarte de que cada una de tus *funciones de path operation* tenga un nombre único. +Si haces esto, tienes que asegurarte de que cada una de tus *path operation functions* tenga un nombre único. Incluso si están en diferentes módulos (archivos de Python). /// -## Excluir de OpenAPI +## Excluir de OpenAPI { #exclude-from-openapi } Para excluir una *path operation* del esquema OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), utiliza el parámetro `include_in_schema` y configúralo en `False`: {* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} -## Descripción avanzada desde el docstring +## Descripción avanzada desde el docstring { #advanced-description-from-docstring } -Puedes limitar las líneas usadas del docstring de una *función de path operation* para OpenAPI. +Puedes limitar las líneas usadas del docstring de una *path operation function* para OpenAPI. Añadir un `\f` (un carácter de separación de página escapado) hace que **FastAPI** trunque la salida usada para OpenAPI en este punto. No aparecerá en la documentación, pero otras herramientas (como Sphinx) podrán usar el resto. -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} -## Responses Adicionales +## Responses Adicionales { #additional-responses } Probablemente has visto cómo declarar el `response_model` y el `status_code` para una *path operation*. @@ -62,11 +62,11 @@ También puedes declarar responses adicionales con sus modelos, códigos de esta Hay un capítulo entero en la documentación sobre ello, puedes leerlo en [Responses Adicionales en OpenAPI](additional-responses.md){.internal-link target=_blank}. -## OpenAPI Extra +## OpenAPI Extra { #openapi-extra } Cuando declaras una *path operation* en tu aplicación, **FastAPI** genera automáticamente los metadatos relevantes sobre esa *path operation* para incluirlos en el esquema de OpenAPI. -/// note | Nota +/// note | Detalles técnicos En la especificación de OpenAPI se llama el Objeto de Operación. @@ -88,7 +88,7 @@ Si solo necesitas declarar responses adicionales, una forma más conveniente de Puedes extender el esquema de OpenAPI para una *path operation* usando el parámetro `openapi_extra`. -### Extensiones de OpenAPI +### Extensiones de OpenAPI { #openapi-extensions } Este `openapi_extra` puede ser útil, por ejemplo, para declarar [Extensiones de OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): @@ -129,7 +129,7 @@ Y si ves el OpenAPI resultante (en `/openapi.json` en tu API), verás tu extensi } ``` -### Esquema de *path operation* personalizada de OpenAPI +### Esquema de *path operation* personalizada de OpenAPI { #custom-openapi-path-operation-schema } El diccionario en `openapi_extra` se combinará profundamente con el esquema de OpenAPI generado automáticamente para la *path operation*. @@ -141,37 +141,37 @@ Podrías hacer eso con `openapi_extra`: {* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} -En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el cuerpo del request ni siquiera se parse como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera. +En este ejemplo, no declaramos ningún modelo Pydantic. De hecho, el cuerpo del request ni siquiera se parse como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera. Sin embargo, podemos declarar el esquema esperado para el cuerpo del request. -### Tipo de contenido personalizado de OpenAPI +### Tipo de contenido personalizado de OpenAPI { #custom-openapi-content-type } -Usando este mismo truco, podrías usar un modelo Pydantic para definir el esquema JSON que luego se incluye en la sección personalizada del esquema OpenAPI para la *path operation*. +Usando este mismo truco, podrías usar un modelo Pydantic para definir el JSON Schema que luego se incluye en la sección personalizada del esquema OpenAPI para la *path operation*. Y podrías hacer esto incluso si el tipo de datos en el request no es JSON. -Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el esquema JSON de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON: +Por ejemplo, en esta aplicación no usamos la funcionalidad integrada de FastAPI para extraer el JSON Schema de los modelos Pydantic ni la validación automática para JSON. De hecho, estamos declarando el tipo de contenido del request como YAML, no JSON: //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} //// /// info | Información -En la versión 1 de Pydantic el método para obtener el esquema JSON para un modelo se llamaba `Item.schema()`, en la versión 2 de Pydantic, el método se llama `Item.model_json_schema()`. +En la versión 1 de Pydantic el método para obtener el JSON Schema para un modelo se llamaba `Item.schema()`, en la versión 2 de Pydantic, el método se llama `Item.model_json_schema()`. /// -Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el esquema JSON para los datos que queremos recibir en YAML. +Sin embargo, aunque no estamos usando la funcionalidad integrada por defecto, aún estamos usando un modelo Pydantic para generar manualmente el JSON Schema para los datos que queremos recibir en YAML. Luego usamos el request directamente, y extraemos el cuerpo como `bytes`. Esto significa que FastAPI ni siquiera intentará parsear la carga útil del request como JSON. @@ -179,13 +179,13 @@ Y luego en nuestro código, parseamos ese contenido YAML directamente, y nuevame //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} //// diff --git a/docs/es/docs/advanced/response-change-status-code.md b/docs/es/docs/advanced/response-change-status-code.md index e0889c4743..067267750b 100644 --- a/docs/es/docs/advanced/response-change-status-code.md +++ b/docs/es/docs/advanced/response-change-status-code.md @@ -1,10 +1,10 @@ -# Response - Cambiar Código de Estado +# Response - Cambiar Código de Estado { #response-change-status-code } Probablemente leíste antes que puedes establecer un [Código de Estado de Response](../tutorial/response-status-code.md){.internal-link target=_blank} por defecto. Pero en algunos casos necesitas devolver un código de estado diferente al predeterminado. -## Caso de uso +## Caso de uso { #use-case } Por ejemplo, imagina que quieres devolver un código de estado HTTP de "OK" `200` por defecto. @@ -14,9 +14,9 @@ Pero todavía quieres poder filtrar y convertir los datos que devuelves con un ` Para esos casos, puedes usar un parámetro `Response`. -## Usa un parámetro `Response` +## Usa un parámetro `Response` { #use-a-response-parameter } -Puedes declarar un parámetro de tipo `Response` en tu *función de path operation* (como puedes hacer para cookies y headers). +Puedes declarar un parámetro de tipo `Response` en tu *path operation function* (como puedes hacer para cookies y headers). Y luego puedes establecer el `status_code` en ese objeto de response *temporal*. diff --git a/docs/es/docs/advanced/response-directly.md b/docs/es/docs/advanced/response-directly.md index 8594011d61..96b30b915c 100644 --- a/docs/es/docs/advanced/response-directly.md +++ b/docs/es/docs/advanced/response-directly.md @@ -1,4 +1,4 @@ -# Devolver una Response Directamente +# Devolver una Response Directamente { #return-a-response-directly } Cuando creas una *path operation* en **FastAPI**, normalmente puedes devolver cualquier dato desde ella: un `dict`, una `list`, un modelo de Pydantic, un modelo de base de datos, etc. @@ -10,7 +10,7 @@ Pero puedes devolver un `JSONResponse` directamente desde tus *path operations*. Esto podría ser útil, por ejemplo, para devolver headers o cookies personalizados. -## Devolver una `Response` +## Devolver una `Response` { #return-a-response } De hecho, puedes devolver cualquier `Response` o cualquier subclase de ella. @@ -26,7 +26,7 @@ No hará ninguna conversión de datos con los modelos de Pydantic, no convertir Esto te da mucha flexibilidad. Puedes devolver cualquier tipo de datos, sobrescribir cualquier declaración o validación de datos, etc. -## Usar el `jsonable_encoder` en una `Response` +## Usar el `jsonable_encoder` en una `Response` { #using-the-jsonable-encoder-in-a-response } Como **FastAPI** no realiza cambios en una `Response` que devuelves, tienes que asegurarte de que sus contenidos estén listos para ello. @@ -34,9 +34,9 @@ Por ejemplo, no puedes poner un modelo de Pydantic en un `JSONResponse` sin prim Para esos casos, puedes usar el `jsonable_encoder` para convertir tus datos antes de pasarlos a un response: -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} -/// note | Nota +/// note | Detalles técnicos También podrías usar `from starlette.responses import JSONResponse`. @@ -44,7 +44,7 @@ También podrías usar `from starlette.responses import JSONResponse`. /// -## Devolver una `Response` personalizada +## Devolver una `Response` personalizada { #returning-a-custom-response } El ejemplo anterior muestra todas las partes que necesitas, pero aún no es muy útil, ya que podrías haber devuelto el `item` directamente, y **FastAPI** lo colocaría en un `JSONResponse` por ti, convirtiéndolo a un `dict`, etc. Todo eso por defecto. @@ -56,7 +56,7 @@ Podrías poner tu contenido XML en un string, poner eso en un `Response`, y devo {* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} -## Notas +## Notas { #notes } Cuando devuelves una `Response` directamente, sus datos no son validados, convertidos (serializados), ni documentados automáticamente. diff --git a/docs/es/docs/advanced/security/http-basic-auth.md b/docs/es/docs/advanced/security/http-basic-auth.md index 629e6c50a0..440c081e06 100644 --- a/docs/es/docs/advanced/security/http-basic-auth.md +++ b/docs/es/docs/advanced/security/http-basic-auth.md @@ -1,4 +1,4 @@ -# HTTP Basic Auth +# HTTP Basic Auth { #http-basic-auth } Para los casos más simples, puedes usar HTTP Basic Auth. @@ -12,7 +12,7 @@ Eso le dice al navegador que muestre el prompt integrado para un nombre de usuar Luego, cuando escribes ese nombre de usuario y contraseña, el navegador los envía automáticamente en el header. -## Simple HTTP Basic Auth +## Simple HTTP Basic Auth { #simple-http-basic-auth } * Importa `HTTPBasic` y `HTTPBasicCredentials`. * Crea un "esquema de `security`" usando `HTTPBasic`. @@ -26,7 +26,7 @@ Cuando intentas abrir la URL por primera vez (o haces clic en el botón "Execute -## Revisa el nombre de usuario +## Revisa el nombre de usuario { #check-the-username } Aquí hay un ejemplo más completo. @@ -46,13 +46,13 @@ Esto sería similar a: ```Python if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): - # Return some error + # Devuelve algún error ... ``` Pero al usar `secrets.compare_digest()` será seguro contra un tipo de ataques llamados "timing attacks". -### Timing Attacks +### Timing attacks { #timing-attacks } ¿Pero qué es un "timing attack"? @@ -80,19 +80,19 @@ if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": Python tendrá que comparar todo `stanleyjobso` en ambos `stanleyjobsox` y `stanleyjobson` antes de darse cuenta de que ambas strings no son las mismas. Así que tomará algunos microsegundos extra para responder "Nombre de usuario o contraseña incorrectos". -#### El tiempo de respuesta ayuda a los atacantes +#### El tiempo de respuesta ayuda a los atacantes { #the-time-to-answer-helps-the-attackers } En ese punto, al notar que el servidor tardó algunos microsegundos más en enviar el response "Nombre de usuario o contraseña incorrectos", los atacantes sabrán que acertaron en _algo_, algunas de las letras iniciales eran correctas. Y luego pueden intentar de nuevo sabiendo que probablemente es algo más similar a `stanleyjobsox` que a `johndoe`. -#### Un ataque "profesional" +#### Un ataque "profesional" { #a-professional-attack } Por supuesto, los atacantes no intentarían todo esto a mano, escribirían un programa para hacerlo, posiblemente con miles o millones de pruebas por segundo. Y obtendrían solo una letra correcta adicional a la vez. Pero haciendo eso, en algunos minutos u horas, los atacantes habrían adivinado el nombre de usuario y la contraseña correctos, con la "ayuda" de nuestra aplicación, solo usando el tiempo tomado para responder. -#### Arréglalo con `secrets.compare_digest()` +#### Arréglalo con `secrets.compare_digest()` { #fix-it-with-secrets-compare-digest } Pero en nuestro código estamos usando realmente `secrets.compare_digest()`. @@ -100,7 +100,7 @@ En resumen, tomará el mismo tiempo comparar `stanleyjobsox` con `stanleyjobson` De esa manera, usando `secrets.compare_digest()` en el código de tu aplicación, será seguro contra todo este rango de ataques de seguridad. -### Devuelve el error +### Devuelve el error { #return-the-error } Después de detectar que las credenciales son incorrectas, regresa un `HTTPException` con un código de estado 401 (el mismo que se devuelve cuando no se proporcionan credenciales) y agrega el header `WWW-Authenticate` para que el navegador muestre el prompt de inicio de sesión nuevamente: diff --git a/docs/es/docs/advanced/security/index.md b/docs/es/docs/advanced/security/index.md index e4ccb59781..8b3e67fac4 100644 --- a/docs/es/docs/advanced/security/index.md +++ b/docs/es/docs/advanced/security/index.md @@ -1,6 +1,6 @@ -# Seguridad Avanzada +# Seguridad Avanzada { #advanced-security } -## Funcionalidades Adicionales +## Funcionalidades Adicionales { #additional-features } Hay algunas funcionalidades extra para manejar la seguridad aparte de las cubiertas en el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank}. @@ -12,8 +12,8 @@ Y es posible que para tu caso de uso, la solución esté en una de ellas. /// -## Lee primero el Tutorial +## Lee primero el Tutorial { #read-the-tutorial-first } -Las siguientes secciones asumen que ya leíste el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank}. +Las siguientes secciones asumen que ya leíste el [Tutorial - Guía del Usuario: Seguridad](../../tutorial/security/index.md){.internal-link target=_blank} principal. Todas están basadas en los mismos conceptos, pero permiten algunas funcionalidades adicionales. diff --git a/docs/es/docs/advanced/settings.md b/docs/es/docs/advanced/settings.md index 7e591cc01b..65dee933db 100644 --- a/docs/es/docs/advanced/settings.md +++ b/docs/es/docs/advanced/settings.md @@ -1,4 +1,4 @@ -# Configuraciones y Variables de Entorno +# Configuraciones y Variables de Entorno { #settings-and-environment-variables } En muchos casos, tu aplicación podría necesitar algunas configuraciones o ajustes externos, por ejemplo, claves secretas, credenciales de base de datos, credenciales para servicios de correo electrónico, etc. @@ -12,17 +12,17 @@ Para entender las variables de entorno, puedes leer [Variables de Entorno](../en /// -## Tipos y validación +## Tipos y validación { #types-and-validation } Estas variables de entorno solo pueden manejar strings de texto, ya que son externas a Python y tienen que ser compatibles con otros programas y el resto del sistema (e incluso con diferentes sistemas operativos, como Linux, Windows, macOS). Eso significa que cualquier valor leído en Python desde una variable de entorno será un `str`, y cualquier conversión a un tipo diferente o cualquier validación tiene que hacerse en código. -## Pydantic `Settings` +## Pydantic `Settings` { #pydantic-settings } Afortunadamente, Pydantic proporciona una gran utilidad para manejar estas configuraciones provenientes de variables de entorno con Pydantic: Settings management. -### Instalar `pydantic-settings` +### Instalar `pydantic-settings` { #install-pydantic-settings } Primero, asegúrate de crear tu [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, actívalo y luego instala el paquete `pydantic-settings`: @@ -52,7 +52,7 @@ En Pydantic v1 venía incluido con el paquete principal. Ahora se distribuye com /// -### Crear el objeto `Settings` +### Crear el objeto `Settings` { #create-the-settings-object } Importa `BaseSettings` de Pydantic y crea una sub-clase, muy similar a un modelo de Pydantic. @@ -88,13 +88,13 @@ Luego, cuando creas una instance de esa clase `Settings` (en este caso, en el ob Luego convertirá y validará los datos. Así que, cuando uses ese objeto `settings`, tendrás datos de los tipos que declaraste (por ejemplo, `items_per_user` será un `int`). -### Usar el `settings` +### Usar el `settings` { #use-the-settings } Luego puedes usar el nuevo objeto `settings` en tu aplicación: {* ../../docs_src/settings/tutorial001.py hl[18:20] *} -### Ejecutar el servidor +### Ejecutar el servidor { #run-the-server } Luego, ejecutarías el servidor pasando las configuraciones como variables de entorno, por ejemplo, podrías establecer un `ADMIN_EMAIL` y `APP_NAME` con: @@ -120,7 +120,7 @@ El `app_name` sería `"ChimichangApp"`. Y el `items_per_user` mantendría su valor por defecto de `50`. -## Configuraciones en otro módulo +## Configuraciones en otro módulo { #settings-in-another-module } Podrías poner esas configuraciones en otro archivo de módulo como viste en [Aplicaciones Más Grandes - Múltiples Archivos](../tutorial/bigger-applications.md){.internal-link target=_blank}. @@ -138,21 +138,21 @@ También necesitarías un archivo `__init__.py` como viste en [Aplicaciones Más /// -## Configuraciones en una dependencia +## Configuraciones en una dependencia { #settings-in-a-dependency } En algunas ocasiones podría ser útil proporcionar las configuraciones desde una dependencia, en lugar de tener un objeto global con `settings` que se use en todas partes. Esto podría ser especialmente útil durante las pruebas, ya que es muy fácil sobrescribir una dependencia con tus propias configuraciones personalizadas. -### El archivo de configuración +### El archivo de configuración { #the-config-file } Proveniente del ejemplo anterior, tu archivo `config.py` podría verse como: -{* ../../docs_src/settings/app02/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} Nota que ahora no creamos una instance por defecto `settings = Settings()`. -### El archivo principal de la app +### El archivo principal de la app { #the-main-app-file } Ahora creamos una dependencia que devuelve un nuevo `config.Settings()`. @@ -170,17 +170,17 @@ Y luego podemos requerirlo desde la *path operation function* como una dependenc {* ../../docs_src/settings/app02_an_py39/main.py hl[17,19:21] *} -### Configuraciones y pruebas +### Configuraciones y pruebas { #settings-and-testing } Luego sería muy fácil proporcionar un objeto de configuraciones diferente durante las pruebas al sobrescribir una dependencia para `get_settings`: -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} En la dependencia sobreescrita establecemos un nuevo valor para el `admin_email` al crear el nuevo objeto `Settings`, y luego devolvemos ese nuevo objeto. Luego podemos probar que se está usando. -## Leer un archivo `.env` +## Leer un archivo `.env` { #reading-a-env-file } Si tienes muchas configuraciones que posiblemente cambien mucho, tal vez en diferentes entornos, podría ser útil ponerlos en un archivo y luego leerlos desde allí como si fueran variables de entorno. @@ -202,7 +202,7 @@ Para que esto funcione, necesitas `pip install python-dotenv`. /// -### El archivo `.env` +### El archivo `.env` { #the-env-file } Podrías tener un archivo `.env` con: @@ -211,13 +211,13 @@ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" ``` -### Leer configuraciones desde `.env` +### Leer configuraciones desde `.env` { #read-settings-from-env } Y luego actualizar tu `config.py` con: //// tab | Pydantic v2 -{* ../../docs_src/settings/app03_an/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Consejo @@ -229,7 +229,7 @@ El atributo `model_config` se usa solo para configuración de Pydantic. Puedes l //// tab | Pydantic v1 -{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} +{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} /// tip | Consejo @@ -247,7 +247,7 @@ En la versión 1 de Pydantic la configuración se hacía en una clase interna `C Aquí definimos la configuración `env_file` dentro de tu clase Pydantic `Settings`, y establecemos el valor en el nombre del archivo con el archivo dotenv que queremos usar. -### Creando el `Settings` solo una vez con `lru_cache` +### Creando el `Settings` solo una vez con `lru_cache` { #creating-the-settings-only-once-with-lru-cache } Leer un archivo desde el disco es normalmente una operación costosa (lenta), por lo que probablemente quieras hacerlo solo una vez y luego reutilizar el mismo objeto de configuraciones, en lugar de leerlo para cada request. @@ -274,7 +274,7 @@ Pero como estamos usando el decorador `@lru_cache` encima, el objeto `Settings` Entonces, para cualquier llamada subsiguiente de `get_settings()` en las dependencias de los próximos requests, en lugar de ejecutar el código interno de `get_settings()` y crear un nuevo objeto `Settings`, devolverá el mismo objeto que fue devuelto en la primera llamada, una y otra vez. -#### Detalles Técnicos de `lru_cache` +#### Detalles Técnicos de `lru_cache` { #lru-cache-technical-details } `@lru_cache` modifica la función que decora para devolver el mismo valor que se devolvió la primera vez, en lugar de calcularlo nuevamente, ejecutando el código de la función cada vez. @@ -335,9 +335,9 @@ En el caso de nuestra dependencia `get_settings()`, la función ni siquiera toma De esa manera, se comporta casi como si fuera solo una variable global. Pero como usa una función de dependencia, entonces podemos sobrescribirla fácilmente para las pruebas. -`@lru_cache` es parte de `functools`, que es parte del library estándar de Python, puedes leer más sobre él en las docs de Python para `@lru_cache`. +`@lru_cache` es parte de `functools`, que es parte del paquete estándar de Python, puedes leer más sobre él en las docs de Python para `@lru_cache`. -## Resumen +## Resumen { #recap } Puedes usar Pydantic Settings para manejar las configuraciones o ajustes de tu aplicación, con todo el poder de los modelos de Pydantic. diff --git a/docs/es/docs/advanced/sub-applications.md b/docs/es/docs/advanced/sub-applications.md index ccb31f1ea7..ddc0cd69a5 100644 --- a/docs/es/docs/advanced/sub-applications.md +++ b/docs/es/docs/advanced/sub-applications.md @@ -1,18 +1,18 @@ -# Sub Aplicaciones - Mounts +# Sub Aplicaciones - Mounts { #sub-applications-mounts } Si necesitas tener dos aplicaciones de **FastAPI** independientes, cada una con su propio OpenAPI independiente y su propia interfaz de docs, puedes tener una aplicación principal y "montar" una (o más) sub-aplicación(es). -## Montar una aplicación **FastAPI** +## Montar una aplicación **FastAPI** { #mounting-a-fastapi-application } "Montar" significa añadir una aplicación completamente "independiente" en un path específico, que luego se encarga de manejar todo bajo ese path, con las _path operations_ declaradas en esa sub-aplicación. -### Aplicación de nivel superior +### Aplicación de nivel superior { #top-level-application } Primero, crea la aplicación principal de nivel superior de **FastAPI**, y sus *path operations*: {* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} -### Sub-aplicación +### Sub-aplicación { #sub-application } Luego, crea tu sub-aplicación, y sus *path operations*. @@ -20,7 +20,7 @@ Esta sub-aplicación es solo otra aplicación estándar de FastAPI, pero es la q {* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} -### Montar la sub-aplicación +### Montar la sub-aplicación { #mount-the-sub-application } En tu aplicación de nivel superior, `app`, monta la sub-aplicación, `subapi`. @@ -28,7 +28,7 @@ En este caso, se montará en el path `/subapi`: {* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} -### Revisa la documentación automática de la API +### Revisa la documentación automática de la API { #check-the-automatic-api-docs } Ahora, ejecuta el comando `fastapi` con tu archivo: @@ -56,7 +56,7 @@ Verás la documentación automática de la API para la sub-aplicación, incluyen Si intentas interactuar con cualquiera de las dos interfaces de usuario, funcionarán correctamente, porque el navegador podrá comunicarse con cada aplicación o sub-aplicación específica. -### Detalles Técnicos: `root_path` +### Detalles Técnicos: `root_path` { #technical-details-root-path } Cuando montas una sub-aplicación como se describe arriba, FastAPI se encargará de comunicar el path de montaje para la sub-aplicación usando un mecanismo de la especificación ASGI llamado `root_path`. diff --git a/docs/es/docs/advanced/testing-dependencies.md b/docs/es/docs/advanced/testing-dependencies.md index 14b90ea069..d209f2e401 100644 --- a/docs/es/docs/advanced/testing-dependencies.md +++ b/docs/es/docs/advanced/testing-dependencies.md @@ -1,6 +1,6 @@ -# Probando Dependencias con Overrides +# Probando Dependencias con Overrides { #testing-dependencies-with-overrides } -## Sobrescribir dependencias durante las pruebas +## Sobrescribir dependencias durante las pruebas { #overriding-dependencies-during-testing } Hay algunos escenarios donde podrías querer sobrescribir una dependencia durante las pruebas. @@ -8,7 +8,7 @@ No quieres que la dependencia original se ejecute (ni ninguna de las sub-depende En cambio, quieres proporcionar una dependencia diferente que se usará solo durante las pruebas (posiblemente solo algunas pruebas específicas), y que proporcionará un valor que pueda ser usado donde se usó el valor de la dependencia original. -### Casos de uso: servicio externo +### Casos de uso: servicio externo { #use-cases-external-service } Un ejemplo podría ser que tienes un proveedor de autenticación externo al que necesitas llamar. @@ -20,7 +20,7 @@ Probablemente quieras probar el proveedor externo una vez, pero no necesariament En este caso, puedes sobrescribir la dependencia que llama a ese proveedor y usar una dependencia personalizada que devuelva un usuario de prueba, solo para tus tests. -### Usa el atributo `app.dependency_overrides` +### Usa el atributo `app.dependency_overrides` { #use-the-app-dependency-overrides-attribute } Para estos casos, tu aplicación **FastAPI** tiene un atributo `app.dependency_overrides`, es un simple `dict`. diff --git a/docs/es/docs/advanced/testing-events.md b/docs/es/docs/advanced/testing-events.md index 9c2ec77b91..3b48d5fa12 100644 --- a/docs/es/docs/advanced/testing-events.md +++ b/docs/es/docs/advanced/testing-events.md @@ -1,5 +1,12 @@ -# Testing Events: startup - shutdown +# Eventos de testing: lifespan y startup - shutdown { #testing-events-lifespan-and-startup-shutdown } -Cuando necesitas que tus manejadores de eventos (`startup` y `shutdown`) se ejecuten en tus tests, puedes usar el `TestClient` con un statement `with`: +Cuando necesitas que `lifespan` se ejecute en tus tests, puedes usar el `TestClient` con un statement `with`: + +{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} + + +Puedes leer más detalles sobre ["Ejecutar lifespan en tests en el sitio oficial de documentación de Starlette."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) + +Para los eventos obsoletos `startup` y `shutdown`, puedes usar el `TestClient` así: {* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/es/docs/advanced/wsgi.md b/docs/es/docs/advanced/wsgi.md index 7df62fc9ae..9fd54e68da 100644 --- a/docs/es/docs/advanced/wsgi.md +++ b/docs/es/docs/advanced/wsgi.md @@ -1,10 +1,10 @@ -# Incluyendo WSGI - Flask, Django, otros +# Incluyendo WSGI - Flask, Django, otros { #including-wsgi-flask-django-others } Puedes montar aplicaciones WSGI como viste con [Sub Aplicaciones - Mounts](sub-applications.md){.internal-link target=_blank}, [Detrás de un Proxy](behind-a-proxy.md){.internal-link target=_blank}. Para eso, puedes usar `WSGIMiddleware` y usarlo para envolver tu aplicación WSGI, por ejemplo, Flask, Django, etc. -## Usando `WSGIMiddleware` +## Usando `WSGIMiddleware` { #using-wsgimiddleware } Necesitas importar `WSGIMiddleware`. @@ -14,7 +14,7 @@ Y luego móntala bajo un path. {* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} -## Revisa +## Revisa { #check-it } Ahora, cada request bajo el path `/v1/` será manejado por la aplicación Flask. diff --git a/docs/es/docs/benchmarks.md b/docs/es/docs/benchmarks.md index 49d65b6ba1..e6f8f99647 100644 --- a/docs/es/docs/benchmarks.md +++ b/docs/es/docs/benchmarks.md @@ -1,10 +1,10 @@ -# Benchmarks +# Benchmarks { #benchmarks } Los benchmarks independientes de TechEmpower muestran aplicaciones de **FastAPI** ejecutándose bajo Uvicorn como uno de los frameworks de Python más rápidos disponibles, solo por debajo de Starlette y Uvicorn en sí mismos (utilizados internamente por FastAPI). Pero al revisar benchmarks y comparaciones, debes tener en cuenta lo siguiente. -## Benchmarks y velocidad +## Benchmarks y velocidad { #benchmarks-and-speed } Cuando ves los benchmarks, es común ver varias herramientas de diferentes tipos comparadas como equivalentes. diff --git a/docs/es/docs/deployment/cloud.md b/docs/es/docs/deployment/cloud.md index f72b88c033..f3c951d989 100644 --- a/docs/es/docs/deployment/cloud.md +++ b/docs/es/docs/deployment/cloud.md @@ -1,15 +1,24 @@ -# Despliega FastAPI en Proveedores de Nube +# Despliega FastAPI en Proveedores de Nube { #deploy-fastapi-on-cloud-providers } Puedes usar prácticamente **cualquier proveedor de nube** para desplegar tu aplicación FastAPI. En la mayoría de los casos, los principales proveedores de nube tienen guías para desplegar FastAPI con ellos. -## Proveedores de Nube - Sponsors +## FastAPI Cloud { #fastapi-cloud } -Algunos proveedores de nube ✨ [**son sponsors de FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, esto asegura el desarrollo **continuado** y **saludable** de FastAPI y su **ecosistema**. +**FastAPI Cloud** está construido por el mismo autor y equipo detrás de **FastAPI**. -Y muestra su verdadero compromiso con FastAPI y su **comunidad** (tú), ya que no solo quieren proporcionarte un **buen servicio**, sino también asegurarse de que tengas un **framework bueno y saludable**, FastAPI. 🙇 +Simplifica el proceso de **construir**, **desplegar** y **acceder** a una API con un esfuerzo mínimo. -Podrías querer probar sus servicios y seguir sus guías: +Trae la misma experiencia de desarrollador de construir aplicaciones con FastAPI a desplegarlas en la nube. 🎉 + +FastAPI Cloud es el sponsor principal y proveedor de financiamiento de los proyectos open source *FastAPI and friends*. ✨ + +## Proveedores de Nube - Sponsors { #cloud-providers-sponsors } + +Otros proveedores de nube ✨ [**son sponsors de FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ también. 🙇 + +También podrías considerarlos para seguir sus guías y probar sus servicios: * Render +* Railway diff --git a/docs/es/docs/deployment/concepts.md b/docs/es/docs/deployment/concepts.md index bcc7948bc5..c42ced70bc 100644 --- a/docs/es/docs/deployment/concepts.md +++ b/docs/es/docs/deployment/concepts.md @@ -1,4 +1,4 @@ -# Conceptos de Implementación +# Conceptos de Implementación { #deployments-concepts } Cuando implementas una aplicación **FastAPI**, o en realidad, cualquier tipo de API web, hay varios conceptos que probablemente te importen, y al entenderlos, puedes encontrar la **forma más adecuada** de **implementar tu aplicación**. @@ -23,7 +23,7 @@ En los próximos capítulos, te daré más **recetas concretas** para implementa Pero por ahora, revisemos estas importantes **ideas conceptuales**. Estos conceptos también se aplican a cualquier otro tipo de API web. 💡 -## Seguridad - HTTPS +## Seguridad - HTTPS { #security-https } En el [capítulo anterior sobre HTTPS](https.md){.internal-link target=_blank} aprendimos sobre cómo HTTPS proporciona cifrado para tu API. @@ -31,7 +31,7 @@ También vimos que HTTPS es normalmente proporcionado por un componente **extern Y debe haber algo encargado de **renovar los certificados HTTPS**, podría ser el mismo componente o algo diferente. -### Herramientas de Ejemplo para HTTPS +### Herramientas de Ejemplo para HTTPS { #example-tools-for-https } Algunas de las herramientas que podrías usar como Proxy de Terminación TLS son: @@ -55,11 +55,11 @@ Te mostraré algunos ejemplos concretos en los próximos capítulos. Luego, los siguientes conceptos a considerar son todos acerca del programa que ejecuta tu API real (por ejemplo, Uvicorn). -## Programa y Proceso +## Programa y Proceso { #program-and-process } Hablaremos mucho sobre el "**proceso**" en ejecución, así que es útil tener claridad sobre lo que significa y cuál es la diferencia con la palabra "**programa**". -### Qué es un Programa +### Qué es un Programa { #what-is-a-program } La palabra **programa** se usa comúnmente para describir muchas cosas: @@ -67,7 +67,7 @@ La palabra **programa** se usa comúnmente para describir muchas cosas: * El **archivo** que puede ser **ejecutado** por el sistema operativo, por ejemplo: `python`, `python.exe` o `uvicorn`. * Un programa específico mientras está siendo **ejecutado** en el sistema operativo, usando la CPU y almacenando cosas en la memoria. Esto también se llama **proceso**. -### Qué es un Proceso +### Qué es un Proceso { #what-is-a-process } La palabra **proceso** se usa normalmente de una manera más específica, refiriéndose solo a lo que está ejecutándose en el sistema operativo (como en el último punto anterior): @@ -88,11 +88,11 @@ Y, por ejemplo, probablemente verás que hay múltiples procesos ejecutando el m Ahora que conocemos la diferencia entre los términos **proceso** y **programa**, sigamos hablando sobre implementaciones. -## Ejecución al Iniciar +## Ejecución al Iniciar { #running-on-startup } En la mayoría de los casos, cuando creas una API web, quieres que esté **siempre en ejecución**, ininterrumpida, para que tus clientes puedan acceder a ella en cualquier momento. Esto, por supuesto, a menos que tengas una razón específica para que se ejecute solo en ciertas situaciones, pero la mayoría de las veces quieres que esté constantemente en ejecución y **disponible**. -### En un Servidor Remoto +### En un Servidor Remoto { #in-a-remote-server } Cuando configuras un servidor remoto (un servidor en la nube, una máquina virtual, etc.) lo más sencillo que puedes hacer es usar `fastapi run` (que utiliza Uvicorn) o algo similar, manualmente, de la misma manera que lo haces al desarrollar localmente. @@ -102,15 +102,15 @@ Pero si pierdes la conexión con el servidor, el **proceso en ejecución** proba Y si el servidor se reinicia (por ejemplo, después de actualizaciones o migraciones del proveedor de la nube) probablemente **no lo notarás**. Y debido a eso, ni siquiera sabrás que tienes que reiniciar el proceso manualmente. Así, tu API simplemente quedará muerta. 😱 -### Ejecutar Automáticamente al Iniciar +### Ejecutar Automáticamente al Iniciar { #run-automatically-on-startup } En general, probablemente querrás que el programa del servidor (por ejemplo, Uvicorn) se inicie automáticamente al arrancar el servidor, y sin necesidad de ninguna **intervención humana**, para tener siempre un proceso en ejecución con tu API (por ejemplo, Uvicorn ejecutando tu aplicación FastAPI). -### Programa Separado +### Programa Separado { #separate-program } Para lograr esto, normalmente tendrás un **programa separado** que se asegurará de que tu aplicación se ejecute al iniciarse. Y en muchos casos, también se asegurará de que otros componentes o aplicaciones se ejecuten, por ejemplo, una base de datos. -### Herramientas de Ejemplo para Ejecutar al Iniciar +### Herramientas de Ejemplo para Ejecutar al Iniciar { #example-tools-to-run-at-startup } Algunos ejemplos de las herramientas que pueden hacer este trabajo son: @@ -125,29 +125,29 @@ Algunos ejemplos de las herramientas que pueden hacer este trabajo son: Te daré más ejemplos concretos en los próximos capítulos. -## Reinicios +## Reinicios { #restarts } De manera similar a asegurarte de que tu aplicación se ejecute al iniciar, probablemente también quieras asegurarte de que se **reinicie** después de fallos. -### Cometemos Errores +### Cometemos Errores { #we-make-mistakes } Nosotros, como humanos, cometemos **errores**, todo el tiempo. El software casi *siempre* tiene **bugs** ocultos en diferentes lugares. 🐛 Y nosotros, como desarrolladores, seguimos mejorando el código a medida que encontramos esos bugs y a medida que implementamos nuevas funcionalidades (posiblemente agregando nuevos bugs también 😅). -### Errores Pequeños Manejados Automáticamente +### Errores Pequeños Manejados Automáticamente { #small-errors-automatically-handled } -Al construir APIs web con FastAPI, si hay un error en nuestro código, FastAPI normalmente lo contiene a la solicitud única que desencadenó el error. 🛡 +Al construir APIs web con FastAPI, si hay un error en nuestro código, FastAPI normalmente lo contiene al request único que desencadenó el error. 🛡 -El cliente obtendrá un **500 Internal Server Error** para esa solicitud, pero la aplicación continuará funcionando para las siguientes solicitudes en lugar de simplemente colapsar por completo. +El cliente obtendrá un **500 Internal Server Error** para ese request, pero la aplicación continuará funcionando para los siguientes requests en lugar de simplemente colapsar por completo. -### Errores Mayores - Colapsos +### Errores Mayores - Colapsos { #bigger-errors-crashes } Sin embargo, puede haber casos en los que escribamos algún código que **colapse toda la aplicación** haciendo que Uvicorn y Python colapsen. 💥 Y aún así, probablemente no querrías que la aplicación quede muerta porque hubo un error en un lugar, probablemente querrás que **siga ejecutándose** al menos para las *path operations* que no estén rotas. -### Reiniciar Después del Colapso +### Reiniciar Después del Colapso { #restart-after-crash } Pero en esos casos con errores realmente malos que colapsan el **proceso en ejecución**, querrías un componente externo encargado de **reiniciar** el proceso, al menos un par de veces... @@ -161,7 +161,7 @@ Así que enfoquémonos en los casos principales, donde podría colapsar por comp Probablemente querrías que la cosa encargada de reiniciar tu aplicación sea un **componente externo**, porque para ese punto, la misma aplicación con Uvicorn y Python ya colapsó, así que no hay nada en el mismo código de la misma aplicación que pueda hacer algo al respecto. -### Herramientas de Ejemplo para Reiniciar Automáticamente +### Herramientas de Ejemplo para Reiniciar Automáticamente { #example-tools-to-restart-automatically } En la mayoría de los casos, la misma herramienta que se utiliza para **ejecutar el programa al iniciar** también se utiliza para manejar reinicios automáticos. @@ -176,19 +176,19 @@ Por ejemplo, esto podría ser manejado por: * Manejado internamente por un proveedor de nube como parte de sus servicios * Otros... -## Replicación - Procesos y Memoria +## Replicación - Procesos y Memoria { #replication-processes-and-memory } Con una aplicación FastAPI, usando un programa servidor como el comando `fastapi` que ejecuta Uvicorn, ejecutarlo una vez en **un proceso** puede servir a múltiples clientes concurrentemente. Pero en muchos casos, querrás ejecutar varios worker processes al mismo tiempo. -### Múltiples Procesos - Workers +### Múltiples Procesos - Workers { #multiple-processes-workers } Si tienes más clientes de los que un solo proceso puede manejar (por ejemplo, si la máquina virtual no es muy grande) y tienes **múltiples núcleos** en la CPU del servidor, entonces podrías tener **múltiples procesos** ejecutando la misma aplicación al mismo tiempo, y distribuir todas las requests entre ellos. Cuando ejecutas **múltiples procesos** del mismo programa de API, comúnmente se les llama **workers**. -### Worker Processes y Puertos +### Worker Processes y Puertos { #worker-processes-and-ports } Recuerda de la documentación [Sobre HTTPS](https.md){.internal-link target=_blank} que solo un proceso puede estar escuchando en una combinación de puerto y dirección IP en un servidor. @@ -196,19 +196,19 @@ Esto sigue siendo cierto. Así que, para poder tener **múltiples procesos** al mismo tiempo, tiene que haber un **solo proceso escuchando en un puerto** que luego transmita la comunicación a cada worker process de alguna forma. -### Memoria por Proceso +### Memoria por Proceso { #memory-per-process } -Ahora, cuando el programa carga cosas en memoria, por ejemplo, un modelo de machine learning en una variable, o el contenido de un archivo grande en una variable, todo eso **consume un poco de la memoria (RAM)** del servidor. +Ahora, cuando el programa carga cosas en memoria, por ejemplo, un modelo de Machine Learning en una variable, o el contenido de un archivo grande en una variable, todo eso **consume un poco de la memoria (RAM)** del servidor. Y múltiples procesos normalmente **no comparten ninguna memoria**. Esto significa que cada proceso en ejecución tiene sus propias cosas, variables y memoria. Y si estás consumiendo una gran cantidad de memoria en tu código, **cada proceso** consumirá una cantidad equivalente de memoria. -### Memoria del Servidor +### Memoria del Servidor { #server-memory } Por ejemplo, si tu código carga un modelo de Machine Learning con **1 GB de tamaño**, cuando ejecutas un proceso con tu API, consumirá al menos 1 GB de RAM. Y si inicias **4 procesos** (4 workers), cada uno consumirá 1 GB de RAM. Así que, en total, tu API consumirá **4 GB de RAM**. Y si tu servidor remoto o máquina virtual solo tiene 3 GB de RAM, intentar cargar más de 4 GB de RAM causará problemas. 🚨 -### Múltiples Procesos - Un Ejemplo +### Múltiples Procesos - Un Ejemplo { #multiple-processes-an-example } En este ejemplo, hay un **Proceso Administrador** que inicia y controla dos **Worker Processes**. @@ -224,7 +224,7 @@ Un detalle interesante es que el porcentaje de **CPU utilizado** por cada proces Si tienes una API que hace una cantidad comparable de cálculos cada vez y tienes muchos clientes, entonces la **utilización de CPU** probablemente *también sea estable* (en lugar de constantemente subir y bajar rápidamente). -### Ejemplos de Herramientas y Estrategias de Replicación +### Ejemplos de Herramientas y Estrategias de Replicación { #examples-of-replication-tools-and-strategies } Puede haber varios enfoques para lograr esto, y te contaré más sobre estrategias específicas en los próximos capítulos, por ejemplo, al hablar sobre Docker y contenedores. @@ -247,7 +247,7 @@ Te contaré más sobre imágenes de contenedores, Docker, Kubernetes, etc. en un /// -## Pasos Previos Antes de Iniciar +## Pasos Previos Antes de Iniciar { #previous-steps-before-starting } Hay muchos casos en los que quieres realizar algunos pasos **antes de iniciar** tu aplicación. @@ -269,7 +269,7 @@ En ese caso, no tendrías que preocuparte por nada de esto. 🤷 /// -### Ejemplos de Estrategias para Pasos Previos +### Ejemplos de Estrategias para Pasos Previos { #examples-of-previous-steps-strategies } Esto **dependerá mucho** de la forma en que **implementarás tu sistema**, y probablemente estará conectado con la forma en que inicias programas, manejas reinicios, etc. @@ -285,7 +285,7 @@ Te daré más ejemplos concretos para hacer esto con contenedores en un capítul /// -## Utilización de Recursos +## Utilización de Recursos { #resource-utilization } Tu(s) servidor(es) es(son) un **recurso** que puedes consumir o **utilizar**, con tus programas, el tiempo de cómputo en las CPUs y la memoria RAM disponible. @@ -305,7 +305,7 @@ Podrías establecer un **número arbitrario** para alcanzar, por ejemplo, algo * Puedes usar herramientas simples como `htop` para ver la CPU y RAM utilizadas en tu servidor o la cantidad utilizada por cada proceso. O puedes usar herramientas de monitoreo más complejas, que pueden estar distribuidas a través de servidores, etc. -## Resumen +## Resumen { #recap } Has estado leyendo aquí algunos de los conceptos principales que probablemente necesitarás tener en mente al decidir cómo implementar tu aplicación: diff --git a/docs/es/docs/deployment/docker.md b/docs/es/docs/deployment/docker.md index 3a39d3661a..114a62ec35 100644 --- a/docs/es/docs/deployment/docker.md +++ b/docs/es/docs/deployment/docker.md @@ -1,4 +1,4 @@ -# FastAPI en Contenedores - Docker +# FastAPI en Contenedores - Docker { #fastapi-in-containers-docker } Al desplegar aplicaciones de FastAPI, un enfoque común es construir una **imagen de contenedor de Linux**. Normalmente se realiza usando **Docker**. Luego puedes desplegar esa imagen de contenedor de varias formas. @@ -6,7 +6,7 @@ Usar contenedores de Linux tiene varias ventajas, incluyendo **seguridad**, **re /// tip | Consejo -¿Tienes prisa y ya conoces esto? Salta al [`Dockerfile` más abajo 👇](#construir-una-imagen-de-docker-para-fastapi). +¿Tienes prisa y ya conoces esto? Salta al [`Dockerfile` más abajo 👇](#build-a-docker-image-for-fastapi). /// @@ -32,7 +32,7 @@ CMD ["fastapi", "run", "app/main.py", "--port", "80"] -## Qué es un Contenedor +## Qué es un Contenedor { #what-is-a-container } Los contenedores (principalmente contenedores de Linux) son una forma muy **ligera** de empaquetar aplicaciones incluyendo todas sus dependencias y archivos necesarios, manteniéndolos aislados de otros contenedores (otras aplicaciones o componentes) en el mismo sistema. @@ -42,11 +42,11 @@ De esta forma, los contenedores consumen **pocos recursos**, una cantidad compar Los contenedores también tienen sus propios procesos de ejecución **aislados** (normalmente solo un proceso), sistema de archivos y red, simplificando el despliegue, la seguridad, el desarrollo, etc. -## Qué es una Imagen de Contenedor +## Qué es una Imagen de Contenedor { #what-is-a-container-image } Un **contenedor** se ejecuta desde una **imagen de contenedor**. -Una imagen de contenedor es una versión **estática** de todos los archivos, variables de entorno y el comando/programa por defecto que debería estar presente en un contenedor. **Estático** aquí significa que la imagen de contenedor **no se está ejecutando**, no está siendo ejecutada, son solo los archivos empaquetados y los metadatos. +Una imagen de contenedor es una versión **estática** de todos los archivos, variables de entorno y el comando/programa por defecto que debería estar presente en un contenedor. **Estático** aquí significa que la **imagen** de contenedor no se está ejecutando, no está siendo ejecutada, son solo los archivos empaquetados y los metadatos. En contraste con una "**imagen de contenedor**" que son los contenidos estáticos almacenados, un "**contenedor**" normalmente se refiere a la instance en ejecución, lo que está siendo **ejecutado**. @@ -56,7 +56,7 @@ Una imagen de contenedor es comparable al archivo de **programa** y sus contenid Y el **contenedor** en sí (en contraste con la **imagen de contenedor**) es la instance real en ejecución de la imagen, comparable a un **proceso**. De hecho, un contenedor solo se está ejecutando cuando tiene un **proceso en ejecución** (y normalmente es solo un proceso). El contenedor se detiene cuando no hay un proceso en ejecución en él. -## Imágenes de Contenedor +## Imágenes de Contenedor { #container-images } Docker ha sido una de las herramientas principales para crear y gestionar **imágenes de contenedor** y **contenedores**. @@ -77,9 +77,9 @@ De esta manera, en muchos casos puedes aprender sobre contenedores y Docker y re Así, ejecutarías **múltiples contenedores** con diferentes cosas, como una base de datos, una aplicación de Python, un servidor web con una aplicación frontend en React, y conectarlos entre sí a través de su red interna. -Todos los sistemas de gestión de contenedores (como Docker o Kubernetes) tienen estas características de redes integradas en ellos. +Todos los sistemas de gestión de contenedores (como Docker o Kubernetes) tienen estas funcionalidades de redes integradas. -## Contenedores y Procesos +## Contenedores y Procesos { #containers-and-processes } Una **imagen de contenedor** normalmente incluye en sus metadatos el programa o comando por defecto que debería ser ejecutado cuando el **contenedor** se inicie y los parámetros que deben pasar a ese programa. Muy similar a lo que sería si estuviera en la línea de comandos. @@ -91,7 +91,7 @@ Un contenedor normalmente tiene un **proceso único**, pero también es posible Pero no es posible tener un contenedor en ejecución sin **al menos un proceso en ejecución**. Si el proceso principal se detiene, el contenedor se detiene. -## Construir una Imagen de Docker para FastAPI +## Construir una Imagen de Docker para FastAPI { #build-a-docker-image-for-fastapi } ¡Bien, construyamos algo ahora! 🚀 @@ -103,7 +103,7 @@ Esto es lo que querrías hacer en **la mayoría de los casos**, por ejemplo: * Al ejecutar en un **Raspberry Pi** * Usando un servicio en la nube que ejecutaría una imagen de contenedor por ti, etc. -### Requisitos del Paquete +### Requisitos del Paquete { #package-requirements } Normalmente tendrías los **requisitos del paquete** para tu aplicación en algún archivo. @@ -138,7 +138,7 @@ Existen otros formatos y herramientas para definir e instalar dependencias de pa /// -### Crear el Código de **FastAPI** +### Crear el Código de **FastAPI** { #create-the-fastapi-code } * Crea un directorio `app` y entra en él. * Crea un archivo vacío `__init__.py`. @@ -162,7 +162,7 @@ def read_item(item_id: int, q: Union[str, None] = None): return {"item_id": item_id, "q": q} ``` -### Dockerfile +### Dockerfile { #dockerfile } Ahora, en el mismo directorio del proyecto, crea un archivo `Dockerfile` con: @@ -238,7 +238,7 @@ Asegúrate de **siempre** usar la **forma exec** de la instrucción `CMD`, como /// -#### Usar `CMD` - Forma Exec +#### Usar `CMD` - Forma Exec { #use-cmd-exec-form } La instrucción Docker `CMD` se puede escribir usando dos formas: @@ -262,20 +262,20 @@ Puedes leer más sobre esto en las ¿Por qué mis servicios tardan 10 segundos en recrearse o detenerse?. -#### Estructura de Directorios +#### Estructura de Directorios { #directory-structure } Ahora deberías tener una estructura de directorios como: ``` . ├── app -│ ├── __init__.py +│   ├── __init__.py │ └── main.py ├── Dockerfile └── requirements.txt ``` -#### Detrás de un Proxy de Terminación TLS +#### Detrás de un Proxy de Terminación TLS { #behind-a-tls-termination-proxy } Si estás ejecutando tu contenedor detrás de un Proxy de Terminación TLS (load balancer) como Nginx o Traefik, añade la opción `--proxy-headers`, esto le dirá a Uvicorn (a través de la CLI de FastAPI) que confíe en los headers enviados por ese proxy indicando que la aplicación se está ejecutando detrás de HTTPS, etc. @@ -283,7 +283,7 @@ Si estás ejecutando tu contenedor detrás de un Proxy de Terminación TLS (load CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"] ``` -#### Cache de Docker +#### Caché de Docker { #docker-cache } Hay un truco importante en este `Dockerfile`, primero copiamos **el archivo con las dependencias solo**, no el resto del código. Déjame decirte por qué es así. @@ -315,7 +315,7 @@ Luego, cerca del final del `Dockerfile`, copiamos todo el código. Como esto es COPY ./app /code/app ``` -### Construir la Imagen de Docker +### Construir la Imagen de Docker { #build-the-docker-image } Ahora que todos los archivos están en su lugar, vamos a construir la imagen del contenedor. @@ -340,7 +340,7 @@ En este caso, es el mismo directorio actual (`.`). /// -### Iniciar el Contenedor Docker +### Iniciar el Contenedor Docker { #start-the-docker-container } * Ejecuta un contenedor basado en tu imagen: @@ -352,7 +352,7 @@ $ docker run -d --name mycontainer -p 80:80 myimage
-## Revísalo +## Revísalo { #check-it } Deberías poder revisarlo en la URL de tu contenedor de Docker, por ejemplo: http://192.168.99.100/items/5?q=somequery o http://127.0.0.1/items/5?q=somequery (o equivalente, usando tu host de Docker). @@ -362,7 +362,7 @@ Verás algo como: {"item_id": 5, "q": "somequery"} ``` -## Documentación Interactiva de la API +## Documentación Interactiva de la API { #interactive-api-docs } Ahora puedes ir a http://192.168.99.100/docs o http://127.0.0.1/docs (o equivalente, usando tu host de Docker). @@ -370,7 +370,7 @@ Verás la documentación interactiva automática de la API (proporcionada por http://192.168.99.100/redoc o http://127.0.0.1/redoc (o equivalente, usando tu host de Docker). @@ -378,7 +378,7 @@ Verás la documentación alternativa automática (proporcionada por cluster de máquinas con **Kubernetes**, Docker Swarm Mode, Nomad, u otro sistema complejo similar para gestionar contenedores distribuidos en varias máquinas, entonces probablemente querrás manejar la **replicación** a nivel de **cluster** en lugar de usar un **gestor de procesos** (como Uvicorn con workers) en cada contenedor. @@ -462,7 +462,7 @@ Uno de esos sistemas de gestión de contenedores distribuidos como Kubernetes no En esos casos, probablemente desearías construir una **imagen de Docker desde cero** como se [explica arriba](#dockerfile), instalando tus dependencias, y ejecutando **un solo proceso de Uvicorn** en lugar de usar múltiples workers de Uvicorn. -### Load Balancer +### Load Balancer { #load-balancer } Al usar contenedores, normalmente tendrías algún componente **escuchando en el puerto principal**. Podría posiblemente ser otro contenedor que es también un **Proxy de Terminación TLS** para manejar **HTTPS** o alguna herramienta similar. @@ -476,17 +476,17 @@ El mismo componente **Proxy de Terminación TLS** usado para HTTPS probablemente Y al trabajar con contenedores, el mismo sistema que usas para iniciarlos y gestionarlos ya tendría herramientas internas para transmitir la **comunicación en red** (e.g., requests HTTP) desde ese **load balancer** (que también podría ser un **Proxy de Terminación TLS**) a los contenedores con tu aplicación. -### Un Load Balancer - Múltiples Contenedores Worker +### Un Load Balancer - Múltiples Contenedores Worker { #one-load-balancer-multiple-worker-containers } Al trabajar con **Kubernetes** u otros sistemas de gestión de contenedores distribuidos similares, usar sus mecanismos de red internos permitiría que el único **load balancer** que está escuchando en el **puerto** principal transmita la comunicación (requests) a posiblemente **múltiples contenedores** ejecutando tu aplicación. Cada uno de estos contenedores ejecutando tu aplicación normalmente tendría **solo un proceso** (e.g., un proceso Uvicorn ejecutando tu aplicación FastAPI). Todos serían **contenedores idénticos**, ejecutando lo mismo, pero cada uno con su propio proceso, memoria, etc. De esa forma, aprovecharías la **paralelización** en **diferentes núcleos** de la CPU, o incluso en **diferentes máquinas**. -Y el sistema de contenedores distribuido con el **load balancer** **distribuiría las requests** a cada uno de los contenedores **replicados** que ejecutan tu aplicación **en turnos**. Así, cada request podría ser manejado por uno de los múltiples **contenedores replicados** ejecutando tu aplicación. +Y el sistema de contenedores distribuido con el **load balancer** **distribuiría las requests** a cada uno de los contenedores con tu aplicación **en turnos**. Así, cada request podría ser manejada por uno de los múltiples **contenedores replicados** ejecutando tu aplicación. -Y normalmente este **load balancer** podría manejar requests que vayan a *otras* aplicaciones en tu cluster (p. ej., a un dominio diferente, o bajo un prefijo de ruta de URL diferente), y transmitiría esa comunicación a los contenedores correctos para *esa otra* aplicación ejecutándose en tu cluster. +Y normalmente este **load balancer** podría manejar requests que vayan a *otras* aplicaciones en tu cluster (p. ej., a un dominio diferente, o bajo un prefijo de path de URL diferente), y transmitiría esa comunicación a los contenedores correctos para *esa otra* aplicación ejecutándose en tu cluster. -### Un Proceso por Contenedor +### Un Proceso por Contenedor { #one-process-per-container } En este tipo de escenario, probablemente querrías tener **un solo proceso (Uvicorn) por contenedor**, ya que ya estarías manejando la replicación a nivel de cluster. @@ -494,7 +494,7 @@ Así que, en este caso, **no** querrías tener múltiples workers en el contened Tener otro gestor de procesos dentro del contenedor (como sería con múltiples workers) solo añadiría **complejidad innecesaria** que probablemente ya estés manejando con tu sistema de cluster. -### Contenedores con Múltiples Procesos y Casos Especiales +### Contenedores con Múltiples Procesos y Casos Especiales { #containers-with-multiple-processes-and-special-cases } Por supuesto, hay **casos especiales** donde podrías querer tener **un contenedor** con varios **worker processes de Uvicorn** dentro. @@ -519,11 +519,11 @@ CMD ["fastapi", "run", "app/main.py", "--port", "80", "--workers", "4"] Aquí hay algunos ejemplos de cuándo eso podría tener sentido: -#### Una Aplicación Simple +#### Una Aplicación Simple { #a-simple-app } Podrías querer un gestor de procesos en el contenedor si tu aplicación es **lo suficientemente simple** que pueda ejecutarse en un **servidor único**, no un cluster. -#### Docker Compose +#### Docker Compose { #docker-compose } Podrías estar desplegando en un **servidor único** (no un cluster) con **Docker Compose**, por lo que no tendrías una forma fácil de gestionar la replicación de contenedores (con Docker Compose) mientras se preserva la red compartida y el **load balancing**. @@ -540,7 +540,7 @@ El punto principal es que, **ninguna** de estas son **reglas escritas en piedra* * Memoria * Pasos previos antes de comenzar -## Memoria +## Memoria { #memory } Si ejecutas **un solo proceso por contenedor**, tendrás una cantidad de memoria más o menos bien definida, estable y limitada consumida por cada uno de esos contenedores (más de uno si están replicados). @@ -550,11 +550,11 @@ Si tu aplicación es **simple**, probablemente esto **no será un problema**, y Si ejecutas **múltiples procesos por contenedor**, tendrás que asegurarte de que el número de procesos iniciados no **consuma más memoria** de la que está disponible. -## Pasos Previos Antes de Comenzar y Contenedores +## Pasos Previos Antes de Comenzar y Contenedores { #previous-steps-before-starting-and-containers } Si estás usando contenedores (por ejemplo, Docker, Kubernetes), entonces hay dos enfoques principales que puedes usar. -### Múltiples Contenedores +### Múltiples Contenedores { #multiple-containers } Si tienes **múltiples contenedores**, probablemente cada uno ejecutando un **proceso único** (por ejemplo, en un cluster de **Kubernetes**), entonces probablemente querrías tener un **contenedor separado** realizando el trabajo de los **pasos previos** en un solo contenedor, ejecutando un solo proceso, **antes** de ejecutar los contenedores worker replicados. @@ -566,13 +566,13 @@ Si estás usando Kubernetes, probablemente sería un tiangolo/uvicorn-gunicorn-fastapi. Pero ahora está obsoleta. ⛔️ +Solía haber una imagen official de Docker de FastAPI: tiangolo/uvicorn-gunicorn-fastapi-docker. Pero ahora está obsoleta. ⛔️ Probablemente **no** deberías usar esta imagen base de Docker (o cualquier otra similar). @@ -588,7 +588,7 @@ Pero ahora que Uvicorn (y el comando `fastapi`) soportan el uso de `--workers`, /// -## Desplegar la Imagen del Contenedor +## Desplegar la Imagen del Contenedor { #deploy-the-container-image } Después de tener una Imagen de Contenedor (Docker) hay varias maneras de desplegarla. @@ -600,11 +600,11 @@ Por ejemplo: * Con otra herramienta como Nomad * Con un servicio en la nube que tome tu imagen de contenedor y la despliegue -## Imagen de Docker con `uv` +## Imagen de Docker con `uv` { #docker-image-with-uv } Si estás usando uv para instalar y gestionar tu proyecto, puedes seguir su guía de Docker de uv. -## Resumen +## Resumen { #recap } Usando sistemas de contenedores (por ejemplo, con **Docker** y **Kubernetes**) se vuelve bastante sencillo manejar todos los **conceptos de despliegue**: diff --git a/docs/es/docs/deployment/https.md b/docs/es/docs/deployment/https.md index 3ccb5dc474..e62bf8b153 100644 --- a/docs/es/docs/deployment/https.md +++ b/docs/es/docs/deployment/https.md @@ -1,4 +1,4 @@ -# Sobre HTTPS +# Sobre HTTPS { #about-https } Es fácil asumir que HTTPS es algo que simplemente está "activado" o no. @@ -28,7 +28,7 @@ Ahora, desde una **perspectiva de desarrollador**, aquí hay varias cosas a tene * **Por defecto**, eso significaría que solo puedes tener **un certificado HTTPS por dirección IP**. * No importa cuán grande sea tu servidor o qué tan pequeña pueda ser cada aplicación que tengas en él. * Sin embargo, hay una **solución** para esto. -* Hay una **extensión** para el protocolo **TLS** (el que maneja la encriptación a nivel de TCP, antes de HTTP) llamada **SNI**. +* Hay una **extensión** para el protocolo **TLS** (el que maneja la encriptación a nivel de TCP, antes de HTTP) llamada **SNI**. * Esta extensión SNI permite que un solo servidor (con una **sola dirección IP**) tenga **varios certificados HTTPS** y sirva **múltiples dominios/aplicaciones HTTPS**. * Para que esto funcione, un componente (programa) **único** que se ejecute en el servidor, escuchando en la **dirección IP pública**, debe tener **todos los certificados HTTPS** en el servidor. * **Después** de obtener una conexión segura, el protocolo de comunicación sigue siendo **HTTP**. @@ -43,7 +43,7 @@ Algunas de las opciones que podrías usar como un TLS Termination Proxy son: * Nginx * HAProxy -## Let's Encrypt +## Let's Encrypt { #lets-encrypt } Antes de Let's Encrypt, estos **certificados HTTPS** eran vendidos por terceros. @@ -51,17 +51,17 @@ El proceso para adquirir uno de estos certificados solía ser complicado, requer Pero luego se creó **Let's Encrypt**. -Es un proyecto de la Linux Foundation. Proporciona **certificados HTTPS de forma gratuita**, de manera automatizada. Estos certificados usan toda la seguridad criptográfica estándar, y tienen una corta duración (aproximadamente 3 meses), por lo que la **seguridad es en realidad mejor** debido a su corta vida útil. +Es un proyecto de la Linux Foundation. Proporciona **certificados HTTPS de forma gratuita**, de manera automatizada. Estos certificados usan toda la seguridad criptográfica estándar, y tienen una corta duración (aproximadamente 3 meses), por lo que la **seguridad es en realidad mejor** debido a su lifespan reducida. Los dominios son verificados de manera segura y los certificados se generan automáticamente. Esto también permite automatizar la renovación de estos certificados. La idea es automatizar la adquisición y renovación de estos certificados para que puedas tener **HTTPS seguro, gratuito, para siempre**. -## HTTPS para Desarrolladores +## HTTPS para Desarrolladores { #https-for-developers } Aquí tienes un ejemplo de cómo podría ser una API HTTPS, paso a paso, prestando atención principalmente a las ideas importantes para los desarrolladores. -### Nombre de Dominio +### Nombre de Dominio { #domain-name } Probablemente todo comenzaría adquiriendo un **nombre de dominio**. Luego, lo configurarías en un servidor DNS (posiblemente tu mismo proveedor de la nube). @@ -77,7 +77,7 @@ Esta parte del Nombre de Dominio es mucho antes de HTTPS, pero como todo depende /// -### DNS +### DNS { #dns } Ahora centrémonos en todas las partes realmente de HTTPS. @@ -87,7 +87,7 @@ Los servidores DNS le dirían al navegador que use una **dirección IP** especí -### Inicio del Handshake TLS +### Inicio del Handshake TLS { #tls-handshake-start } El navegador luego se comunicaría con esa dirección IP en el **puerto 443** (el puerto HTTPS). @@ -97,7 +97,7 @@ La primera parte de la comunicación es solo para establecer la conexión entre Esta interacción entre el cliente y el servidor para establecer la conexión TLS se llama **handshake TLS**. -### TLS con Extensión SNI +### TLS con Extensión SNI { #tls-with-sni-extension } **Solo un proceso** en el servidor puede estar escuchando en un **puerto** específico en una **dirección IP** específica. Podría haber otros procesos escuchando en otros puertos en la misma dirección IP, pero solo uno para cada combinación de dirección IP y puerto. @@ -127,7 +127,7 @@ Ten en cuenta que la encriptación de la comunicación ocurre a nivel de **TCP** /// -### Request HTTPS +### Request HTTPS { #https-request } Ahora que el cliente y el servidor (específicamente el navegador y el TLS Termination Proxy) tienen una **conexión TCP encriptada**, pueden iniciar la **comunicación HTTP**. @@ -135,19 +135,19 @@ Así que, el cliente envía un **request HTTPS**. Esto es simplemente un request -### Desencriptar el Request +### Desencriptar el Request { #decrypt-the-request } El TLS Termination Proxy usaría la encriptación acordada para **desencriptar el request**, y transmitiría el **request HTTP simple (desencriptado)** al proceso que ejecuta la aplicación (por ejemplo, un proceso con Uvicorn ejecutando la aplicación FastAPI). -### Response HTTP +### Response HTTP { #http-response } La aplicación procesaría el request y enviaría un **response HTTP simple (sin encriptar)** al TLS Termination Proxy. -### Response HTTPS +### Response HTTPS { #https-response } El TLS Termination Proxy entonces **encriptaría el response** usando la criptografía acordada antes (que comenzó con el certificado para `someapp.example.com`), y lo enviaría de vuelta al navegador. @@ -157,7 +157,7 @@ Luego, el navegador verificaría que el response sea válido y encriptado con la El cliente (navegador) sabrá que el response proviene del servidor correcto porque está utilizando la criptografía que acordaron usando el **certificado HTTPS** anteriormente. -### Múltiples Aplicaciones +### Múltiples Aplicaciones { #multiple-applications } En el mismo servidor (o servidores), podrían haber **múltiples aplicaciones**, por ejemplo, otros programas API o una base de datos. @@ -167,7 +167,7 @@ Solo un proceso puede estar gestionando la IP y puerto específica (el TLS Termi De esa manera, el TLS Termination Proxy podría gestionar HTTPS y certificados para **múltiples dominios**, para múltiples aplicaciones, y luego transmitir los requests a la aplicación correcta en cada caso. -### Renovación de Certificados +### Renovación de Certificados { #certificate-renewal } En algún momento en el futuro, cada certificado **expiraría** (alrededor de 3 meses después de haberlo adquirido). @@ -190,7 +190,39 @@ Para hacer eso, y para acomodar diferentes necesidades de aplicaciones, hay vari Todo este proceso de renovación, mientras aún se sirve la aplicación, es una de las principales razones por las que querrías tener un **sistema separado para gestionar el HTTPS** con un TLS Termination Proxy en lugar de simplemente usar los certificados TLS con el servidor de aplicaciones directamente (por ejemplo, Uvicorn). -## Resumen +## Headers reenviados por el proxy { #proxy-forwarded-headers } + +Al usar un proxy para gestionar HTTPS, tu **servidor de aplicaciones** (por ejemplo Uvicorn vía FastAPI CLI) no sabe nada sobre el proceso HTTPS, se comunica con HTTP simple con el **TLS Termination Proxy**. + +Este **proxy** normalmente configuraría algunos headers HTTP sobre la marcha antes de transmitir el request al **servidor de aplicaciones**, para hacerle saber al servidor de aplicaciones que el request está siendo **reenviado** por el proxy. + +/// note | Detalles técnicos + +Los headers del proxy son: + +* X-Forwarded-For +* X-Forwarded-Proto +* X-Forwarded-Host + +/// + +Aun así, como el **servidor de aplicaciones** no sabe que está detrás de un **proxy** de confianza, por defecto, no confiaría en esos headers. + +Pero puedes configurar el **servidor de aplicaciones** para confiar en los headers reenviados enviados por el **proxy**. Si estás usando FastAPI CLI, puedes usar la *Opción de la CLI* `--forwarded-allow-ips` para indicarle desde qué IPs debería confiar en esos headers reenviados. + +Por ejemplo, si el **servidor de aplicaciones** solo está recibiendo comunicación del **proxy** de confianza, puedes establecerlo en `--forwarded-allow-ips="*"` para hacer que confíe en todas las IPs entrantes, ya que solo recibirá requests desde la IP que sea utilizada por el **proxy**. + +De esta manera la aplicación podrá saber cuál es su propia URL pública, si está usando HTTPS, el dominio, etc. + +Esto sería útil, por ejemplo, para manejar correctamente redirecciones. + +/// tip | Consejo + +Puedes aprender más sobre esto en la documentación de [Detrás de un proxy - Habilitar headers reenviados por el proxy](../advanced/behind-a-proxy.md#enable-proxy-forwarded-headers){.internal-link target=_blank} + +/// + +## Resumen { #recap } Tener **HTTPS** es muy importante y bastante **crítico** en la mayoría de los casos. La mayor parte del esfuerzo que como desarrollador tienes que poner en torno a HTTPS es solo sobre **entender estos conceptos** y cómo funcionan. diff --git a/docs/es/docs/deployment/index.md b/docs/es/docs/deployment/index.md index 3b6dcc05d3..1260c68b98 100644 --- a/docs/es/docs/deployment/index.md +++ b/docs/es/docs/deployment/index.md @@ -1,8 +1,8 @@ -# Despliegue +# Despliegue { #deployment } Desplegar una aplicación **FastAPI** es relativamente fácil. -## Qué Significa Despliegue +## Qué Significa Despliegue { #what-does-deployment-mean } **Desplegar** una aplicación significa realizar los pasos necesarios para hacerla **disponible para los usuarios**. @@ -10,12 +10,14 @@ Para una **API web**, normalmente implica ponerla en una **máquina remota**, co Esto contrasta con las etapas de **desarrollo**, donde estás constantemente cambiando el código, rompiéndolo y arreglándolo, deteniendo y reiniciando el servidor de desarrollo, etc. -## Estrategias de Despliegue +## Estrategias de Despliegue { #deployment-strategies } Hay varias maneras de hacerlo dependiendo de tu caso de uso específico y las herramientas que utilices. Podrías **desplegar un servidor** tú mismo utilizando una combinación de herramientas, podrías usar un **servicio en la nube** que hace parte del trabajo por ti, u otras opciones posibles. +Por ejemplo, nosotros, el equipo detrás de FastAPI, construimos **FastAPI Cloud**, para hacer que desplegar aplicaciones de FastAPI en la nube sea lo más ágil posible, con la misma experiencia de desarrollador de trabajar con FastAPI. + Te mostraré algunos de los conceptos principales que probablemente deberías tener en cuenta al desplegar una aplicación **FastAPI** (aunque la mayoría se aplica a cualquier otro tipo de aplicación web). Verás más detalles a tener en cuenta y algunas de las técnicas para hacerlo en las siguientes secciones. ✨ diff --git a/docs/es/docs/deployment/server-workers.md b/docs/es/docs/deployment/server-workers.md index 1a1127ca6a..9cdd79bc0f 100644 --- a/docs/es/docs/deployment/server-workers.md +++ b/docs/es/docs/deployment/server-workers.md @@ -1,4 +1,4 @@ -# Servidores Workers - Uvicorn con Workers +# Servidores Workers - Uvicorn con Workers { #server-workers-uvicorn-with-workers } Vamos a revisar esos conceptos de despliegue de antes: @@ -25,7 +25,7 @@ En particular, cuando corras en **Kubernetes** probablemente **no** querrás usa /// -## Múltiples Workers +## Múltiples Workers { #multiple-workers } Puedes iniciar múltiples workers con la opción de línea de comando `--workers`: @@ -36,56 +36,43 @@ Si usas el comando `fastapi`:
```console -$
 fastapi run --workers 4 main.py
-INFO     Using path main.py
-INFO     Resolved absolute path /home/user/code/awesomeapp/main.py
-INFO     Searching for package file structure from directories with __init__.py files
-INFO     Importing from /home/user/code/awesomeapp
+$ fastapi run --workers 4 main.py
 
- ╭─ Python module file ─╮
- │                      │
- │  🐍 main.py          │
- │                      │
- ╰──────────────────────╯
+   FastAPI   Starting production server 🚀
 
-INFO     Importing module main
-INFO     Found importable FastAPI app
+             Searching for package file structure from directories with
+             __init__.py files
+             Importing from /home/user/code/awesomeapp
 
- ╭─ Importable FastAPI app ─╮
- │                          │
- │  from main import app    │
- │                          │
- ╰──────────────────────────╯
+    module   🐍 main.py
 
-INFO     Using import string main:app
+      code   Importing the FastAPI app object from the module with the
+             following code:
 
- ╭─────────── FastAPI CLI - Production mode ───────────╮
- │                                                     │
- │  Serving at: http://0.0.0.0:8000                    │
- │                                                     │
- │  API docs: http://0.0.0.0:8000/docs                 │
- │                                                     │
- │  Running in production mode, for development use:   │
- │                                                     │
- fastapi dev
- │                                                     │
- ╰─────────────────────────────────────────────────────╯
+             from main import app
 
-INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
-INFO:     Started parent process [27365]
-INFO:     Started server process [27368]
-INFO:     Waiting for application startup.
-INFO:     Application startup complete.
-INFO:     Started server process [27369]
-INFO:     Waiting for application startup.
-INFO:     Application startup complete.
-INFO:     Started server process [27370]
-INFO:     Waiting for application startup.
-INFO:     Application startup complete.
-INFO:     Started server process [27367]
-INFO:     Waiting for application startup.
-INFO:     Application startup complete.
-
+ app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to + quit) + INFO Started parent process [27365] + INFO Started server process [27368] + INFO Started server process [27369] + INFO Started server process [27370] + INFO Started server process [27367] + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Waiting for application startup. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. + INFO Application startup complete. ```
@@ -124,7 +111,7 @@ La única opción nueva aquí es `--workers` indicando a Uvicorn que inicie 4 wo También puedes ver que muestra el **PID** de cada proceso, `27365` para el proceso padre (este es el **gestor de procesos**) y uno para cada worker process: `27368`, `27369`, `27370`, y `27367`. -## Conceptos de Despliegue +## Conceptos de Despliegue { #deployment-concepts } Aquí viste cómo usar múltiples **workers** para **paralelizar** la ejecución de la aplicación, aprovechar los **múltiples núcleos** del CPU, y poder servir **más requests**. @@ -137,13 +124,13 @@ De la lista de conceptos de despliegue de antes, usar workers ayudaría principa * **Memoria** * **Pasos previos antes de empezar** -## Contenedores y Docker +## Contenedores y Docker { #containers-and-docker } En el próximo capítulo sobre [FastAPI en Contenedores - Docker](docker.md){.internal-link target=_blank} te explicaré algunas estrategias que podrías usar para manejar los otros **conceptos de despliegue**. Te mostraré cómo **construir tu propia imagen desde cero** para ejecutar un solo proceso de Uvicorn. Es un proceso sencillo y probablemente es lo que querrías hacer al usar un sistema de gestión de contenedores distribuido como **Kubernetes**. -## Resumen +## Resumen { #recap } Puedes usar múltiples worker processes con la opción CLI `--workers` con los comandos `fastapi` o `uvicorn` para aprovechar los **CPUs de múltiples núcleos**, para ejecutar **múltiples procesos en paralelo**. diff --git a/docs/es/docs/deployment/versions.md b/docs/es/docs/deployment/versions.md index d16ecf0a5b..193654b2d2 100644 --- a/docs/es/docs/deployment/versions.md +++ b/docs/es/docs/deployment/versions.md @@ -1,4 +1,4 @@ -# Sobre las versiones de FastAPI +# Sobre las versiones de FastAPI { #about-fastapi-versions } **FastAPI** ya se está utilizando en producción en muchas aplicaciones y sistemas. Y la cobertura de tests se mantiene al 100%. Pero su desarrollo sigue avanzando rápidamente. @@ -8,7 +8,7 @@ Por eso las versiones actuales siguen siendo `0.x.x`, esto refleja que cada vers Puedes crear aplicaciones de producción con **FastAPI** ahora mismo (y probablemente ya lo has estado haciendo desde hace algún tiempo), solo debes asegurarte de que utilizas una versión que funciona correctamente con el resto de tu código. -## Fijar tu versión de `fastapi` +## Fija tu versión de `fastapi` { #pin-your-fastapi-version } Lo primero que debes hacer es "fijar" la versión de **FastAPI** que estás usando a la versión específica más reciente que sabes que funciona correctamente para tu aplicación. @@ -32,11 +32,11 @@ eso significaría que usarías las versiones `0.112.0` o superiores, pero menore Si utilizas cualquier otra herramienta para gestionar tus instalaciones, como `uv`, Poetry, Pipenv, u otras, todas tienen una forma que puedes usar para definir versiones específicas para tus paquetes. -## Versiones disponibles +## Versiones disponibles { #available-versions } Puedes ver las versiones disponibles (por ejemplo, para revisar cuál es la más reciente) en las [Release Notes](../release-notes.md){.internal-link target=_blank}. -## Sobre las versiones +## Sobre las versiones { #about-versions } Siguiendo las convenciones del Semantic Versioning, cualquier versión por debajo de `1.0.0` podría potencialmente añadir cambios incompatibles. @@ -62,7 +62,7 @@ El "MINOR" es el número en el medio, por ejemplo, en `0.2.3`, la versión MINOR /// -## Actualizando las versiones de FastAPI +## Actualizando las versiones de FastAPI { #upgrading-the-fastapi-versions } Deberías añadir tests para tu aplicación. @@ -72,7 +72,7 @@ Después de tener tests, puedes actualizar la versión de **FastAPI** a una más Si todo está funcionando, o después de hacer los cambios necesarios, y todos tus tests pasan, entonces puedes fijar tu `fastapi` a esa nueva versión más reciente. -## Sobre Starlette +## Sobre Starlette { #about-starlette } No deberías fijar la versión de `starlette`. @@ -80,7 +80,7 @@ Diferentes versiones de **FastAPI** utilizarán una versión más reciente espec Así que, puedes simplemente dejar que **FastAPI** use la versión correcta de Starlette. -## Sobre Pydantic +## Sobre Pydantic { #about-pydantic } Pydantic incluye los tests para **FastAPI** con sus propios tests, así que nuevas versiones de Pydantic (por encima de `1.0.0`) siempre son compatibles con FastAPI. diff --git a/docs/es/docs/environment-variables.md b/docs/es/docs/environment-variables.md index 8e85b413cd..1b0941a7f5 100644 --- a/docs/es/docs/environment-variables.md +++ b/docs/es/docs/environment-variables.md @@ -1,4 +1,4 @@ -# Variables de Entorno +# Variables de Entorno { #environment-variables } /// tip | Consejo @@ -10,7 +10,7 @@ Una variable de entorno (también conocida como "**env var**") es una variable q Las variables de entorno pueden ser útiles para manejar **configuraciones** de aplicaciones, como parte de la **instalación** de Python, etc. -## Crear y Usar Variables de Entorno +## Crear y Usar Variables de Entorno { #create-and-use-env-vars } Puedes **crear** y usar variables de entorno en la **shell (terminal)**, sin necesidad de Python: @@ -50,7 +50,7 @@ Hello Wade Wilson //// -## Leer Variables de Entorno en Python +## Leer Variables de Entorno en Python { #read-env-vars-in-python } También podrías crear variables de entorno **fuera** de Python, en la terminal (o con cualquier otro método), y luego **leerlas en Python**. @@ -157,7 +157,7 @@ Puedes leer más al respecto en parámetros adicionales de Swagger UI. @@ -8,7 +8,7 @@ Para configurarlos, pasa el argumento `swagger_ui_parameters` al crear el objeto FastAPI convierte las configuraciones a **JSON** para hacerlas compatibles con JavaScript, ya que eso es lo que Swagger UI necesita. -## Desactivar el resaltado de sintaxis +## Desactivar el resaltado de sintaxis { #disable-syntax-highlighting } Por ejemplo, podrías desactivar el resaltado de sintaxis en Swagger UI. @@ -24,7 +24,7 @@ Pero puedes desactivarlo estableciendo `syntaxHighlight` en `False`: -## Cambiar el tema +## Cambiar el tema { #change-the-theme } De la misma manera, podrías configurar el tema del resaltado de sintaxis con la clave `"syntaxHighlight.theme"` (ten en cuenta que tiene un punto en el medio): @@ -34,13 +34,13 @@ Esa configuración cambiaría el tema de color del resaltado de sintaxis: -## Cambiar los parámetros predeterminados de Swagger UI +## Cambiar los parámetros predeterminados de Swagger UI { #change-default-swagger-ui-parameters } FastAPI incluye algunos parámetros de configuración predeterminados apropiados para la mayoría de los casos de uso. Incluye estas configuraciones predeterminadas: -{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} Puedes sobrescribir cualquiera de ellos estableciendo un valor diferente en el argumento `swagger_ui_parameters`. @@ -48,11 +48,11 @@ Por ejemplo, para desactivar `deepLinking` podrías pasar estas configuraciones {* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} -## Otros parámetros de Swagger UI +## Otros parámetros de Swagger UI { #other-swagger-ui-parameters } Para ver todas las demás configuraciones posibles que puedes usar, lee la documentación oficial de los parámetros de Swagger UI. -## Configuraciones solo de JavaScript +## Configuraciones solo de JavaScript { #javascript-only-settings } Swagger UI también permite otras configuraciones que son objetos **solo de JavaScript** (por ejemplo, funciones de JavaScript). diff --git a/docs/es/docs/how-to/custom-docs-ui-assets.md b/docs/es/docs/how-to/custom-docs-ui-assets.md index 0a03ff330b..e9f9770265 100644 --- a/docs/es/docs/how-to/custom-docs-ui-assets.md +++ b/docs/es/docs/how-to/custom-docs-ui-assets.md @@ -1,26 +1,26 @@ -# Recursos Estáticos Personalizados para la Docs UI (Self-Hosting) +# Recursos Estáticos Personalizados para la Docs UI (self hosting) { #custom-docs-ui-static-assets-self-hosting } La documentación de la API utiliza **Swagger UI** y **ReDoc**, y cada uno de estos necesita algunos archivos JavaScript y CSS. -Por defecto, esos archivos se sirven desde un CDN. +Por defecto, esos archivos se sirven desde un CDN. Pero es posible personalizarlo, puedes establecer un CDN específico, o servir los archivos tú mismo. -## CDN Personalizado para JavaScript y CSS +## CDN Personalizado para JavaScript y CSS { #custom-cdn-for-javascript-and-css } -Digamos que quieres usar un CDN diferente, por ejemplo, quieres usar `https://unpkg.com/`. +Digamos que quieres usar un CDN diferente, por ejemplo, quieres usar `https://unpkg.com/`. Esto podría ser útil si, por ejemplo, vives en un país que restringe algunas URLs. -### Desactiva la documentación automática +### Desactiva la documentación automática { #disable-the-automatic-docs } -El primer paso es desactivar la documentación automática, ya que por defecto, esos usan el CDN predeterminado. +El primer paso es desactivar la documentación automática, ya que por defecto, esos usan el CDN por defecto. Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de `FastAPI`: {* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} -### Incluye la documentación personalizada +### Incluye la documentación personalizada { #include-the-custom-docs } Ahora puedes crear las *path operations* para la documentación personalizada. @@ -28,7 +28,7 @@ Puedes reutilizar las funciones internas de FastAPI para crear las páginas HTML * `openapi_url`: la URL donde la página HTML para la documentación puede obtener el OpenAPI esquema de tu API. Puedes usar aquí el atributo `app.openapi_url`. * `title`: el título de tu API. -* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor predeterminado. +* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor por defecto. * `swagger_js_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **JavaScript**. Esta es la URL personalizada del CDN. * `swagger_css_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **CSS**. Esta es la URL personalizada del CDN. @@ -46,23 +46,23 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d /// -### Crea una *path operation* para probarlo +### Crea una *path operation* para probarlo { #create-a-path-operation-to-test-it } Ahora, para poder probar que todo funciona, crea una *path operation*: {* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} -### Pruébalo +### Pruébalo { #test-it } Ahora, deberías poder ir a tu documentación en http://127.0.0.1:8000/docs, y recargar la página, cargará esos recursos desde el nuevo CDN. -## Self-hosting de JavaScript y CSS para la documentación +## self hosting de JavaScript y CSS para la documentación { #self-hosting-javascript-and-css-for-docs } -El self-hosting de JavaScript y CSS podría ser útil si, por ejemplo, necesitas que tu aplicación siga funcionando incluso offline, sin acceso a Internet, o en una red local. +El self hosting de JavaScript y CSS podría ser útil si, por ejemplo, necesitas que tu aplicación siga funcionando incluso offline, sin acceso a Internet, o en una red local. Aquí verás cómo servir esos archivos tú mismo, en la misma aplicación de FastAPI, y configurar la documentación para usarla. -### Estructura de archivos del proyecto +### Estructura de archivos del proyecto { #project-file-structure } Supongamos que la estructura de archivos de tu proyecto se ve así: @@ -85,7 +85,7 @@ Tu nueva estructura de archivos podría verse así: └── static/ ``` -### Descarga los archivos +### Descarga los archivos { #download-the-files } Descarga los archivos estáticos necesarios para la documentación y ponlos en ese directorio `static/`. @@ -113,14 +113,14 @@ Después de eso, tu estructura de archivos podría verse así: └── swagger-ui.css ``` -### Sirve los archivos estáticos +### Sirve los archivos estáticos { #serve-the-static-files } * Importa `StaticFiles`. * "Monta" una instance de `StaticFiles()` en un path específico. {* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} -### Prueba los archivos estáticos +### Prueba los archivos estáticos { #test-the-static-files } Inicia tu aplicación y ve a http://127.0.0.1:8000/static/redoc.standalone.js. @@ -138,7 +138,7 @@ Eso confirma que puedes servir archivos estáticos desde tu aplicación, y que c Ahora podemos configurar la aplicación para usar esos archivos estáticos para la documentación. -### Desactiva la documentación automática para archivos estáticos +### Desactiva la documentación automática para archivos estáticos { #disable-the-automatic-docs-for-static-files } Igual que cuando usas un CDN personalizado, el primer paso es desactivar la documentación automática, ya que esos usan el CDN por defecto. @@ -146,7 +146,7 @@ Para desactivarlos, establece sus URLs en `None` cuando crees tu aplicación de {* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} -### Incluye la documentación personalizada para archivos estáticos +### Incluye la documentación personalizada para archivos estáticos { #include-the-custom-docs-for-static-files } Y de la misma manera que con un CDN personalizado, ahora puedes crear las *path operations* para la documentación personalizada. @@ -154,7 +154,7 @@ Nuevamente, puedes reutilizar las funciones internas de FastAPI para crear las p * `openapi_url`: la URL donde la página HTML para la documentación puede obtener el OpenAPI esquema de tu API. Puedes usar aquí el atributo `app.openapi_url`. * `title`: el título de tu API. -* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor predeterminado. +* `oauth2_redirect_url`: puedes usar `app.swagger_ui_oauth2_redirect_url` aquí para usar el valor por defecto. * `swagger_js_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **JavaScript**. **Este es el que tu propia aplicación está sirviendo ahora**. * `swagger_css_url`: la URL donde el HTML para tu documentación de Swagger UI puede obtener el archivo **CSS**. **Este es el que tu propia aplicación está sirviendo ahora**. @@ -172,13 +172,13 @@ Swagger UI lo manejará detrás de escena para ti, pero necesita este auxiliar d /// -### Crea una *path operation* para probar archivos estáticos +### Crea una *path operation* para probar archivos estáticos { #create-a-path-operation-to-test-static-files } Ahora, para poder probar que todo funciona, crea una *path operation*: {* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} -### Prueba la UI de Archivos Estáticos +### Prueba la UI de Archivos Estáticos { #test-static-files-ui } Ahora, deberías poder desconectar tu WiFi, ir a tu documentación en http://127.0.0.1:8000/docs, y recargar la página. diff --git a/docs/es/docs/how-to/custom-request-and-route.md b/docs/es/docs/how-to/custom-request-and-route.md index a6ea657d15..ff13196f8a 100644 --- a/docs/es/docs/how-to/custom-request-and-route.md +++ b/docs/es/docs/how-to/custom-request-and-route.md @@ -1,4 +1,4 @@ -# Clase personalizada de Request y APIRoute +# Clase personalizada de Request y APIRoute { #custom-request-and-apiroute-class } En algunos casos, puede que quieras sobrescribir la lógica utilizada por las clases `Request` y `APIRoute`. @@ -14,7 +14,7 @@ Si apenas estás comenzando con **FastAPI**, quizás quieras saltar esta secció /// -## Casos de uso +## Casos de uso { #use-cases } Algunos casos de uso incluyen: @@ -22,13 +22,13 @@ Algunos casos de uso incluyen: * Descomprimir cuerpos de requests comprimidos con gzip. * Registrar automáticamente todos los request bodies. -## Manejo de codificaciones personalizadas de request body +## Manejo de codificaciones personalizadas de request body { #handling-custom-request-body-encodings } Veamos cómo hacer uso de una subclase personalizada de `Request` para descomprimir requests gzip. Y una subclase de `APIRoute` para usar esa clase de request personalizada. -### Crear una clase personalizada `GzipRequest` +### Crear una clase personalizada `GzipRequest` { #create-a-custom-gziprequest-class } /// tip | Consejo @@ -42,9 +42,9 @@ Si no hay `gzip` en el header, no intentará descomprimir el cuerpo. De esa manera, la misma clase de ruta puede manejar requests comprimidos con gzip o no comprimidos. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} -### Crear una clase personalizada `GzipRoute` +### Crear una clase personalizada `GzipRoute` { #create-a-custom-gziproute-class } A continuación, creamos una subclase personalizada de `fastapi.routing.APIRoute` que hará uso de `GzipRequest`. @@ -54,7 +54,7 @@ Este método devuelve una función. Y esa función es la que recibirá un reques Aquí lo usamos para crear un `GzipRequest` a partir del request original. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} /// note | Detalles técnicos @@ -78,7 +78,7 @@ Después de eso, toda la lógica de procesamiento es la misma. Pero debido a nuestros cambios en `GzipRequest.body`, el request body se descomprimirá automáticamente cuando sea cargado por **FastAPI** si es necesario. -## Accediendo al request body en un manejador de excepciones +## Accediendo al request body en un manejador de excepciones { #accessing-the-request-body-in-an-exception-handler } /// tip | Consejo @@ -92,18 +92,18 @@ También podemos usar este mismo enfoque para acceder al request body en un mane Todo lo que necesitamos hacer es manejar el request dentro de un bloque `try`/`except`: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} Si ocurre una excepción, la `Request instance` aún estará en el alcance, así que podemos leer y hacer uso del request body cuando manejamos el error: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} -## Clase personalizada `APIRoute` en un router +## Clase personalizada `APIRoute` en un router { #custom-apiroute-class-in-a-router } También puedes establecer el parámetro `route_class` de un `APIRouter`: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} En este ejemplo, las *path operations* bajo el `router` usarán la clase personalizada `TimedRoute`, y tendrán un header `X-Response-Time` extra en el response con el tiempo que tomó generar el response: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/es/docs/how-to/extending-openapi.md b/docs/es/docs/how-to/extending-openapi.md index 3dbdd666be..299c541246 100644 --- a/docs/es/docs/how-to/extending-openapi.md +++ b/docs/es/docs/how-to/extending-openapi.md @@ -1,10 +1,10 @@ -# Extender OpenAPI +# Extender OpenAPI { #extending-openapi } Hay algunos casos en los que podrías necesitar modificar el esquema de OpenAPI generado. En esta sección verás cómo hacerlo. -## El proceso normal +## El proceso normal { #the-normal-process } El proceso normal (por defecto) es el siguiente. @@ -33,31 +33,31 @@ El parámetro `summary` está disponible en OpenAPI 3.1.0 y versiones superiores /// -## Sobrescribir los valores por defecto +## Sobrescribir los valores por defecto { #overriding-the-defaults } Usando la información anterior, puedes usar la misma función de utilidad para generar el esquema de OpenAPI y sobrescribir cada parte que necesites. Por ejemplo, vamos a añadir la extensión OpenAPI de ReDoc para incluir un logo personalizado. -### **FastAPI** normal +### **FastAPI** normal { #normal-fastapi } Primero, escribe toda tu aplicación **FastAPI** como normalmente: {* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} -### Generar el esquema de OpenAPI +### Generar el esquema de OpenAPI { #generate-the-openapi-schema } Luego, usa la misma función de utilidad para generar el esquema de OpenAPI, dentro de una función `custom_openapi()`: {* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} -### Modificar el esquema de OpenAPI +### Modificar el esquema de OpenAPI { #modify-the-openapi-schema } Ahora puedes añadir la extensión de ReDoc, agregando un `x-logo` personalizado al "objeto" `info` en el esquema de OpenAPI: {* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} -### Cachear el esquema de OpenAPI +### Cachear el esquema de OpenAPI { #cache-the-openapi-schema } Puedes usar la propiedad `.openapi_schema` como un "cache", para almacenar tu esquema generado. @@ -67,13 +67,13 @@ Se generará solo una vez, y luego se usará el mismo esquema cacheado para las {* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} -### Sobrescribir el método +### Sobrescribir el método { #override-the-method } Ahora puedes reemplazar el método `.openapi()` por tu nueva función. {* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} -### Revisa +### Revisa { #check-it } Una vez que vayas a http://127.0.0.1:8000/redoc verás que estás usando tu logo personalizado (en este ejemplo, el logo de **FastAPI**): diff --git a/docs/es/docs/how-to/general.md b/docs/es/docs/how-to/general.md index e10621ce51..3a3dc82943 100644 --- a/docs/es/docs/how-to/general.md +++ b/docs/es/docs/how-to/general.md @@ -1,39 +1,39 @@ -# General - Cómo Hacer - Recetas +# General - Cómo Hacer - Recetas { #general-how-to-recipes } Aquí tienes varias indicaciones hacia otros lugares en la documentación, para preguntas generales o frecuentes. -## Filtrar Datos - Seguridad +## Filtrar Datos - Seguridad { #filter-data-security } Para asegurarte de que no devuelves más datos de los que deberías, lee la documentación para [Tutorial - Modelo de Response - Tipo de Retorno](../tutorial/response-model.md){.internal-link target=_blank}. -## Etiquetas de Documentación - OpenAPI +## Etiquetas de Documentación - OpenAPI { #documentation-tags-openapi } Para agregar etiquetas a tus *path operations*, y agruparlas en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Etiquetas](../tutorial/path-operation-configuration.md#tags){.internal-link target=_blank}. -## Resumen y Descripción de Documentación - OpenAPI +## Resumen y Descripción de Documentación - OpenAPI { #documentation-summary-and-description-openapi } Para agregar un resumen y descripción a tus *path operations*, y mostrarlos en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Resumen y Descripción](../tutorial/path-operation-configuration.md#summary-and-description){.internal-link target=_blank}. -## Documentación de Descripción de Response - OpenAPI +## Documentación de Descripción de Response - OpenAPI { #documentation-response-description-openapi } Para definir la descripción del response, mostrada en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Descripción del Response](../tutorial/path-operation-configuration.md#response-description){.internal-link target=_blank}. -## Documentar la Deprecación de una *Path Operation* - OpenAPI +## Documentar la Deprecación de una *Path Operation* - OpenAPI { #documentation-deprecate-a-path-operation-openapi } Para deprecar una *path operation*, y mostrarla en la interfaz de usuario de la documentación, lee la documentación para [Tutorial - Configuraciones de Path Operation - Deprecación](../tutorial/path-operation-configuration.md#deprecate-a-path-operation){.internal-link target=_blank}. -## Convertir cualquier Dato a Compatible con JSON +## Convertir cualquier Dato a Compatible con JSON { #convert-any-data-to-json-compatible } Para convertir cualquier dato a compatible con JSON, lee la documentación para [Tutorial - Codificador Compatible con JSON](../tutorial/encoder.md){.internal-link target=_blank}. -## Metadatos OpenAPI - Documentación +## Metadatos OpenAPI - Documentación { #openapi-metadata-docs } Para agregar metadatos a tu esquema de OpenAPI, incluyendo una licencia, versión, contacto, etc, lee la documentación para [Tutorial - Metadatos y URLs de Documentación](../tutorial/metadata.md){.internal-link target=_blank}. -## URL Personalizada de OpenAPI +## URL Personalizada de OpenAPI { #openapi-custom-url } Para personalizar la URL de OpenAPI (o eliminarla), lee la documentación para [Tutorial - Metadatos y URLs de Documentación](../tutorial/metadata.md#openapi-url){.internal-link target=_blank}. -## URLs de Documentación de OpenAPI +## URLs de Documentación de OpenAPI { #openapi-docs-urls } Para actualizar las URLs usadas para las interfaces de usuario de documentación generadas automáticamente, lee la documentación para [Tutorial - Metadatos y URLs de Documentación](../tutorial/metadata.md#docs-urls){.internal-link target=_blank}. diff --git a/docs/es/docs/how-to/graphql.md b/docs/es/docs/how-to/graphql.md index 52f1638097..76031b6712 100644 --- a/docs/es/docs/how-to/graphql.md +++ b/docs/es/docs/how-to/graphql.md @@ -1,4 +1,4 @@ -# GraphQL +# GraphQL { #graphql } Como **FastAPI** se basa en el estándar **ASGI**, es muy fácil integrar cualquier paquete de **GraphQL** que también sea compatible con ASGI. @@ -14,7 +14,7 @@ Asegúrate de evaluar si los **beneficios** para tu caso de uso compensan los ** /// -## Paquetes de GraphQL +## Paquetes de GraphQL { #graphql-libraries } Aquí algunos de los paquetes de **GraphQL** que tienen soporte **ASGI**. Podrías usarlos con **FastAPI**: @@ -27,7 +27,7 @@ Aquí algunos de los paquetes de **GraphQL** que tienen soporte **ASGI**. Podrí * Graphene * Con starlette-graphene3 -## GraphQL con Strawberry +## GraphQL con Strawberry { #graphql-with-strawberry } Si necesitas o quieres trabajar con **GraphQL**, **Strawberry** es el paquete **recomendado** ya que tiene un diseño muy similar al diseño de **FastAPI**, todo basado en **anotaciones de tipos**. @@ -35,13 +35,13 @@ Dependiendo de tu caso de uso, podrías preferir usar un paquete diferente, pero Aquí tienes una pequeña vista previa de cómo podrías integrar Strawberry con FastAPI: -{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *} +{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} Puedes aprender más sobre Strawberry en la documentación de Strawberry. Y también la documentación sobre Strawberry con FastAPI. -## `GraphQLApp` viejo de Starlette +## `GraphQLApp` viejo de Starlette { #older-graphqlapp-from-starlette } Las versiones anteriores de Starlette incluían una clase `GraphQLApp` para integrar con Graphene. @@ -53,7 +53,7 @@ Si necesitas GraphQL, aún te recomendaría revisar documentación oficial de GraphQL. diff --git a/docs/es/docs/how-to/index.md b/docs/es/docs/how-to/index.md index 152499af88..6f5988049a 100644 --- a/docs/es/docs/how-to/index.md +++ b/docs/es/docs/how-to/index.md @@ -1,4 +1,4 @@ -# How To - Recetas +# Cómo hacer - Recetas { #how-to-recipes } Aquí verás diferentes recetas o guías de "cómo hacer" para **varios temas**. @@ -8,6 +8,6 @@ Si algo parece interesante y útil para tu proyecto, adelante y revísalo, pero /// tip | Consejo -Si quieres **aprender FastAPI** de una manera estructurada (recomendado), ve y lee el [Tutorial - User Guide](../tutorial/index.md){.internal-link target=_blank} capítulo por capítulo. +Si quieres **aprender FastAPI** de una manera estructurada (recomendado), ve y lee el [Tutorial - Guía de Usuario](../tutorial/index.md){.internal-link target=_blank} capítulo por capítulo en su lugar. /// diff --git a/docs/es/docs/how-to/separate-openapi-schemas.md b/docs/es/docs/how-to/separate-openapi-schemas.md index b779158511..72396f8c95 100644 --- a/docs/es/docs/how-to/separate-openapi-schemas.md +++ b/docs/es/docs/how-to/separate-openapi-schemas.md @@ -1,4 +1,4 @@ -# Separación de Esquemas OpenAPI para Entrada y Salida o No +# Separación de Esquemas OpenAPI para Entrada y Salida o No { #separate-openapi-schemas-for-input-and-output-or-not } Al usar **Pydantic v2**, el OpenAPI generado es un poco más exacto y **correcto** que antes. 😎 @@ -6,13 +6,13 @@ De hecho, en algunos casos, incluso tendrá **dos JSON Schemas** en OpenAPI para Veamos cómo funciona eso y cómo cambiarlo si necesitas hacerlo. -## Modelos Pydantic para Entrada y Salida +## Modelos Pydantic para Entrada y Salida { #pydantic-models-for-input-and-output } Digamos que tienes un modelo Pydantic con valores por defecto, como este: {* ../../docs_src/separate_openapi_schemas/tutorial001_py310.py ln[1:7] hl[7] *} -### Modelo para Entrada +### Modelo para Entrada { #model-for-input } Si usas este modelo como entrada, como aquí: @@ -20,7 +20,7 @@ Si usas este modelo como entrada, como aquí: ...entonces el campo `description` **no será requerido**. Porque tiene un valor por defecto de `None`. -### Modelo de Entrada en la Documentación +### Modelo de Entrada en la Documentación { #input-model-in-docs } Puedes confirmar eso en la documentación, el campo `description` no tiene un **asterisco rojo**, no está marcado como requerido: @@ -28,7 +28,7 @@ Puedes confirmar eso en la documentación, el campo `description` no tiene un **
-### Modelo para Salida +### Modelo para Salida { #model-for-output } Pero si usas el mismo modelo como salida, como aquí: @@ -36,7 +36,7 @@ Pero si usas el mismo modelo como salida, como aquí: ...entonces, porque `description` tiene un valor por defecto, si **no devuelves nada** para ese campo, aún tendrá ese **valor por defecto**. -### Modelo para Datos de Response de Salida +### Modelo para Datos de Response de Salida { #model-for-output-response-data } Si interactúas con la documentación y revisas el response, aunque el código no agregó nada en uno de los campos `description`, el response JSON contiene el valor por defecto (`null`): @@ -46,7 +46,7 @@ Si interactúas con la documentación y revisas el response, aunque el código n Esto significa que **siempre tendrá un valor**, solo que a veces el valor podría ser `None` (o `null` en JSON). -Eso significa que, los clientes que usan tu API no tienen que comprobar si el valor existe o no, pueden **asumir que el campo siempre estará allí**, pero solo que en algunos casos tendrá el valor por defecto de `None`. +Eso significa que, los clientes que usan tu API no tienen que revisar si el valor existe o no, pueden **asumir que el campo siempre estará allí**, pero solo que en algunos casos tendrá el valor por defecto de `None`. La forma de describir esto en OpenAPI es marcar ese campo como **requerido**, porque siempre estará allí. @@ -55,7 +55,7 @@ Debido a eso, el JSON Schema para un modelo puede ser diferente dependiendo de s * para **entrada** el `description` **no será requerido** * para **salida** será **requerido** (y posiblemente `None`, o en términos de JSON, `null`) -### Modelo para Salida en la Documentación +### Modelo para Salida en la Documentación { #model-for-output-in-docs } También puedes revisar el modelo de salida en la documentación, **ambos** `name` y `description` están marcados como **requeridos** con un **asterisco rojo**: @@ -63,7 +63,7 @@ También puedes revisar el modelo de salida en la documentación, **ambos** `nam -### Modelo para Entrada y Salida en la Documentación +### Modelo para Entrada y Salida en la Documentación { #model-for-input-and-output-in-docs } Y si revisas todos los esquemas disponibles (JSON Schemas) en OpenAPI, verás que hay dos, uno `Item-Input` y uno `Item-Output`. @@ -77,7 +77,7 @@ Pero para `Item-Output`, `description` **es requerido**, tiene un asterisco rojo Con esta funcionalidad de **Pydantic v2**, la documentación de tu API es más **precisa**, y si tienes clientes y SDKs autogenerados, también serán más precisos, con una mejor **experiencia para desarrolladores** y consistencia. 🎉 -## No Separar Esquemas +## No Separar Esquemas { #do-not-separate-schemas } Ahora, hay algunos casos donde podrías querer tener el **mismo esquema para entrada y salida**. @@ -93,12 +93,12 @@ El soporte para `separate_input_output_schemas` fue agregado en FastAPI `0.102.0 {* ../../docs_src/separate_openapi_schemas/tutorial002_py310.py hl[10] *} -### Mismo Esquema para Modelos de Entrada y Salida en la Documentación +### Mismo Esquema para Modelos de Entrada y Salida en la Documentación { #same-schema-for-input-and-output-models-in-docs } Y ahora habrá un único esquema para entrada y salida para el modelo, solo `Item`, y tendrá `description` como **no requerido**:
- +
Este es el mismo comportamiento que en Pydantic v1. 🤓 diff --git a/docs/es/docs/how-to/testing-database.md b/docs/es/docs/how-to/testing-database.md index b76f4c33a3..2fa2603123 100644 --- a/docs/es/docs/how-to/testing-database.md +++ b/docs/es/docs/how-to/testing-database.md @@ -1,4 +1,4 @@ -# Probando una Base de Datos +# Probando una Base de Datos { #testing-a-database } Puedes estudiar sobre bases de datos, SQL y SQLModel en la documentación de SQLModel. 🤓 diff --git a/docs/es/docs/index.md b/docs/es/docs/index.md index a965fa4b5f..c14369a115 100644 --- a/docs/es/docs/index.md +++ b/docs/es/docs/index.md @@ -1,11 +1,11 @@ -# FastAPI +# FastAPI { #fastapi }

- FastAPI + FastAPI

FastAPI framework, alto rendimiento, fácil de aprender, rápido de programar, listo para producción @@ -27,7 +27,7 @@ --- -**Documentación**: https://fastapi.tiangolo.com +**Documentación**: https://fastapi.tiangolo.com **Código Fuente**: https://github.com/fastapi/fastapi @@ -35,12 +35,12 @@ FastAPI es un framework web moderno, rápido (de alto rendimiento), para construir APIs con Python basado en las anotaciones de tipos estándar de Python. -Las características clave son: +Las funcionalidades clave son: * **Rápido**: Muy alto rendimiento, a la par con **NodeJS** y **Go** (gracias a Starlette y Pydantic). [Uno de los frameworks Python más rápidos disponibles](#performance). * **Rápido de programar**: Aumenta la velocidad para desarrollar funcionalidades en aproximadamente un 200% a 300%. * * **Menos bugs**: Reduce en aproximadamente un 40% los errores inducidos por humanos (desarrolladores). * -* **Intuitivo**: Gran soporte para editores. Autocompletado en todas partes. Menos tiempo depurando. +* **Intuitivo**: Gran soporte para editores. Autocompletado en todas partes. Menos tiempo depurando. * **Fácil**: Diseñado para ser fácil de usar y aprender. Menos tiempo leyendo documentación. * **Corto**: Minimiza la duplicación de código. Múltiples funcionalidades desde cada declaración de parámetro. Menos bugs. * **Robusto**: Obtén código listo para producción. Con documentación interactiva automática. @@ -48,24 +48,30 @@ Las características clave son: * estimación basada en pruebas con un equipo de desarrollo interno, construyendo aplicaciones de producción. -## Sponsors +## Sponsors { #sponsors } -{% if sponsors %} +### Sponsor Keystone { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} + +{% endfor -%} + +### Sponsors Oro y Plata { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} {% endfor -%} {%- for sponsor in sponsors.silver -%} {% endfor %} -{% endif %} -Otros sponsors +Otros sponsors -## Opiniones +## Opiniones { #opinions } "_[...] Estoy usando **FastAPI** un montón estos días. [...] De hecho, estoy planeando usarlo para todos los servicios de **ML de mi equipo en Microsoft**. Algunos de ellos se están integrando en el núcleo del producto **Windows** y algunos productos de **Office**._" @@ -111,24 +117,24 @@ Las características clave son: --- -## **Typer**, el FastAPI de las CLIs +## **Typer**, el FastAPI de las CLIs { #typer-the-fastapi-of-clis } -Si estás construyendo una aplicación de CLI para ser usada en el terminal en lugar de una API web, revisa **Typer**. +Si estás construyendo una aplicación de CLI para ser usada en la terminal en lugar de una API web, revisa **Typer**. **Typer** es el hermano pequeño de FastAPI. Y está destinado a ser el **FastAPI de las CLIs**. ⌨️ 🚀 -## Requisitos +## Requisitos { #requirements } FastAPI se apoya en hombros de gigantes: * Starlette para las partes web. * Pydantic para las partes de datos. -## Instalación +## Instalación { #installation } -Crea y activa un entorno virtual y luego instala FastAPI: +Crea y activa un entorno virtual y luego instala FastAPI:

@@ -142,11 +148,11 @@ $ pip install "fastapi[standard]" **Nota**: Asegúrate de poner `"fastapi[standard]"` entre comillas para asegurar que funcione en todas las terminales. -## Ejemplo +## Ejemplo { #example } -### Créalo +### Créalo { #create-it } -* Crea un archivo `main.py` con: +Crea un archivo `main.py` con: ```Python from typing import Union @@ -191,11 +197,11 @@ async def read_item(item_id: int, q: Union[str, None] = None): **Nota**: -Si no lo sabes, revisa la sección _"¿Con prisa?"_ sobre `async` y `await` en la documentación. +Si no lo sabes, revisa la sección _"¿Con prisa?"_ sobre `async` y `await` en la documentación. -### Córrelo +### Córrelo { #run-it } Corre el servidor con: @@ -233,11 +239,11 @@ El comando `fastapi dev` lee tu archivo `main.py`, detecta la app **FastAPI** en Por defecto, `fastapi dev` comenzará con auto-recarga habilitada para el desarrollo local. -Puedes leer más sobre esto en la documentación del CLI de FastAPI. +Puedes leer más sobre esto en la documentación del CLI de FastAPI. -### Revísalo +### Revísalo { #check-it } Abre tu navegador en http://127.0.0.1:8000/items/5?q=somequery. @@ -254,7 +260,7 @@ Ya creaste una API que: * El _path_ `/items/{item_id}` tiene un _parámetro de path_ `item_id` que debe ser un `int`. * El _path_ `/items/{item_id}` tiene un _parámetro de query_ `q` opcional que es un `str`. -### Documentación interactiva de la API +### Documentación interactiva de la API { #interactive-api-docs } Ahora ve a http://127.0.0.1:8000/docs. @@ -262,7 +268,7 @@ Verás la documentación interactiva automática de la API (proporcionada por http://127.0.0.1:8000/redoc. @@ -270,7 +276,7 @@ Verás la documentación alternativa automática (proporcionada por http://127.0.0.1:8000/docs. @@ -324,7 +330,7 @@ Ahora ve a http://127.0.0.1:8000/redoc. @@ -332,7 +338,7 @@ Y ahora, ve a Tutorial - Guía del Usuario. +Para un ejemplo más completo incluyendo más funcionalidades, ve al Tutorial - Guía del Usuario. **Alerta de spoilers**: el tutorial - guía del usuario incluye: @@ -444,17 +450,69 @@ Para un ejemplo más completo incluyendo más funcionalidades, ve al FastAPI Cloud, ve y únete a la lista de espera si no lo has hecho. 🚀 + +Si ya tienes una cuenta de **FastAPI Cloud** (te invitamos desde la lista de espera 😉), puedes desplegar tu aplicación con un solo comando. + +Antes de desplegar, asegúrate de haber iniciado sesión: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Luego despliega tu app: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +¡Eso es todo! Ahora puedes acceder a tu app en esa URL. ✨ + +#### Acerca de FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** está construido por el mismo autor y equipo detrás de **FastAPI**. + +Optimiza el proceso de **construir**, **desplegar** y **acceder** a una API con un esfuerzo mínimo. + +Trae la misma **experiencia de desarrollador** de construir apps con FastAPI a **desplegarlas** en la nube. 🎉 + +FastAPI Cloud es el sponsor principal y proveedor de financiamiento para los proyectos open source *FastAPI and friends*. ✨ + +#### Despliega en otros proveedores de cloud { #deploy-to-other-cloud-providers } + +FastAPI es open source y está basado en estándares. Puedes desplegar apps de FastAPI en cualquier proveedor de cloud que elijas. + +Sigue las guías de tu proveedor de cloud para desplegar apps de FastAPI con ellos. 🤓 + +## Rendimiento { #performance } Benchmarks independientes de TechEmpower muestran aplicaciones **FastAPI** ejecutándose bajo Uvicorn como uno de los frameworks Python más rápidos disponibles, solo por debajo de Starlette y Uvicorn (usados internamente por FastAPI). (*) -Para entender más sobre esto, ve la sección Benchmarks. +Para entender más sobre esto, ve la sección Benchmarks. -## Dependencias +## Dependencias { #dependencies } FastAPI depende de Pydantic y Starlette. -### Dependencias `standard` +### Dependencias `standard` { #standard-dependencies } Cuando instalas FastAPI con `pip install "fastapi[standard]"` viene con el grupo `standard` de dependencias opcionales: @@ -465,19 +523,24 @@ Usadas por Pydantic: Usadas por Starlette: * httpx - Requerido si deseas usar el `TestClient`. -* jinja2 - Requerido si deseas usar la configuración de plantilla predeterminada. +* jinja2 - Requerido si deseas usar la configuración de plantilla por defecto. * python-multipart - Requerido si deseas soportar "parsing" de forms, con `request.form()`. -Usadas por FastAPI / Starlette: +Usadas por FastAPI: * uvicorn - para el servidor que carga y sirve tu aplicación. Esto incluye `uvicorn[standard]`, que incluye algunas dependencias (por ejemplo, `uvloop`) necesarias para servir con alto rendimiento. -* `fastapi-cli` - para proporcionar el comando `fastapi`. +* `fastapi-cli[standard]` - para proporcionar el comando `fastapi`. + * Esto incluye `fastapi-cloud-cli`, que te permite desplegar tu aplicación de FastAPI en FastAPI Cloud. -### Sin Dependencias `standard` +### Sin Dependencias `standard` { #without-standard-dependencies } Si no deseas incluir las dependencias opcionales `standard`, puedes instalar con `pip install fastapi` en lugar de `pip install "fastapi[standard]"`. -### Dependencias Opcionales Adicionales +### Sin `fastapi-cloud-cli` { #without-fastapi-cloud-cli } + +Si quieres instalar FastAPI con las dependencias standard pero sin `fastapi-cloud-cli`, puedes instalar con `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. + +### Dependencias Opcionales Adicionales { #additional-optional-dependencies } Existen algunas dependencias adicionales que podrías querer instalar. @@ -491,6 +554,6 @@ Dependencias opcionales adicionales de FastAPI: * orjson - Requerido si deseas usar `ORJSONResponse`. * ujson - Requerido si deseas usar `UJSONResponse`. -## Licencia +## Licencia { #license } Este proyecto tiene licencia bajo los términos de la licencia MIT. diff --git a/docs/es/docs/learn/index.md b/docs/es/docs/learn/index.md index cc6c7cc3f0..4333bfcf6e 100644 --- a/docs/es/docs/learn/index.md +++ b/docs/es/docs/learn/index.md @@ -1,4 +1,4 @@ -# Aprende +# Aprende { #learn } Aquí están las secciones introductorias y los tutoriales para aprender **FastAPI**. diff --git a/docs/es/docs/project-generation.md b/docs/es/docs/project-generation.md index 5599951511..414c498246 100644 --- a/docs/es/docs/project-generation.md +++ b/docs/es/docs/project-generation.md @@ -1,4 +1,4 @@ -# Plantilla Full Stack FastAPI +# Plantilla Full Stack FastAPI { #full-stack-fastapi-template } Las plantillas, aunque normalmente vienen con una configuración específica, están diseñadas para ser flexibles y personalizables. Esto te permite modificarlas y adaptarlas a los requisitos de tu proyecto, haciéndolas un excelente punto de partida. 🏁 @@ -6,23 +6,23 @@ Puedes usar esta plantilla para comenzar, ya que incluye gran parte de la config Repositorio de GitHub: Plantilla Full Stack FastAPI -## Plantilla Full Stack FastAPI - Tecnología y Funcionalidades +## Plantilla Full Stack FastAPI - Tecnología y Funcionalidades { #full-stack-fastapi-template-technology-stack-and-features } -- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) para la API del backend en Python. +- ⚡ [**FastAPI**](https://fastapi.tiangolo.com/es) para la API del backend en Python. - 🧰 [SQLModel](https://sqlmodel.tiangolo.com) para las interacciones con bases de datos SQL en Python (ORM). - 🔍 [Pydantic](https://docs.pydantic.dev), utilizado por FastAPI, para la validación de datos y gestión de configuraciones. - 💾 [PostgreSQL](https://www.postgresql.org) como base de datos SQL. - 🚀 [React](https://react.dev) para el frontend. - 💃 Usando TypeScript, hooks, [Vite](https://vitejs.dev), y otras partes de una stack moderna de frontend. - - 🎨 [Chakra UI](https://chakra-ui.com) para los componentes del frontend. + - 🎨 [Tailwind CSS](https://tailwindcss.com) y [shadcn/ui](https://ui.shadcn.com) para los componentes del frontend. - 🤖 Un cliente de frontend generado automáticamente. - - 🧪 [Playwright](https://playwright.dev) para pruebas End-to-End. + - 🧪 [Playwright](https://playwright.dev) para escribir pruebas End-to-End. - 🦇 Soporte para modo oscuro. - 🐋 [Docker Compose](https://www.docker.com) para desarrollo y producción. - 🔒 Hashing seguro de contraseñas por defecto. - 🔑 Autenticación con tokens JWT. - 📫 Recuperación de contraseñas basada en email. - ✅ Pruebas con [Pytest](https://pytest.org). -- 📞 [Traefik](https://traefik.io) como proxy inverso / balanceador de carga. +- 📞 [Traefik](https://traefik.io) como proxy inverso / load balancer. - 🚢 Instrucciones de despliegue usando Docker Compose, incluyendo cómo configurar un proxy Traefik frontend para manejar certificados HTTPS automáticos. - 🏭 CI (integración continua) y CD (despliegue continuo) basados en GitHub Actions. diff --git a/docs/es/docs/python-types.md b/docs/es/docs/python-types.md index 769204f8fd..e51c2352c5 100644 --- a/docs/es/docs/python-types.md +++ b/docs/es/docs/python-types.md @@ -1,4 +1,4 @@ -# Introducción a Tipos en Python +# Introducción a Tipos en Python { #python-types-intro } Python tiene soporte para "anotaciones de tipos" opcionales (también llamadas "type hints"). @@ -18,7 +18,7 @@ Si eres un experto en Python, y ya sabes todo sobre las anotaciones de tipos, sa /// -## Motivación +## Motivación { #motivation } Comencemos con un ejemplo simple: @@ -38,7 +38,7 @@ La función hace lo siguiente: {* ../../docs_src/python_types/tutorial001.py hl[2] *} -### Edítalo +### Edítalo { #edit-it } Es un programa muy simple. @@ -58,7 +58,7 @@ Pero, tristemente, no obtienes nada útil: -### Añadir tipos +### Añadir tipos { #add-types } Modifiquemos una sola línea de la versión anterior. @@ -102,7 +102,7 @@ Con eso, puedes desplazarte, viendo las opciones, hasta que encuentres la que "t -## Más motivación +## Más motivación { #more-motivation } Revisa esta función, ya tiene anotaciones de tipos: @@ -116,13 +116,13 @@ Ahora sabes que debes corregirlo, convertir `age` a un string con `str(age)`: {* ../../docs_src/python_types/tutorial004.py hl[2] *} -## Declaración de tipos +## Declaración de tipos { #declaring-types } Acabas de ver el lugar principal para declarar anotaciones de tipos. Como parámetros de función. Este también es el lugar principal donde los utilizarías con **FastAPI**. -### Tipos simples +### Tipos simples { #simple-types } Puedes declarar todos los tipos estándar de Python, no solo `str`. @@ -135,7 +135,7 @@ Puedes usar, por ejemplo: {* ../../docs_src/python_types/tutorial005.py hl[1] *} -### Tipos genéricos con parámetros de tipo +### Tipos genéricos con parámetros de tipo { #generic-types-with-type-parameters } Hay algunas estructuras de datos que pueden contener otros valores, como `dict`, `list`, `set` y `tuple`. Y los valores internos también pueden tener su propio tipo. @@ -143,7 +143,7 @@ Estos tipos que tienen tipos internos se denominan tipos "**genéricos**". Y es Para declarar esos tipos y los tipos internos, puedes usar el módulo estándar de Python `typing`. Existe específicamente para soportar estas anotaciones de tipos. -#### Versiones más recientes de Python +#### Versiones más recientes de Python { #newer-versions-of-python } La sintaxis que utiliza `typing` es **compatible** con todas las versiones, desde Python 3.6 hasta las versiones más recientes, incluyendo Python 3.9, Python 3.10, etc. @@ -157,7 +157,7 @@ Por ejemplo, "**Python 3.6+**" significa que es compatible con Python 3.6 o supe Si puedes usar las **últimas versiones de Python**, utiliza los ejemplos para la última versión, esos tendrán la **mejor y más simple sintaxis**, por ejemplo, "**Python 3.10+**". -#### Lista +#### Lista { #list } Por ejemplo, vamos a definir una variable para ser una `list` de `str`. @@ -221,7 +221,7 @@ Nota que la variable `item` es uno de los elementos en la lista `items`. Y aún así, el editor sabe que es un `str` y proporciona soporte para eso. -#### Tuple y Set +#### Tuple y Set { #tuple-and-set } Harías lo mismo para declarar `tuple`s y `set`s: @@ -246,7 +246,7 @@ Esto significa: * La variable `items_t` es un `tuple` con 3 ítems, un `int`, otro `int`, y un `str`. * La variable `items_s` es un `set`, y cada uno de sus ítems es del tipo `bytes`. -#### Dict +#### Dict { #dict } Para definir un `dict`, pasas 2 parámetros de tipo, separados por comas. @@ -276,7 +276,7 @@ Esto significa: * Las claves de este `dict` son del tipo `str` (digamos, el nombre de cada ítem). * Los valores de este `dict` son del tipo `float` (digamos, el precio de cada ítem). -#### Union +#### Union { #union } Puedes declarar que una variable puede ser cualquier de **varios tipos**, por ejemplo, un `int` o un `str`. @@ -302,7 +302,7 @@ En Python 3.10 también hay una **nueva sintaxis** donde puedes poner los posibl En ambos casos, esto significa que `item` podría ser un `int` o un `str`. -#### Posiblemente `None` +#### Posiblemente `None` { #possibly-none } Puedes declarar que un valor podría tener un tipo, como `str`, pero que también podría ser `None`. @@ -334,7 +334,7 @@ Esto también significa que en Python 3.10, puedes usar `Something | None`: //// -//// tab | Python 3.8+ alternative +//// tab | Python 3.8+ alternativa ```Python hl_lines="1 4" {!> ../../docs_src/python_types/tutorial009b.py!} @@ -342,7 +342,7 @@ Esto también significa que en Python 3.10, puedes usar `Something | None`: //// -#### Uso de `Union` u `Optional` +#### Uso de `Union` u `Optional` { #using-union-or-optional } Si estás usando una versión de Python inferior a 3.10, aquí tienes un consejo desde mi punto de vista muy **subjetivo**: @@ -377,7 +377,7 @@ La buena noticia es que, una vez que estés en Python 3.10, no tendrás que preo Y entonces no tendrás que preocuparte por nombres como `Optional` y `Union`. 😎 -#### Tipos genéricos +#### Tipos genéricos { #generic-types } Estos tipos que toman parámetros de tipo en corchetes se llaman **Tipos Genéricos** o **Genéricos**, por ejemplo: @@ -429,7 +429,7 @@ Y lo mismo que con Python 3.8, desde el módulo `typing`: //// -### Clases como tipos +### Clases como tipos { #classes-as-types } También puedes declarar una clase como el tipo de una variable. @@ -449,7 +449,7 @@ Nota que esto significa "`one_person` es una **instance** de la clase `Person`". No significa "`one_person` es la **clase** llamada `Person`". -## Modelos Pydantic +## Modelos Pydantic { #pydantic-models } Pydantic es un paquete de Python para realizar la validación de datos. @@ -503,13 +503,13 @@ Pydantic tiene un comportamiento especial cuando utilizas `Optional` o `Union[So /// -## Anotaciones de tipos con metadata +## Anotaciones de tipos con metadata { #type-hints-with-metadata-annotations } -Python también tiene una funcionalidad que permite poner **metadata adicional** en estas anotaciones de tipos usando `Annotated`. +Python también tiene una funcionalidad que permite poner **metadatos adicional** en estas anotaciones de tipos usando `Annotated`. //// tab | Python 3.9+ -En Python 3.9, `Annotated` es parte de la librería estándar, así que puedes importarlo desde `typing`. +En Python 3.9, `Annotated` es parte de la standard library, así que puedes importarlo desde `typing`. ```Python hl_lines="1 4" {!> ../../docs_src/python_types/tutorial013_py39.py!} @@ -547,7 +547,7 @@ Y también que tu código será muy compatible con muchas otras herramientas y p /// -## Anotaciones de tipos en **FastAPI** +## Anotaciones de tipos en **FastAPI** { #type-hints-in-fastapi } **FastAPI** aprovecha estas anotaciones de tipos para hacer varias cosas. diff --git a/docs/es/docs/resources/index.md b/docs/es/docs/resources/index.md index 92898d319e..3240095611 100644 --- a/docs/es/docs/resources/index.md +++ b/docs/es/docs/resources/index.md @@ -1,3 +1,3 @@ -# Recursos +# Recursos { #resources } -Recursos adicionales, enlaces externos, artículos y más. ✈️ +Recursos adicionales, enlaces externos y más. ✈️ diff --git a/docs/es/docs/tutorial/bigger-applications.md b/docs/es/docs/tutorial/bigger-applications.md index c3d8f06860..eacdb13c74 100644 --- a/docs/es/docs/tutorial/bigger-applications.md +++ b/docs/es/docs/tutorial/bigger-applications.md @@ -1,4 +1,4 @@ -# Aplicaciones más grandes - Múltiples archivos +# Aplicaciones más grandes - Múltiples archivos { #bigger-applications-multiple-files } Si estás construyendo una aplicación o una API web, rara vez podrás poner todo en un solo archivo. @@ -10,7 +10,7 @@ Si vienes de Flask, esto sería el equivalente a los Blueprints de Flask. /// -## Un ejemplo de estructura de archivos +## Un ejemplo de estructura de archivos { #an-example-file-structure } Digamos que tienes una estructura de archivos como esta: @@ -71,7 +71,7 @@ La misma estructura de archivos con comentarios: │   └── admin.py # submódulo "admin", por ejemplo import app.internal.admin ``` -## `APIRouter` +## `APIRouter` { #apirouter } Digamos que el archivo dedicado solo a manejar usuarios es el submódulo en `/app/routers/users.py`. @@ -81,23 +81,19 @@ Pero todavía es parte de la misma aplicación/web API de **FastAPI** (es parte Puedes crear las *path operations* para ese módulo usando `APIRouter`. -### Importar `APIRouter` +### Importar `APIRouter` { #import-apirouter } Lo importas y creas una "instance" de la misma manera que lo harías con la clase `FastAPI`: -```Python hl_lines="1 3" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} -### *Path operations* con `APIRouter` +### *Path operations* con `APIRouter` { #path-operations-with-apirouter } Y luego lo usas para declarar tus *path operations*. Úsalo de la misma manera que usarías la clase `FastAPI`: -```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Puedes pensar en `APIRouter` como una clase "mini `FastAPI`". @@ -113,53 +109,25 @@ En este ejemplo, la variable se llama `router`, pero puedes nombrarla como quier Vamos a incluir este `APIRouter` en la aplicación principal de `FastAPI`, pero primero, revisemos las dependencias y otro `APIRouter`. -## Dependencias +## Dependencias { #dependencies } Vemos que vamos a necesitar algunas dependencias usadas en varios lugares de la aplicación. Así que las ponemos en su propio módulo `dependencies` (`app/dependencies.py`). -Ahora utilizaremos una dependencia simple para leer un encabezado `X-Token` personalizado: +Ahora utilizaremos una dependencia simple para leer un header `X-Token` personalizado: -//// tab | Python 3.9+ - -```Python hl_lines="3 6-8" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Consejo -Preferiblemente usa la versión `Annotated` si es posible. - -/// - -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app/dependencies.py!} -``` - -//// - -/// tip | Consejo - -Estamos usando un encabezado inventado para simplificar este ejemplo. +Estamos usando un header inventado para simplificar este ejemplo. Pero en casos reales obtendrás mejores resultados usando las [utilidades de Seguridad](security/index.md){.internal-link target=_blank} integradas. /// -## Otro módulo con `APIRouter` +## Otro módulo con `APIRouter` { #another-module-with-apirouter } Digamos que también tienes los endpoints dedicados a manejar "items" de tu aplicación en el módulo `app/routers/items.py`. @@ -181,9 +149,7 @@ Sabemos que todas las *path operations* en este módulo tienen el mismo: Entonces, en lugar de agregar todo eso a cada *path operation*, podemos agregarlo al `APIRouter`. -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Como el path de cada *path operation* tiene que empezar con `/`, como en: @@ -234,7 +200,7 @@ Los parámetros `prefix`, `tags`, `responses`, y `dependencies` son (como en muc /// -### Importar las dependencias +### Importar las dependencias { #import-the-dependencies } Este código vive en el módulo `app.routers.items`, el archivo `app/routers/items.py`. @@ -242,11 +208,9 @@ Y necesitamos obtener la función de dependencia del módulo `app.dependencies`, Así que usamos un import relativo con `..` para las dependencias: -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} -#### Cómo funcionan los imports relativos +#### Cómo funcionan los imports relativos { #how-relative-imports-work } /// tip | Consejo @@ -309,15 +273,13 @@ Eso se referiría a algún paquete arriba de `app/`, con su propio archivo `__in Pero ahora sabes cómo funciona, para que puedas usar imports relativos en tus propias aplicaciones sin importar cuán complejas sean. 🤓 -### Agregar algunos `tags`, `responses`, y `dependencies` personalizados +### Agregar algunos `tags`, `responses`, y `dependencies` personalizados { #add-some-custom-tags-responses-and-dependencies } No estamos agregando el prefijo `/items` ni los `tags=["items"]` a cada *path operation* porque los hemos añadido al `APIRouter`. Pero aún podemos agregar _más_ `tags` que se aplicarán a una *path operation* específica, y también algunas `responses` extra específicas para esa *path operation*: -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Consejo @@ -327,7 +289,7 @@ Y también tendrá ambas responses en la documentación, una para `404` y otra p /// -## El `FastAPI` principal +## El `FastAPI` principal { #the-main-fastapi } Ahora, veamos el módulo en `app/main.py`. @@ -335,27 +297,25 @@ Aquí es donde importas y usas la clase `FastAPI`. Este será el archivo principal en tu aplicación que conecta todo. -### Importar `FastAPI` +Y como la mayor parte de tu lógica ahora vivirá en su propio módulo específico, el archivo principal será bastante simple. + +### Importar `FastAPI` { #import-fastapi } Importas y creas una clase `FastAPI` como de costumbre. Y podemos incluso declarar [dependencias globales](dependencies/global-dependencies.md){.internal-link target=_blank} que se combinarán con las dependencias para cada `APIRouter`: -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} -### Importar el `APIRouter` +### Importar el `APIRouter` { #import-the-apirouter } Ahora importamos los otros submódulos que tienen `APIRouter`s: -```Python hl_lines="4-5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} Como los archivos `app/routers/users.py` y `app/routers/items.py` son submódulos que son parte del mismo paquete de Python `app`, podemos usar un solo punto `.` para importarlos usando "imports relativos". -### Cómo funciona la importación +### Cómo funciona la importación { #how-the-importing-works } La sección: @@ -397,7 +357,7 @@ Para aprender más sobre Paquetes y Módulos de Python, lee -## Incluir el mismo router múltiples veces con diferentes `prefix` +## Incluir el mismo router múltiples veces con diferentes `prefix` { #include-the-same-router-multiple-times-with-different-prefix } También puedes usar `.include_router()` múltiples veces con el *mismo* router usando diferentes prefijos. @@ -543,7 +493,7 @@ Esto podría ser útil, por ejemplo, para exponer la misma API bajo diferentes p Este es un uso avanzado que quizás no necesites realmente, pero está allí en caso de que lo necesites. -## Incluir un `APIRouter` en otro +## Incluir un `APIRouter` en otro { #include-an-apirouter-in-another } De la misma manera que puedes incluir un `APIRouter` en una aplicación `FastAPI`, puedes incluir un `APIRouter` en otro `APIRouter` usando: diff --git a/docs/es/docs/tutorial/body-fields.md b/docs/es/docs/tutorial/body-fields.md index d07d214ec8..8902ce4e94 100644 --- a/docs/es/docs/tutorial/body-fields.md +++ b/docs/es/docs/tutorial/body-fields.md @@ -1,8 +1,8 @@ -# Body - Campos +# Body - Campos { #body-fields } De la misma manera que puedes declarar validaciones adicionales y metadatos en los parámetros de las *path operation function* con `Query`, `Path` y `Body`, puedes declarar validaciones y metadatos dentro de los modelos de Pydantic usando `Field` de Pydantic. -## Importar `Field` +## Importar `Field` { #import-field } Primero, tienes que importarlo: @@ -14,7 +14,7 @@ Fíjate que `Field` se importa directamente desde `pydantic`, no desde `fastapi` /// -## Declarar atributos del modelo +## Declarar atributos del modelo { #declare-model-attributes } Después puedes utilizar `Field` con los atributos del modelo: @@ -40,7 +40,7 @@ Observa cómo cada atributo del modelo con un tipo, un valor por defecto y `Fiel /// -## Agregar información extra +## Agregar información extra { #add-extra-information } Puedes declarar información extra en `Field`, `Query`, `Body`, etc. Y será incluida en el JSON Schema generado. @@ -53,7 +53,7 @@ Como estas claves no necesariamente tienen que ser parte de la especificación d /// -## Resumen +## Resumen { #recap } Puedes utilizar `Field` de Pydantic para declarar validaciones adicionales y metadatos para los atributos del modelo. diff --git a/docs/es/docs/tutorial/body-multiple-params.md b/docs/es/docs/tutorial/body-multiple-params.md index df6560b620..57cec16743 100644 --- a/docs/es/docs/tutorial/body-multiple-params.md +++ b/docs/es/docs/tutorial/body-multiple-params.md @@ -1,24 +1,22 @@ -# Cuerpo - Múltiples Parámetros +# Cuerpo - Múltiples Parámetros { #body-multiple-parameters } Ahora que hemos visto cómo usar `Path` y `Query`, veamos usos más avanzados de las declaraciones del request body. -## Mezclar `Path`, `Query` y parámetros del cuerpo +## Mezclar `Path`, `Query` y parámetros del cuerpo { #mix-path-query-and-body-parameters } Primero, por supuesto, puedes mezclar las declaraciones de parámetros de `Path`, `Query` y del request body libremente y **FastAPI** sabrá qué hacer. -Y también puedes declarar parámetros del cuerpo como opcionales, estableciendo el valor predeterminado a `None`: +Y también puedes declarar parámetros del cuerpo como opcionales, estableciendo el valor por defecto a `None`: {* ../../docs_src/body_multiple_params/tutorial001_an_py310.py hl[18:20] *} -## Múltiples parámetros del cuerpo - /// note | Nota Ten en cuenta que, en este caso, el `item` que se tomaría del cuerpo es opcional. Ya que tiene un valor por defecto de `None`. /// -## Múltiples parámetros del cuerpo +## Múltiples parámetros del cuerpo { #multiple-body-parameters } En el ejemplo anterior, las *path operations* esperarían un cuerpo JSON con los atributos de un `Item`, como: @@ -64,7 +62,7 @@ Ten en cuenta que aunque el `item` se declaró de la misma manera que antes, aho Realizará la validación de los datos compuestos, y los documentará así para el esquema de OpenAPI y la documentación automática. -## Valores singulares en el cuerpo +## Valores singulares en el cuerpo { #singular-values-in-body } De la misma manera que hay un `Query` y `Path` para definir datos extra para parámetros de query y path, **FastAPI** proporciona un equivalente `Body`. @@ -96,7 +94,7 @@ En este caso, **FastAPI** esperará un cuerpo como: Nuevamente, convertirá los tipos de datos, validará, documentará, etc. -## Múltiples parámetros de cuerpo y query +## Múltiples parámetros de cuerpo y query { #multiple-body-params-and-query } Por supuesto, también puedes declarar parámetros adicionales de query siempre que lo necesites, además de cualquier parámetro del cuerpo. @@ -122,7 +120,7 @@ Por ejemplo: /// -## Embeber un solo parámetro de cuerpo +## Embeber un solo parámetro de cuerpo { #embed-a-single-body-parameter } Supongamos que solo tienes un único parámetro de cuerpo `item` de un modelo Pydantic `Item`. @@ -162,7 +160,7 @@ en lugar de: } ``` -## Resumen +## Resumen { #recap } Puedes añadir múltiples parámetros de cuerpo a tu *path operation function*, aunque un request solo puede tener un único cuerpo. diff --git a/docs/es/docs/tutorial/body-nested-models.md b/docs/es/docs/tutorial/body-nested-models.md index 5b4cfc14c2..04f4b39c4a 100644 --- a/docs/es/docs/tutorial/body-nested-models.md +++ b/docs/es/docs/tutorial/body-nested-models.md @@ -1,8 +1,8 @@ -# Cuerpo - Modelos Anidados +# Cuerpo - Modelos Anidados { #body-nested-models } Con **FastAPI**, puedes definir, validar, documentar y usar modelos anidados de manera arbitraria (gracias a Pydantic). -## Campos de lista +## Campos de lista { #list-fields } Puedes definir un atributo como un subtipo. Por ejemplo, una `list` en Python: @@ -10,11 +10,11 @@ Puedes definir un atributo como un subtipo. Por ejemplo, una `list` en Python: Esto hará que `tags` sea una lista, aunque no declare el tipo de los elementos de la lista. -## Campos de lista con parámetro de tipo +## Campos de lista con parámetro de tipo { #list-fields-with-type-parameter } Pero Python tiene una forma específica de declarar listas con tipos internos, o "parámetros de tipo": -### Importar `List` de typing +### Importar `List` de typing { #import-typings-list } En Python 3.9 y superior, puedes usar el `list` estándar para declarar estas anotaciones de tipo como veremos a continuación. 💡 @@ -22,7 +22,7 @@ Pero en versiones de Python anteriores a 3.9 (desde 3.6 en adelante), primero ne {* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} -### Declarar una `list` con un parámetro de tipo +### Declarar una `list` con un parámetro de tipo { #declare-a-list-with-a-type-parameter } Para declarar tipos que tienen parámetros de tipo (tipos internos), como `list`, `dict`, `tuple`: @@ -51,7 +51,7 @@ Así, en nuestro ejemplo, podemos hacer que `tags` sea específicamente una "lis {* ../../docs_src/body_nested_models/tutorial002_py310.py hl[12] *} -## Tipos de conjunto +## Tipos de conjunto { #set-types } Pero luego pensamos en ello, y nos damos cuenta de que los tags no deberían repetirse, probablemente serían strings únicos. @@ -67,7 +67,7 @@ Y siempre que emitas esos datos, incluso si la fuente tenía duplicados, se emit Y también se anotará/documentará en consecuencia. -## Modelos Anidados +## Modelos Anidados { #nested-models } Cada atributo de un modelo Pydantic tiene un tipo. @@ -77,13 +77,13 @@ Así que, puedes declarar "objetos" JSON anidados profundamente con nombres de a Todo eso, de manera arbitraria. -### Definir un submodelo +### Definir un submodelo { #define-a-submodel } Por ejemplo, podemos definir un modelo `Image`: {* ../../docs_src/body_nested_models/tutorial004_py310.py hl[7:9] *} -### Usar el submodelo como tipo +### Usar el submodelo como tipo { #use-the-submodel-as-a-type } Y luego podemos usarlo como el tipo de un atributo: @@ -112,7 +112,7 @@ Nuevamente, haciendo solo esa declaración, con **FastAPI** obtienes: * Validación de datos * Documentación automática -## Tipos especiales y validación +## Tipos especiales y validación { #special-types-and-validation } Además de tipos singulares normales como `str`, `int`, `float`, etc., puedes usar tipos singulares más complejos que heredan de `str`. @@ -124,7 +124,7 @@ Por ejemplo, como en el modelo `Image` tenemos un campo `url`, podemos declararl El string será verificado para ser una URL válida, y documentado en JSON Schema / OpenAPI como tal. -## Atributos con listas de submodelos +## Atributos con listas de submodelos { #attributes-with-lists-of-submodels } También puedes usar modelos Pydantic como subtipos de `list`, `set`, etc.: @@ -162,7 +162,7 @@ Nota cómo la clave `images` ahora tiene una lista de objetos de imagen. /// -## Modelos anidados profundamente +## Modelos anidados profundamente { #deeply-nested-models } Puedes definir modelos anidados tan profundamente como desees: @@ -174,7 +174,7 @@ Observa cómo `Offer` tiene una lista de `Item`s, que a su vez tienen una lista /// -## Cuerpos de listas puras +## Cuerpos de listas puras { #bodies-of-pure-lists } Si el valor superior del cuerpo JSON que esperas es un `array` JSON (una `list` en Python), puedes declarar el tipo en el parámetro de la función, al igual que en los modelos Pydantic: @@ -192,7 +192,7 @@ como en: {* ../../docs_src/body_nested_models/tutorial008_py39.py hl[13] *} -## Soporte de editor en todas partes +## Soporte de editor en todas partes { #editor-support-everywhere } Y obtienes soporte de editor en todas partes. @@ -204,7 +204,7 @@ No podrías obtener este tipo de soporte de editor si estuvieras trabajando dire Pero tampoco tienes que preocuparte por ellos, los `dicts` entrantes se convierten automáticamente y tu salida se convierte automáticamente a JSON también. -## Cuerpos de `dict`s arbitrarios +## Cuerpos de `dict`s arbitrarios { #bodies-of-arbitrary-dicts } También puedes declarar un cuerpo como un `dict` con claves de algún tipo y valores de algún otro tipo. @@ -234,7 +234,7 @@ Y el `dict` que recibas como `weights` tendrá realmente claves `int` y valores /// -## Resumen +## Resumen { #recap } Con **FastAPI** tienes la máxima flexibilidad proporcionada por los modelos Pydantic, manteniendo tu código simple, corto y elegante. diff --git a/docs/es/docs/tutorial/body-updates.md b/docs/es/docs/tutorial/body-updates.md index 26cd3345fd..1f4713f350 100644 --- a/docs/es/docs/tutorial/body-updates.md +++ b/docs/es/docs/tutorial/body-updates.md @@ -1,6 +1,6 @@ -# Cuerpo - Actualizaciones +# Cuerpo - Actualizaciones { #body-updates } -## Actualización reemplazando con `PUT` +## Actualización reemplazando con `PUT` { #update-replacing-with-put } Para actualizar un ítem puedes utilizar la operación de HTTP `PUT`. @@ -10,7 +10,7 @@ Puedes usar el `jsonable_encoder` para convertir los datos de entrada en datos q `PUT` se usa para recibir datos que deben reemplazar los datos existentes. -### Advertencia sobre el reemplazo +### Advertencia sobre el reemplazo { #warning-about-replacing } Esto significa que si quieres actualizar el ítem `bar` usando `PUT` con un body que contenga: @@ -26,7 +26,7 @@ debido a que no incluye el atributo ya almacenado `"tax": 20.2`, el modelo de en Y los datos se guardarían con ese "nuevo" `tax` de `10.5`. -## Actualizaciones parciales con `PATCH` +## Actualizaciones parciales con `PATCH` { #partial-updates-with-patch } También puedes usar la operación de HTTP `PATCH` para actualizar *parcialmente* datos. @@ -44,7 +44,7 @@ Pero esta guía te muestra, más o menos, cómo se pretende que se usen. /// -### Uso del parámetro `exclude_unset` de Pydantic +### Uso del parámetro `exclude_unset` de Pydantic { #using-pydantics-exclude-unset-parameter } Si quieres recibir actualizaciones parciales, es muy útil usar el parámetro `exclude_unset` en el `.model_dump()` del modelo de Pydantic. @@ -64,7 +64,7 @@ Luego puedes usar esto para generar un `dict` solo con los datos que se establec {* ../../docs_src/body_updates/tutorial002_py310.py hl[32] *} -### Uso del parámetro `update` de Pydantic +### Uso del parámetro `update` de Pydantic { #using-pydantics-update-parameter } Ahora, puedes crear una copia del modelo existente usando `.model_copy()`, y pasar el parámetro `update` con un `dict` que contenga los datos a actualizar. @@ -80,7 +80,7 @@ Como `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} -### Resumen de actualizaciones parciales +### Resumen de actualizaciones parciales { #partial-updates-recap } En resumen, para aplicar actualizaciones parciales deberías: diff --git a/docs/es/docs/tutorial/body.md b/docs/es/docs/tutorial/body.md index 6d0aa7c60f..58877c5c46 100644 --- a/docs/es/docs/tutorial/body.md +++ b/docs/es/docs/tutorial/body.md @@ -1,4 +1,4 @@ -# Request Body +# Request Body { #request-body } Cuando necesitas enviar datos desde un cliente (digamos, un navegador) a tu API, los envías como un **request body**. @@ -18,13 +18,13 @@ Como no se recomienda, la documentación interactiva con Swagger UI no mostrará /// -## Importar `BaseModel` de Pydantic +## Importar `BaseModel` de Pydantic { #import-pydantics-basemodel } Primero, necesitas importar `BaseModel` de `pydantic`: {* ../../docs_src/body/tutorial001_py310.py hl[2] *} -## Crea tu modelo de datos +## Crea tu modelo de datos { #create-your-data-model } Luego, declaras tu modelo de datos como una clase que hereda de `BaseModel`. @@ -54,7 +54,7 @@ Por ejemplo, el modelo anterior declara un “`object`” JSON (o `dict` en Pyth } ``` -## Decláralo como un parámetro +## Decláralo como un parámetro { #declare-it-as-a-parameter } Para añadirlo a tu *path operation*, decláralo de la misma manera que declaraste parámetros de path y query: @@ -62,7 +62,7 @@ Para añadirlo a tu *path operation*, decláralo de la misma manera que declaras ...y declara su tipo como el modelo que creaste, `Item`. -## Resultados +## Resultados { #results } Con solo esa declaración de tipo en Python, **FastAPI** hará lo siguiente: @@ -73,9 +73,9 @@ Con solo esa declaración de tipo en Python, **FastAPI** hará lo siguiente: * Proporcionar los datos recibidos en el parámetro `item`. * Como lo declaraste en la función como de tipo `Item`, también tendrás todo el soporte del editor (autocompletado, etc.) para todos los atributos y sus tipos. * Generar definiciones de JSON Schema para tu modelo, que también puedes usar en cualquier otro lugar si tiene sentido para tu proyecto. -* Esquemas que serán parte del esquema de OpenAPI generado y usados por la UIs de documentación automática. +* Esos esquemas serán parte del esquema de OpenAPI generado y usados por las UIs de documentación automática. -## Documentación automática +## Documentación automática { #automatic-docs } Los JSON Schemas de tus modelos serán parte del esquema OpenAPI generado y se mostrarán en la documentación API interactiva: @@ -85,7 +85,7 @@ Y también se utilizarán en la documentación API dentro de cada *path operatio -## Soporte del editor +## Soporte del editor { #editor-support } En tu editor, dentro de tu función, obtendrás anotaciones de tipos y autocompletado en todas partes (esto no sucedería si recibieras un `dict` en lugar de un modelo de Pydantic): @@ -121,13 +121,21 @@ Mejora el soporte del editor para modelos de Pydantic, con: /// -## Usa el modelo +## Usa el modelo { #use-the-model } Dentro de la función, puedes acceder a todos los atributos del objeto modelo directamente: {* ../../docs_src/body/tutorial002_py310.py *} -## Request body + parámetros de path +/// info | Información + +En Pydantic v1 el método se llamaba `.dict()`, se marcó como obsoleto (pero sigue soportado) en Pydantic v2, y se renombró a `.model_dump()`. + +Los ejemplos aquí usan `.dict()` por compatibilidad con Pydantic v1, pero deberías usar `.model_dump()` si puedes usar Pydantic v2. + +/// + +## Request body + parámetros de path { #request-body-path-parameters } Puedes declarar parámetros de path y request body al mismo tiempo. @@ -135,7 +143,7 @@ Puedes declarar parámetros de path y request body al mismo tiempo. {* ../../docs_src/body/tutorial003_py310.py hl[15:16] *} -## Request body + path + parámetros de query +## Request body + path + parámetros de query { #request-body-path-query-parameters } También puedes declarar parámetros de **body**, **path** y **query**, todos al mismo tiempo. @@ -159,6 +167,6 @@ Pero agregar las anotaciones de tipos permitirá que tu editor te brinde un mejo /// -## Sin Pydantic +## Sin Pydantic { #without-pydantic } -Si no quieres usar modelos de Pydantic, también puedes usar parámetros **Body**. Consulta la documentación para [Body - Multiples Parametros: Valores singulares en body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. +Si no quieres usar modelos de Pydantic, también puedes usar parámetros **Body**. Consulta la documentación para [Body - Multiple Parameters: Singular values in body](body-multiple-params.md#singular-values-in-body){.internal-link target=_blank}. diff --git a/docs/es/docs/tutorial/cookie-param-models.md b/docs/es/docs/tutorial/cookie-param-models.md index ebdb592651..dab7d8c0a2 100644 --- a/docs/es/docs/tutorial/cookie-param-models.md +++ b/docs/es/docs/tutorial/cookie-param-models.md @@ -1,4 +1,4 @@ -# Modelos de Cookies +# Modelos de Cookies { #cookie-parameter-models } Si tienes un grupo de **cookies** que están relacionadas, puedes crear un **modelo de Pydantic** para declararlas. 🍪 @@ -16,7 +16,7 @@ Esta misma técnica se aplica a `Query`, `Cookie`, y `Header`. 😎 /// -## Cookies con un Modelo de Pydantic +## Cookies con un Modelo de Pydantic { #cookies-with-a-pydantic-model } Declara los parámetros de **cookie** que necesites en un **modelo de Pydantic**, y luego declara el parámetro como `Cookie`: @@ -24,7 +24,7 @@ Declara los parámetros de **cookie** que necesites en un **modelo de Pydantic** **FastAPI** **extraerá** los datos para **cada campo** de las **cookies** recibidas en el request y te entregará el modelo de Pydantic que definiste. -## Revisa la Documentación +## Revisa la Documentación { #check-the-docs } Puedes ver las cookies definidas en la UI de la documentación en `/docs`: @@ -42,7 +42,7 @@ Pero incluso si **rellenas los datos** y haces clic en "Execute", como la UI de /// -## Prohibir Cookies Extra +## Prohibir Cookies Extra { #forbid-extra-cookies } En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** las cookies que deseas recibir. @@ -50,7 +50,7 @@ Tu API ahora tiene el poder de controlar su propio **cookies** en **FastAPI**. 😎 diff --git a/docs/es/docs/tutorial/cookie-params.md b/docs/es/docs/tutorial/cookie-params.md index 45b113ff9c..598872c0a5 100644 --- a/docs/es/docs/tutorial/cookie-params.md +++ b/docs/es/docs/tutorial/cookie-params.md @@ -1,14 +1,14 @@ -# Parámetros de Cookie +# Parámetros de Cookie { #cookie-parameters } Puedes definir parámetros de Cookie de la misma manera que defines los parámetros `Query` y `Path`. -## Importar `Cookie` +## Importar `Cookie` { #import-cookie } Primero importa `Cookie`: {* ../../docs_src/cookie_params/tutorial001_an_py310.py hl[3] *} -## Declarar parámetros de `Cookie` +## Declarar parámetros de `Cookie` { #declare-cookie-parameters } Luego declara los parámetros de cookie usando la misma estructura que con `Path` y `Query`. @@ -30,6 +30,16 @@ Para declarar cookies, necesitas usar `Cookie`, porque de lo contrario los pará /// -## Resumen +/// info | Información + +Ten en cuenta que, como **los navegadores manejan las cookies** de formas especiales y por detrás, **no** permiten fácilmente que **JavaScript** las toque. + +Si vas a la **UI de la documentación de la API** en `/docs` podrás ver la **documentación** de cookies para tus *path operations*. + +Pero incluso si **rellenas los datos** y haces clic en "Execute", como la UI de la documentación funciona con **JavaScript**, las cookies no se enviarán y verás un mensaje de **error** como si no hubieras escrito ningún valor. + +/// + +## Resumen { #recap } Declara cookies con `Cookie`, usando el mismo patrón común que `Query` y `Path`. diff --git a/docs/es/docs/tutorial/cors.md b/docs/es/docs/tutorial/cors.md index e493d44312..d6bc7ea61f 100644 --- a/docs/es/docs/tutorial/cors.md +++ b/docs/es/docs/tutorial/cors.md @@ -1,8 +1,8 @@ -# CORS (Cross-Origin Resource Sharing) +# CORS (Cross-Origin Resource Sharing) { #cors-cross-origin-resource-sharing } CORS o "Cross-Origin Resource Sharing" se refiere a situaciones en las que un frontend que se ejecuta en un navegador tiene código JavaScript que se comunica con un backend, y el backend está en un "origen" diferente al frontend. -## Origen +## Origen { #origin } Un origen es la combinación de protocolo (`http`, `https`), dominio (`myapp.com`, `localhost`, `localhost.tiangolo.com`) y puerto (`80`, `443`, `8080`). @@ -14,7 +14,7 @@ Así que, todos estos son orígenes diferentes: Aunque todos están en `localhost`, usan protocolos o puertos diferentes, por lo tanto, son "orígenes" diferentes. -## Pasos +## Pasos { #steps } Entonces, digamos que tienes un frontend corriendo en tu navegador en `http://localhost:8080`, y su JavaScript está tratando de comunicarse con un backend corriendo en `http://localhost` (porque no especificamos un puerto, el navegador asumirá el puerto por defecto `80`). @@ -24,7 +24,7 @@ Para lograr esto, el backend `:80` debe tener una lista de "orígenes permitidos En este caso, la lista tendría que incluir `http://localhost:8080` para que el frontend `:8080` funcione correctamente. -## Comodines +## Comodines { #wildcards } También es posible declarar la lista como `"*"` (un "comodín") para decir que todos están permitidos. @@ -32,7 +32,7 @@ Pero eso solo permitirá ciertos tipos de comunicación, excluyendo todo lo que Así que, para que todo funcione correctamente, es mejor especificar explícitamente los orígenes permitidos. -## Usa `CORSMiddleware` +## Usa `CORSMiddleware` { #use-corsmiddleware } Puedes configurarlo en tu aplicación **FastAPI** usando el `CORSMiddleware`. @@ -56,23 +56,26 @@ Se admiten los siguientes argumentos: * `allow_origin_regex` - Una cadena regex para coincidir con orígenes que deberían estar permitidos para hacer requests cross-origin. por ejemplo, `'https://.*\.example\.org'`. * `allow_methods` - Una lista de métodos HTTP que deberían estar permitidos para requests cross-origin. Por defecto es `['GET']`. Puedes usar `['*']` para permitir todos los métodos estándar. * `allow_headers` - Una lista de headers de request HTTP que deberían estar soportados para requests cross-origin. Por defecto es `[]`. Puedes usar `['*']` para permitir todos los headers. Los headers `Accept`, `Accept-Language`, `Content-Language` y `Content-Type` siempre están permitidos para requests CORS simples. -* `allow_credentials` - Indica que las cookies deberían estar soportadas para requests cross-origin. Por defecto es `False`. Además, `allow_origins` no puede ser configurado a `['*']` para que las credenciales estén permitidas, los orígenes deben ser especificados. +* `allow_credentials` - Indica que las cookies deberían estar soportadas para requests cross-origin. Por defecto es `False`. + + Ninguno de `allow_origins`, `allow_methods` y `allow_headers` puede establecerse a `['*']` si `allow_credentials` está configurado a `True`. Todos deben ser especificados explícitamente. + * `expose_headers` - Indica cualquier header de response que debería ser accesible para el navegador. Por defecto es `[]`. * `max_age` - Establece un tiempo máximo en segundos para que los navegadores almacenen en caché los responses CORS. Por defecto es `600`. El middleware responde a dos tipos particulares de request HTTP... -### Requests de preflight CORS +### Requests de preflight CORS { #cors-preflight-requests } Estos son cualquier request `OPTIONS` con headers `Origin` y `Access-Control-Request-Method`. En este caso, el middleware interceptará el request entrante y responderá con los headers CORS adecuados, y un response `200` o `400` con fines informativos. -### Requests simples +### Requests simples { #simple-requests } Cualquier request con un header `Origin`. En este caso, el middleware pasará el request a través de lo normal, pero incluirá los headers CORS adecuados en el response. -## Más info +## Más info { #more-info } Para más información sobre CORS, revisa la documentación de CORS de Mozilla. diff --git a/docs/es/docs/tutorial/debugging.md b/docs/es/docs/tutorial/debugging.md index 2a7544e830..1e57df2092 100644 --- a/docs/es/docs/tutorial/debugging.md +++ b/docs/es/docs/tutorial/debugging.md @@ -1,14 +1,14 @@ -# Depuración +# Depuración { #debugging } Puedes conectar el depurador en tu editor, por ejemplo con Visual Studio Code o PyCharm. -## Llama a `uvicorn` +## Llama a `uvicorn` { #call-uvicorn } En tu aplicación de FastAPI, importa y ejecuta `uvicorn` directamente: {* ../../docs_src/debugging/tutorial001.py hl[1,15] *} -### Acerca de `__name__ == "__main__"` +### Acerca de `__name__ == "__main__"` { #about-name-main } El objetivo principal de `__name__ == "__main__"` es tener algo de código que se ejecute cuando tu archivo es llamado con: @@ -26,7 +26,7 @@ pero no es llamado cuando otro archivo lo importa, como en: from myapp import app ``` -#### Más detalles +#### Más detalles { #more-details } Supongamos que tu archivo se llama `myapp.py`. @@ -78,7 +78,7 @@ Para más información, revisa -## Atajo +## Atajo { #shortcut } Pero ves que estamos teniendo algo de repetición de código aquí, escribiendo `CommonQueryParams` dos veces: diff --git a/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md index fbe17c67a0..60baa93a90 100644 --- a/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ b/docs/es/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md @@ -1,4 +1,4 @@ -# Dependencias en decoradores de *path operation* +# Dependencias en decoradores de *path operation* { #dependencies-in-path-operation-decorators } En algunos casos realmente no necesitas el valor de retorno de una dependencia dentro de tu *path operation function*. @@ -8,7 +8,7 @@ Pero aún necesitas que sea ejecutada/resuelta. Para esos casos, en lugar de declarar un parámetro de *path operation function* con `Depends`, puedes añadir una `list` de `dependencies` al decorador de *path operation*. -## Agregar `dependencies` al decorador de *path operation* +## Agregar `dependencies` al decorador de *path operation* { #add-dependencies-to-the-path-operation-decorator } El decorador de *path operation* recibe un argumento opcional `dependencies`. @@ -36,23 +36,23 @@ Pero en casos reales, al implementar seguridad, obtendrías más beneficios usan /// -## Errores de dependencias y valores de retorno +## Errores de dependencias y valores de retorno { #dependencies-errors-and-return-values } Puedes usar las mismas *funciones* de dependencia que usas normalmente. -### Requisitos de dependencia +### Requisitos de dependencia { #dependency-requirements } Pueden declarar requisitos de request (como headers) u otras sub-dependencias: {* ../../docs_src/dependencies/tutorial006_an_py39.py hl[8,13] *} -### Lanzar excepciones +### Lanzar excepciones { #raise-exceptions } Estas dependencias pueden `raise` excepciones, igual que las dependencias normales: {* ../../docs_src/dependencies/tutorial006_an_py39.py hl[10,15] *} -### Valores de retorno +### Valores de retorno { #return-values } Y pueden devolver valores o no, los valores no serán usados. @@ -60,10 +60,10 @@ Así que, puedes reutilizar una dependencia normal (que devuelve un valor) que y {* ../../docs_src/dependencies/tutorial006_an_py39.py hl[11,16] *} -## Dependencias para un grupo de *path operations* +## Dependencias para un grupo de *path operations* { #dependencies-for-a-group-of-path-operations } Más adelante, cuando leas sobre cómo estructurar aplicaciones más grandes ([Aplicaciones Más Grandes - Múltiples Archivos](../../tutorial/bigger-applications.md){.internal-link target=_blank}), posiblemente con múltiples archivos, aprenderás cómo declarar un único parámetro `dependencies` para un grupo de *path operations*. -## Dependencias Globales +## Dependencias Globales { #global-dependencies } A continuación veremos cómo añadir dependencias a toda la aplicación `FastAPI`, de modo que se apliquen a cada *path operation*. diff --git a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md index 94290443aa..e29c749a5f 100644 --- a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md @@ -1,8 +1,8 @@ -# Dependencias con yield +# Dependencias con yield { #dependencies-with-yield } -FastAPI admite dependencias que realizan algunos pasos adicionales después de finalizar. +FastAPI admite dependencias que realizan algunos pasos adicionales después de finalizar. -Para hacer esto, usa `yield` en lugar de `return` y escribe los pasos adicionales (código) después. +Para hacer esto, usa `yield` en lugar de `return`, y escribe los pasos adicionales (código) después. /// tip | Consejo @@ -10,7 +10,7 @@ Asegúrate de usar `yield` una sola vez por dependencia. /// -/// note | Nota técnica +/// note | Detalles técnicos Cualquier función que sea válida para usar con: @@ -23,7 +23,7 @@ De hecho, FastAPI usa esos dos decoradores internamente. /// -## Una dependencia de base de datos con `yield` +## Una dependencia de base de datos con `yield` { #a-database-dependency-with-yield } Por ejemplo, podrías usar esto para crear una sesión de base de datos y cerrarla después de finalizar. @@ -35,7 +35,7 @@ El valor generado es lo que se inyecta en *path operations* y otras dependencias {* ../../docs_src/dependencies/tutorial007.py hl[4] *} -El código posterior a la declaración `yield` se ejecuta después de crear el response pero antes de enviarla: +El código posterior a la declaración `yield` se ejecuta después del response: {* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} @@ -47,7 +47,7 @@ Puedes usar funciones `async` o regulares. /// -## Una dependencia con `yield` y `try` +## Una dependencia con `yield` y `try` { #a-dependency-with-yield-and-try } Si usas un bloque `try` en una dependencia con `yield`, recibirás cualquier excepción que se haya lanzado al usar la dependencia. @@ -59,7 +59,7 @@ Del mismo modo, puedes usar `finally` para asegurarte de que los pasos de salida {* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} -## Sub-dependencias con `yield` +## Sub-dependencias con `yield` { #sub-dependencies-with-yield } Puedes tener sub-dependencias y "árboles" de sub-dependencias de cualquier tamaño y forma, y cualquiera o todas ellas pueden usar `yield`. @@ -85,7 +85,7 @@ Puedes tener cualquier combinación de dependencias que quieras. **FastAPI** se asegurará de que todo se ejecute en el orden correcto. -/// note | Nota técnica +/// note | Detalles técnicos Esto funciona gracias a los Context Managers de Python. @@ -93,15 +93,17 @@ Esto funciona gracias a los > dep_req: Start request + Note over dep_req: Run code up to yield + dep_req ->> dep_func: Pass dependency + Note over dep_func: Run code up to yield + dep_func ->> operation: Run path operation with dependency + operation ->> dep_func: Return from path operation + Note over dep_func: Run code after yield + Note over dep_func: ✅ Dependency closed + dep_func ->> client: Send response to client + Note over client: Response sent + Note over dep_req: Run code after yield + Note over dep_req: ✅ Dependency closed +``` -De esta manera probablemente tendrás un código más limpio. +## Dependencias con `yield`, `HTTPException`, `except` y Tareas en Background { #dependencies-with-yield-httpexception-except-and-background-tasks } -/// +Las dependencias con `yield` han evolucionado con el tiempo para cubrir diferentes casos de uso y corregir algunos problemas. -Si solías depender de este comportamiento, ahora deberías crear los recursos para tareas en background dentro de la propia tarea en background, y usar internamente solo datos que no dependan de los recursos de las dependencias con `yield`. +Si quieres ver qué ha cambiado en diferentes versiones de FastAPI, puedes leer más al respecto en la guía avanzada, en [Dependencias avanzadas - Dependencias con `yield`, `HTTPException`, `except` y Tareas en Background](../../advanced/advanced-dependencies.md#dependencies-with-yield-httpexception-except-and-background-tasks){.internal-link target=_blank}. -Por ejemplo, en lugar de usar la misma sesión de base de datos, crearías una nueva sesión de base de datos dentro de la tarea en background, y obtendrías los objetos de la base de datos usando esta nueva sesión. Y luego, en lugar de pasar el objeto de la base de datos como parámetro a la función de tarea en background, pasarías el ID de ese objeto y luego obtendrías el objeto nuevamente dentro de la función de tarea en background. +## Context Managers { #context-managers } -## Context Managers - -### Qué son los "Context Managers" +### Qué son los "Context Managers" { #what-are-context-managers } Los "Context Managers" son aquellos objetos de Python que puedes usar en una declaración `with`. @@ -240,7 +255,7 @@ Cuando el bloque `with` termina, se asegura de cerrar el archivo, incluso si hub Cuando creas una dependencia con `yield`, **FastAPI** creará internamente un context manager para ella y lo combinará con algunas otras herramientas relacionadas. -### Usando context managers en dependencias con `yield` +### Usando context managers en dependencias con `yield` { #using-context-managers-in-dependencies-with-yield } /// warning | Advertencia diff --git a/docs/es/docs/tutorial/dependencies/global-dependencies.md b/docs/es/docs/tutorial/dependencies/global-dependencies.md index 08dd3d5c50..25a14608e9 100644 --- a/docs/es/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/es/docs/tutorial/dependencies/global-dependencies.md @@ -1,4 +1,4 @@ -# Dependencias Globales +# Dependencias Globales { #global-dependencies } Para algunos tipos de aplicaciones, podrías querer agregar dependencias a toda la aplicación. @@ -10,6 +10,6 @@ En ese caso, se aplicarán a todas las *path operations* en la aplicación: Y todas las ideas en la sección sobre [agregar `dependencies` a los *path operation decorators*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} siguen aplicándose, pero en este caso, a todas las *path operations* en la app. -## Dependencias para grupos de *path operations* +## Dependencias para grupos de *path operations* { #dependencies-for-groups-of-path-operations } Más adelante, al leer sobre cómo estructurar aplicaciones más grandes ([Aplicaciones Más Grandes - Múltiples Archivos](../../tutorial/bigger-applications.md){.internal-link target=_blank}), posiblemente con múltiples archivos, aprenderás cómo declarar un solo parámetro de `dependencies` para un grupo de *path operations*. diff --git a/docs/es/docs/tutorial/dependencies/index.md b/docs/es/docs/tutorial/dependencies/index.md index 2fb060177e..a460bd8bf4 100644 --- a/docs/es/docs/tutorial/dependencies/index.md +++ b/docs/es/docs/tutorial/dependencies/index.md @@ -1,10 +1,10 @@ -# Dependencias +# Dependencias { #dependencies } **FastAPI** tiene un sistema de **Inyección de Dependencias** muy poderoso pero intuitivo. Está diseñado para ser muy simple de usar, y para hacer que cualquier desarrollador integre otros componentes con **FastAPI** de forma muy sencilla. -## Qué es la "Inyección de Dependencias" +## Qué es la "Inyección de Dependencias" { #what-is-dependency-injection } **"Inyección de Dependencias"** significa, en programación, que hay una manera para que tu código (en este caso, tus *path operation functions*) declare las cosas que necesita para funcionar y utilizar: "dependencias". @@ -19,13 +19,13 @@ Esto es muy útil cuando necesitas: Todo esto, mientras minimizas la repetición de código. -## Primeros Pasos +## Primeros Pasos { #first-steps } Veamos un ejemplo muy simple. Será tan simple que no es muy útil, por ahora. Pero de esta manera podemos enfocarnos en cómo funciona el sistema de **Inyección de Dependencias**. -### Crear una dependencia, o "dependable" +### Crear una dependencia, o "dependable" { #create-a-dependency-or-dependable } Primero enfoquémonos en la dependencia. @@ -61,11 +61,11 @@ Asegúrate de [Actualizar la versión de FastAPI](../../deployment/versions.md#u /// -### Importar `Depends` +### Importar `Depends` { #import-depends } {* ../../docs_src/dependencies/tutorial001_an_py310.py hl[3] *} -### Declarar la dependencia, en el "dependant" +### Declarar la dependencia, en el "dependant" { #declare-the-dependency-in-the-dependant } De la misma forma en que usas `Body`, `Query`, etc. con los parámetros de tu *path operation function*, usa `Depends` con un nuevo parámetro: @@ -114,7 +114,7 @@ Solo la pasas a `Depends` y **FastAPI** sabe cómo hacer el resto. /// -## Compartir dependencias `Annotated` +## Compartir dependencias `Annotated` { #share-annotated-dependencies } En los ejemplos anteriores, ves que hay un poquito de **duplicación de código**. @@ -138,9 +138,9 @@ Pero porque **FastAPI** está basado en los estándares de Python, incluido `Ann Las dependencias seguirán funcionando como se esperaba, y la **mejor parte** es que la **información de tipo se preservará**, lo que significa que tu editor podrá seguir proporcionándote **autocompletado**, **errores en línea**, etc. Lo mismo para otras herramientas como `mypy`. -Esto será especialmente útil cuando lo uses en una **gran base de código** donde uses **las mismas dependencias** una y otra vez en **muchas *path operations***. +Esto será especialmente útil cuando lo uses en una **gran code base** donde uses **las mismas dependencias** una y otra vez en **muchas *path operations***. -## Usar `async` o no usar `async` +## Usar `async` o no usar `async` { #to-async-or-not-to-async } Como las dependencias también serán llamadas por **FastAPI** (lo mismo que tus *path operation functions*), las mismas reglas aplican al definir tus funciones. @@ -156,7 +156,7 @@ Si no lo sabes, revisa la sección [Async: *"¿Con prisa?"*](../../async.md#in-a /// -## Integración con OpenAPI +## Integración con OpenAPI { #integrated-with-openapi } Todas las declaraciones de request, validaciones y requisitos de tus dependencias (y sub-dependencias) se integrarán en el mismo esquema de OpenAPI. @@ -164,7 +164,7 @@ Así, la documentación interactiva tendrá toda la información de estas depend -## Uso simple +## Uso simple { #simple-usage } Si lo ves, las *path operation functions* se declaran para ser usadas siempre que un *path* y una *operación* coincidan, y luego **FastAPI** se encarga de llamar la función con los parámetros correctos, extrayendo los datos del request. @@ -182,7 +182,7 @@ Otros términos comunes para esta misma idea de "inyección de dependencias" son * inyectables * componentes -## Plug-ins de **FastAPI** +## Plug-ins de **FastAPI** { #fastapi-plug-ins } Las integraciones y "plug-ins" pueden construirse usando el sistema de **Inyección de Dependencias**. Pero, de hecho, en realidad **no hay necesidad de crear "plug-ins"**, ya que al usar dependencias es posible declarar una cantidad infinita de integraciones e interacciones que se vuelven disponibles para tus *path operation functions*. @@ -190,7 +190,7 @@ Y las dependencias se pueden crear de una manera muy simple e intuitiva que te p Verás ejemplos de esto en los próximos capítulos, sobre bases de datos relacionales y NoSQL, seguridad, etc. -## Compatibilidad de **FastAPI** +## Compatibilidad de **FastAPI** { #fastapi-compatibility } La simplicidad del sistema de inyección de dependencias hace que **FastAPI** sea compatible con: @@ -203,7 +203,7 @@ La simplicidad del sistema de inyección de dependencias hace que **FastAPI** se * sistemas de inyección de datos de response * etc. -## Simple y Poderoso +## Simple y Poderoso { #simple-and-powerful } Aunque el sistema de inyección de dependencias jerárquico es muy simple de definir y usar, sigue siendo muy poderoso. @@ -243,7 +243,7 @@ admin_user --> activate_user paying_user --> pro_items ``` -## Integrado con **OpenAPI** +## Integrado con **OpenAPI** { #integrated-with-openapi_1 } Todas estas dependencias, al declarar sus requisitos, también añaden parámetros, validaciones, etc. a tus *path operations*. diff --git a/docs/es/docs/tutorial/dependencies/sub-dependencies.md b/docs/es/docs/tutorial/dependencies/sub-dependencies.md index bba5322072..ea91d14eae 100644 --- a/docs/es/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/es/docs/tutorial/dependencies/sub-dependencies.md @@ -1,4 +1,4 @@ -# Sub-dependencias +# Sub-dependencias { #sub-dependencies } Puedes crear dependencias que tengan **sub-dependencias**. @@ -6,7 +6,7 @@ Pueden ser tan **profundas** como necesites. **FastAPI** se encargará de resolverlas. -## Primera dependencia "dependable" +## Primera dependencia "dependable" { #first-dependency-dependable } Podrías crear una primera dependencia ("dependable") así: @@ -16,7 +16,7 @@ Declara un parámetro de query opcional `q` como un `str`, y luego simplemente l Esto es bastante simple (no muy útil), pero nos ayudará a centrarnos en cómo funcionan las sub-dependencias. -## Segunda dependencia, "dependable" y "dependant" +## Segunda dependencia, "dependable" y "dependant" { #second-dependency-dependable-and-dependant } Luego puedes crear otra función de dependencia (un "dependable") que al mismo tiempo declare una dependencia propia (por lo que también es un "dependant"): @@ -29,7 +29,7 @@ Centrémonos en los parámetros declarados: * También declara una `last_query` cookie opcional, como un `str`. * Si el usuario no proporcionó ningún query `q`, usamos el último query utilizado, que guardamos previamente en una cookie. -## Usa la dependencia +## Usa la dependencia { #use-the-dependency } Entonces podemos usar la dependencia con: @@ -54,7 +54,7 @@ read_query["/items/"] query_extractor --> query_or_cookie_extractor --> read_query ``` -## Usando la misma dependencia múltiples veces +## Usando la misma dependencia múltiples veces { #using-the-same-dependency-multiple-times } Si una de tus dependencias se declara varias veces para la misma *path operation*, por ejemplo, múltiples dependencias tienen una sub-dependencia común, **FastAPI** sabrá llamar a esa sub-dependencia solo una vez por request. @@ -86,7 +86,7 @@ async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False //// -## Resumen +## Resumen { #recap } Aparte de todas las palabras rimbombantes usadas aquí, el sistema de **Inyección de Dependencias** es bastante simple. diff --git a/docs/es/docs/tutorial/encoder.md b/docs/es/docs/tutorial/encoder.md index 9b8919d4b0..319ae1bde8 100644 --- a/docs/es/docs/tutorial/encoder.md +++ b/docs/es/docs/tutorial/encoder.md @@ -1,4 +1,4 @@ -# JSON Compatible Encoder +# Codificador compatible con JSON { #json-compatible-encoder } Hay algunos casos en los que podrías necesitar convertir un tipo de dato (como un modelo de Pydantic) a algo compatible con JSON (como un `dict`, `list`, etc). @@ -6,13 +6,13 @@ Por ejemplo, si necesitas almacenarlo en una base de datos. Para eso, **FastAPI** proporciona una función `jsonable_encoder()`. -## Usando el `jsonable_encoder` +## Usando el `jsonable_encoder` { #using-the-jsonable-encoder } Imaginemos que tienes una base de datos `fake_db` que solo recibe datos compatibles con JSON. Por ejemplo, no recibe objetos `datetime`, ya que no son compatibles con JSON. -Entonces, un objeto `datetime` tendría que ser convertido a un `str` que contenga los datos en formato ISO. +Entonces, un objeto `datetime` tendría que ser convertido a un `str` que contenga los datos en formato ISO. De la misma manera, esta base de datos no recibiría un modelo de Pydantic (un objeto con atributos), solo un `dict`. diff --git a/docs/es/docs/tutorial/extra-data-types.md b/docs/es/docs/tutorial/extra-data-types.md index 28775780f0..e876921ba4 100644 --- a/docs/es/docs/tutorial/extra-data-types.md +++ b/docs/es/docs/tutorial/extra-data-types.md @@ -1,4 +1,4 @@ -# Tipos de Datos Extra +# Tipos de Datos Extra { #extra-data-types } Hasta ahora, has estado usando tipos de datos comunes, como: @@ -17,7 +17,7 @@ Y seguirás teniendo las mismas funcionalidades como hasta ahora: * Validación de datos. * Anotación y documentación automática. -## Otros tipos de datos +## Otros tipos de datos { #other-data-types } Aquí hay algunos de los tipos de datos adicionales que puedes usar: @@ -51,7 +51,7 @@ Aquí hay algunos de los tipos de datos adicionales que puedes usar: * En requests y responses, manejado igual que un `float`. * Puedes revisar todos los tipos de datos válidos de Pydantic aquí: Tipos de datos de Pydantic. -## Ejemplo +## Ejemplo { #example } Aquí tienes un ejemplo de una *path operation* con parámetros usando algunos de los tipos anteriores. diff --git a/docs/es/docs/tutorial/extra-models.md b/docs/es/docs/tutorial/extra-models.md index 0380b96904..ed5bc80d93 100644 --- a/docs/es/docs/tutorial/extra-models.md +++ b/docs/es/docs/tutorial/extra-models.md @@ -1,4 +1,4 @@ -# Modelos Extra +# Modelos Extra { #extra-models } Continuando con el ejemplo anterior, será común tener más de un modelo relacionado. @@ -16,7 +16,7 @@ Si no lo sabes, aprenderás qué es un "hash de contraseña" en los [capítulos /// -## Múltiples modelos +## Múltiples modelos { #multiple-models } Aquí tienes una idea general de cómo podrían ser los modelos con sus campos de contraseña y los lugares donde se utilizan: @@ -30,9 +30,9 @@ Los ejemplos aquí usan `.dict()` para compatibilidad con Pydantic v1, pero debe /// -### Acerca de `**user_in.dict()` +### Acerca de `**user_in.dict()` { #about-user-in-dict } -#### `.dict()` de Pydantic +#### `.dict()` de Pydantic { #pydantics-dict } `user_in` es un modelo Pydantic de la clase `UserIn`. @@ -69,7 +69,7 @@ obtendremos un `dict` de Python con: } ``` -#### Desempaquetando un `dict` +#### Desempaquetando un `dict` { #unpacking-a-dict } Si tomamos un `dict` como `user_dict` y lo pasamos a una función (o clase) con `**user_dict`, Python lo "desempaquetará". Pasará las claves y valores del `user_dict` directamente como argumentos clave-valor. @@ -101,7 +101,7 @@ UserInDB( ) ``` -#### Un modelo Pydantic a partir del contenido de otro +#### Un modelo Pydantic a partir del contenido de otro { #a-pydantic-model-from-the-contents-of-another } Como en el ejemplo anterior obtuvimos `user_dict` de `user_in.dict()`, este código: @@ -120,7 +120,7 @@ UserInDB(**user_in.dict()) Así, obtenemos un modelo Pydantic a partir de los datos en otro modelo Pydantic. -#### Desempaquetando un `dict` y palabras clave adicionales +#### Desempaquetando un `dict` y palabras clave adicionales { #unpacking-a-dict-and-extra-keywords } Y luego agregando el argumento de palabra clave adicional `hashed_password=hashed_password`, como en: @@ -146,7 +146,7 @@ Las funciones adicionales de soporte `fake_password_hasher` y `fake_save_user` s /// -## Reducir duplicación +## Reducir duplicación { #reduce-duplication } Reducir la duplicación de código es una de las ideas centrales en **FastAPI**. @@ -156,7 +156,7 @@ Y estos modelos están compartiendo muchos de los datos y duplicando nombres y t Podríamos hacerlo mejor. -Podemos declarar un modelo `UserBase` que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (declaraciones de tipo, validación, etc). +Podemos declarar un modelo `UserBase` que sirva como base para nuestros otros modelos. Y luego podemos hacer subclases de ese modelo que heredan sus atributos (anotaciones de tipos, validación, etc). Toda la conversión de datos, validación, documentación, etc. seguirá funcionando normalmente. @@ -164,13 +164,13 @@ De esa manera, podemos declarar solo las diferencias entre los modelos (con `pas {* ../../docs_src/extra_models/tutorial002_py310.py hl[7,13:14,17:18,21:22] *} -## `Union` o `anyOf` +## `Union` o `anyOf` { #union-or-anyof } Puedes declarar un response que sea la `Union` de dos o más tipos, eso significa que el response sería cualquiera de ellos. Se definirá en OpenAPI con `anyOf`. -Para hacerlo, usa el type hint estándar de Python `typing.Union`: +Para hacerlo, usa la anotación de tipos estándar de Python `typing.Union`: /// note | Nota @@ -181,21 +181,21 @@ Al definir una ```console -$ fastapi dev main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with + the following code: - ╭────────── FastAPI CLI - Development mode ───────────╮ - │ │ - │ Serving at: http://127.0.0.1:8000 │ - │ │ - │ API docs: http://127.0.0.1:8000/docs │ - │ │ - │ Running in development mode, for production use: │ - │ │ - fastapi run - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [2265862] using WatchFiles -INFO: Started server process [2265873] -INFO: Waiting for application startup. -INFO: Application startup complete. + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C + to quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
@@ -64,7 +56,7 @@ INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) Esa línea muestra la URL donde tu aplicación está siendo servida, en tu máquina local. -### Compruébalo +### Compruébalo { #check-it } Abre tu navegador en http://127.0.0.1:8000. @@ -74,7 +66,7 @@ Verás el response JSON como: {"message": "Hello World"} ``` -### Documentación interactiva de la API +### Documentación interactiva de la API { #interactive-api-docs } Ahora ve a http://127.0.0.1:8000/docs. @@ -82,7 +74,7 @@ Verás la documentación interactiva automática de la API (proporcionada por http://127.0.0.1:8000/redoc. @@ -90,31 +82,31 @@ Verás la documentación alternativa automática (proporcionada por OpenAPI es una especificación que dicta cómo definir un esquema de tu API. Esta definición de esquema incluye los paths de tu API, los posibles parámetros que toman, etc. -#### Esquema de Datos +#### Esquema de Datos { #data-schema } El término "esquema" también podría referirse a la forma de algunos datos, como el contenido JSON. En ese caso, significaría los atributos del JSON, los tipos de datos que tienen, etc. -#### OpenAPI y JSON Schema +#### OpenAPI y JSON Schema { #openapi-and-json-schema } OpenAPI define un esquema de API para tu API. Y ese esquema incluye definiciones (o "esquemas") de los datos enviados y recibidos por tu API utilizando **JSON Schema**, el estándar para esquemas de datos JSON. -#### Revisa el `openapi.json` +#### Revisa el `openapi.json` { #check-the-openapi-json } Si tienes curiosidad por cómo se ve el esquema OpenAPI en bruto, FastAPI automáticamente genera un JSON (esquema) con las descripciones de toda tu API. @@ -143,7 +135,7 @@ Mostrará un JSON que empieza con algo como: ... ``` -#### Para qué sirve OpenAPI +#### Para qué sirve OpenAPI { #what-is-openapi-for } El esquema OpenAPI es lo que impulsa los dos sistemas de documentación interactiva incluidos. @@ -151,15 +143,51 @@ Y hay docenas de alternativas, todas basadas en OpenAPI. Podrías añadir fácil También podrías usarlo para generar código automáticamente, para clientes que se comuniquen con tu API. Por ejemplo, aplicaciones frontend, móviles o IoT. -## Recapitulación, paso a paso +### Despliega tu app (opcional) { #deploy-your-app-optional } -### Paso 1: importa `FastAPI` +Opcionalmente puedes desplegar tu app de FastAPI en FastAPI Cloud, ve y únete a la lista de espera si aún no lo has hecho. 🚀 + +Si ya tienes una cuenta de **FastAPI Cloud** (te invitamos desde la lista de espera 😉), puedes desplegar tu aplicación con un solo comando. + +Antes de desplegar, asegúrate de haber iniciado sesión: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Luego despliega tu app: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +¡Eso es todo! Ahora puedes acceder a tu app en esa URL. ✨ + +## Recapitulación, paso a paso { #recap-step-by-step } + +### Paso 1: importa `FastAPI` { #step-1-import-fastapi } {* ../../docs_src/first_steps/tutorial001.py hl[1] *} `FastAPI` es una clase de Python que proporciona toda la funcionalidad para tu API. -/// note | Detalles Técnicos +/// note | Detalles técnicos `FastAPI` es una clase que hereda directamente de `Starlette`. @@ -167,7 +195,7 @@ Puedes usar toda la funcionalidad de FastAPI Cloud** con un solo comando: `fastapi deploy`. 🎉 + +#### Sobre FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** está construido por el mismo autor y equipo detrás de **FastAPI**. + +Agiliza el proceso de **construir**, **desplegar** y **acceder** a una API con el mínimo esfuerzo. + +Trae la misma **experiencia de desarrollador** de construir apps con FastAPI a **desplegarlas** en la nube. 🎉 + +FastAPI Cloud es el sponsor principal y proveedor de financiación para los proyectos open source de *FastAPI and friends*. ✨ + +#### Despliega en otros proveedores cloud { #deploy-to-other-cloud-providers } + +FastAPI es open source y basado en estándares. Puedes desplegar apps de FastAPI en cualquier proveedor cloud que elijas. + +Sigue las guías de tu proveedor cloud para desplegar apps de FastAPI con ellos. 🤓 + +## Recapitulación { #recap } * Importa `FastAPI`. -* Crea una instancia `app`. +* Crea una instance `app`. * Escribe un **path operation decorator** usando decoradores como `@app.get("/")`. * Define una **path operation function**; por ejemplo, `def root(): ...`. * Ejecuta el servidor de desarrollo usando el comando `fastapi dev`. +* Opcionalmente, despliega tu app con `fastapi deploy`. diff --git a/docs/es/docs/tutorial/handling-errors.md b/docs/es/docs/tutorial/handling-errors.md index 107af2a70a..f64ede86c4 100644 --- a/docs/es/docs/tutorial/handling-errors.md +++ b/docs/es/docs/tutorial/handling-errors.md @@ -1,4 +1,4 @@ -# Manejo de Errores +# Manejo de Errores { #handling-errors } Existen muchas situaciones en las que necesitas notificar un error a un cliente que está usando tu API. @@ -19,15 +19,15 @@ Los códigos de estado en el rango de 400 significan que hubo un error por parte ¿Recuerdas todos esos errores de **"404 Not Found"** (y chistes)? -## Usa `HTTPException` +## Usa `HTTPException` { #use-httpexception } Para devolver responses HTTP con errores al cliente, usa `HTTPException`. -### Importa `HTTPException` +### Importa `HTTPException` { #import-httpexception } {* ../../docs_src/handling_errors/tutorial001.py hl[1] *} -### Lanza un `HTTPException` en tu código +### Lanza un `HTTPException` en tu código { #raise-an-httpexception-in-your-code } `HTTPException` es una excepción de Python normal con datos adicionales relevantes para APIs. @@ -41,7 +41,7 @@ En este ejemplo, cuando el cliente solicita un ítem por un ID que no existe, la {* ../../docs_src/handling_errors/tutorial001.py hl[11] *} -### El response resultante +### El response resultante { #the-resulting-response } Si el cliente solicita `http://example.com/items/foo` (un `item_id` `"foo"`), ese cliente recibirá un código de estado HTTP de 200, y un response JSON de: @@ -69,7 +69,7 @@ Son manejados automáticamente por **FastAPI** y convertidos a JSON. /// -## Agrega headers personalizados +## Agrega headers personalizados { #add-custom-headers } Existen algunas situaciones en las que es útil poder agregar headers personalizados al error HTTP. Por ejemplo, para algunos tipos de seguridad. @@ -79,11 +79,11 @@ Pero en caso de que los necesites para un escenario avanzado, puedes agregar hea {* ../../docs_src/handling_errors/tutorial002.py hl[14] *} -## Instalar manejadores de excepciones personalizados +## Instalar manejadores de excepciones personalizados { #install-custom-exception-handlers } Puedes agregar manejadores de excepciones personalizados con las mismas utilidades de excepciones de Starlette. -Supongamos que tienes una excepción personalizada `UnicornException` que tú (o un paquete que usas) podría lanzar. +Supongamos que tienes una excepción personalizada `UnicornException` que tú (o un paquete que usas) podrías lanzar. Y quieres manejar esta excepción globalmente con FastAPI. @@ -109,7 +109,7 @@ También podrías usar `from starlette.requests import Request` y `from starlett /// -## Sobrescribir los manejadores de excepciones predeterminados +## Sobrescribir los manejadores de excepciones predeterminados { #override-the-default-exception-handlers } **FastAPI** tiene algunos manejadores de excepciones predeterminados. @@ -117,7 +117,7 @@ Estos manejadores se encargan de devolver los responses JSON predeterminadas cua Puedes sobrescribir estos manejadores de excepciones con los tuyos propios. -### Sobrescribir excepciones de validación de request +### Sobrescribir excepciones de validación de request { #override-request-validation-exceptions } Cuando un request contiene datos inválidos, **FastAPI** lanza internamente un `RequestValidationError`. @@ -127,7 +127,7 @@ Para sobrescribirlo, importa el `RequestValidationError` y úsalo con `@app.exce El manejador de excepciones recibirá un `Request` y la excepción. -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *} Ahora, si vas a `/items/foo`, en lugar de obtener el error JSON por defecto con: @@ -149,36 +149,17 @@ Ahora, si vas a `/items/foo`, en lugar de obtener el error JSON por defecto con: obtendrás una versión en texto, con: ``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) +Validation errors: +Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to parse string as an integer ``` -#### `RequestValidationError` vs `ValidationError` - -/// warning | Advertencia - -Estos son detalles técnicos que podrías omitir si no es importante para ti en este momento. - -/// - -`RequestValidationError` es una subclase de `ValidationError` de Pydantic. - -**FastAPI** la usa para que, si usas un modelo Pydantic en `response_model`, y tus datos tienen un error, lo verás en tu log. - -Pero el cliente/usuario no lo verá. En su lugar, el cliente recibirá un "Error Interno del Servidor" con un código de estado HTTP `500`. - -Debería ser así porque si tienes un `ValidationError` de Pydantic en tu *response* o en cualquier lugar de tu código (no en el *request* del cliente), en realidad es un bug en tu código. - -Y mientras lo arreglas, tus clientes/usuarios no deberían tener acceso a información interna sobre el error, ya que eso podría exponer una vulnerabilidad de seguridad. - -### Sobrescribir el manejador de errores de `HTTPException` +### Sobrescribir el manejador de errores de `HTTPException` { #override-the-httpexception-error-handler } De la misma manera, puedes sobrescribir el manejador de `HTTPException`. Por ejemplo, podrías querer devolver un response de texto plano en lugar de JSON para estos errores: -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *} /// note | Nota Técnica @@ -188,7 +169,15 @@ También podrías usar `from starlette.responses import PlainTextResponse`. /// -### Usar el body de `RequestValidationError` +/// warning | Advertencia + +Ten en cuenta que `RequestValidationError` contiene la información del nombre de archivo y la línea donde ocurre el error de validación, para que puedas mostrarla en tus logs con la información relevante si quieres. + +Pero eso significa que si simplemente lo conviertes a un string y devuelves esa información directamente, podrías estar filtrando un poquito de información sobre tu sistema, por eso aquí el código extrae y muestra cada error de forma independiente. + +/// + +### Usar el body de `RequestValidationError` { #use-the-requestvalidationerror-body } El `RequestValidationError` contiene el `body` que recibió con datos inválidos. @@ -226,7 +215,7 @@ Recibirás un response que te dirá que los datos son inválidos conteniendo el } ``` -#### `HTTPException` de FastAPI vs `HTTPException` de Starlette +#### `HTTPException` de FastAPI vs `HTTPException` de Starlette { #fastapis-httpexception-vs-starlettes-httpexception } **FastAPI** tiene su propio `HTTPException`. @@ -238,7 +227,7 @@ Así que puedes seguir lanzando un `HTTPException` de **FastAPI** como de costum Pero cuando registras un manejador de excepciones, deberías registrarlo para el `HTTPException` de Starlette. -De esta manera, si alguna parte del código interno de Starlette, o una extensión o complemento de Starlette, lanza un `HTTPException` de Starlette, tu manejador podrá capturarlo y manejarlo. +De esta manera, si alguna parte del código interno de Starlette, o una extensión o plug-in de Starlette, lanza un `HTTPException` de Starlette, tu manejador podrá capturarlo y manejarlo. En este ejemplo, para poder tener ambos `HTTPException` en el mismo código, las excepciones de Starlette son renombradas a `StarletteHTTPException`: @@ -246,7 +235,7 @@ En este ejemplo, para poder tener ambos `HTTPException` en el mismo código, las from starlette.exceptions import HTTPException as StarletteHTTPException ``` -### Reutilizar los manejadores de excepciones de **FastAPI** +### Reutilizar los manejadores de excepciones de **FastAPI** { #reuse-fastapis-exception-handlers } Si quieres usar la excepción junto con los mismos manejadores de excepciones predeterminados de **FastAPI**, puedes importar y reutilizar los manejadores de excepciones predeterminados de `fastapi.exception_handlers`: diff --git a/docs/es/docs/tutorial/header-param-models.md b/docs/es/docs/tutorial/header-param-models.md index 3676231e67..546a8946d7 100644 --- a/docs/es/docs/tutorial/header-param-models.md +++ b/docs/es/docs/tutorial/header-param-models.md @@ -1,4 +1,4 @@ -# Modelos de Parámetros de Header +# Modelos de Parámetros de Header { #header-parameter-models } Si tienes un grupo de **parámetros de header** relacionados, puedes crear un **modelo Pydantic** para declararlos. @@ -10,7 +10,7 @@ Esto es compatible desde la versión `0.115.0` de FastAPI. 🤓 /// -## Parámetros de Header con un Modelo Pydantic +## Parámetros de Header con un Modelo Pydantic { #header-parameters-with-a-pydantic-model } Declara los **parámetros de header** que necesitas en un **modelo Pydantic**, y luego declara el parámetro como `Header`: @@ -18,7 +18,7 @@ Declara los **parámetros de header** que necesitas en un **modelo Pydantic**, y **FastAPI** **extraerá** los datos para **cada campo** de los **headers** en el request y te dará el modelo Pydantic que definiste. -## Revisa la Documentación +## Revisa la Documentación { #check-the-docs } Puedes ver los headers requeridos en la interfaz de documentación en `/docs`: @@ -26,7 +26,7 @@ Puedes ver los headers requeridos en la interfaz de documentación en `/docs`: -## Prohibir Headers Extra +## Prohibir Headers Extra { #forbid-extra-headers } En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** los headers que deseas recibir. @@ -51,6 +51,22 @@ Por ejemplo, si el cliente intenta enviar un header `tool` con un valor de `plum } ``` -## Resumen +## Desactivar la conversión de guiones bajos { #disable-convert-underscores } + +De la misma forma que con los parámetros de header normales, cuando tienes caracteres de guion bajo en los nombres de los parámetros, se **convierten automáticamente en guiones**. + +Por ejemplo, si tienes un parámetro de header `save_data` en el código, el header HTTP esperado será `save-data`, y aparecerá así en la documentación. + +Si por alguna razón necesitas desactivar esta conversión automática, también puedes hacerlo para los modelos Pydantic de parámetros de header. + +{* ../../docs_src/header_param_models/tutorial003_an_py310.py hl[19] *} + +/// warning | Advertencia + +Antes de establecer `convert_underscores` a `False`, ten en cuenta que algunos proxies y servidores HTTP no permiten el uso de headers con guiones bajos. + +/// + +## Resumen { #summary } Puedes usar **modelos Pydantic** para declarar **headers** en **FastAPI**. 😎 diff --git a/docs/es/docs/tutorial/header-params.md b/docs/es/docs/tutorial/header-params.md index 9c0112bb2b..12b4d1002a 100644 --- a/docs/es/docs/tutorial/header-params.md +++ b/docs/es/docs/tutorial/header-params.md @@ -1,14 +1,14 @@ -# Parámetros de Header +# Parámetros de Header { #header-parameters } Puedes definir los parámetros de Header de la misma manera que defines los parámetros de `Query`, `Path` y `Cookie`. -## Importar `Header` +## Importar `Header` { #import-header } Primero importa `Header`: {* ../../docs_src/header_params/tutorial001_an_py310.py hl[3] *} -## Declarar parámetros de `Header` +## Declarar parámetros de `Header` { #declare-header-parameters } Luego declara los parámetros de header usando la misma estructura que con `Path`, `Query` y `Cookie`. @@ -30,7 +30,7 @@ Para declarar headers, necesitas usar `Header`, porque de otra forma los paráme /// -## Conversión automática +## Conversión automática { #automatic-conversion } `Header` tiene un poquito de funcionalidad extra además de lo que proporcionan `Path`, `Query` y `Cookie`. @@ -54,7 +54,7 @@ Antes de establecer `convert_underscores` a `False`, ten en cuenta que algunos p /// -## Headers duplicados +## Headers duplicados { #duplicate-headers } Es posible recibir headers duplicados. Eso significa, el mismo header con múltiples valores. @@ -84,7 +84,7 @@ El response sería como: } ``` -## Recapitulación +## Recapitulación { #recap } Declara headers con `Header`, usando el mismo patrón común que `Query`, `Path` y `Cookie`. diff --git a/docs/es/docs/tutorial/index.md b/docs/es/docs/tutorial/index.md index dcfc6cdfb7..7804b6854d 100644 --- a/docs/es/docs/tutorial/index.md +++ b/docs/es/docs/tutorial/index.md @@ -1,4 +1,4 @@ -# Tutorial - Guía del Usuario +# Tutorial - Guía del Usuario { #tutorial-user-guide } Este tutorial te muestra cómo usar **FastAPI** con la mayoría de sus funcionalidades, paso a paso. @@ -6,7 +6,7 @@ Cada sección se basa gradualmente en las anteriores, pero está estructurada pa También está diseñado para funcionar como una referencia futura para que puedas volver y ver exactamente lo que necesitas. -## Ejecuta el código +## Ejecuta el código { #run-the-code } Todos los bloques de código pueden ser copiados y usados directamente (de hecho, son archivos Python probados). @@ -15,48 +15,39 @@ Para ejecutar cualquiera de los ejemplos, copia el código a un archivo `main.py
```console -$ fastapi dev main.py -INFO Using path main.py -INFO Resolved absolute path /home/user/code/awesomeapp/main.py -INFO Searching for package file structure from directories with __init__.py files -INFO Importing from /home/user/code/awesomeapp +$ fastapi dev main.py - ╭─ Python module file ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting development server 🚀 -INFO Importing module main -INFO Found importable FastAPI app + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Importable FastAPI app ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Using import string main:app + code Importing the FastAPI app object from the module with + the following code: - ╭────────── FastAPI CLI - Development mode ───────────╮ - │ │ - │ Serving at: http://127.0.0.1:8000 │ - │ │ - │ API docs: http://127.0.0.1:8000/docs │ - │ │ - │ Running in development mode, for production use: │ - │ │ - fastapi run - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [2265862] using WatchFiles -INFO: Started server process [2265873] -INFO: Waiting for application startup. -INFO: Application startup complete. - + app Using import string: main:app + + server Server started at http://127.0.0.1:8000 + server Documentation at http://127.0.0.1:8000/docs + + tip Running in development mode, for production use: + fastapi run + + Logs: + + INFO Will watch for changes in these directories: + ['/home/user/code/awesomeapp'] + INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C + to quit) + INFO Started reloader process [383138] using WatchFiles + INFO Started server process [383153] + INFO Waiting for application startup. + INFO Application startup complete. ```
@@ -67,7 +58,7 @@ Usarlo en tu editor es lo que realmente te muestra los beneficios de FastAPI, al --- -## Instalar FastAPI +## Instalar FastAPI { #install-fastapi } El primer paso es instalar FastAPI. @@ -85,13 +76,15 @@ $ pip install "fastapi[standard]" /// note | Nota -Cuando instalas con `pip install "fastapi[standard]"` viene con algunas dependencias opcionales estándar por defecto. +Cuando instalas con `pip install "fastapi[standard]"` viene con algunas dependencias opcionales estándar por defecto, incluyendo `fastapi-cloud-cli`, que te permite hacer deploy a FastAPI Cloud. Si no quieres tener esas dependencias opcionales, en su lugar puedes instalar `pip install fastapi`. +Si quieres instalar las dependencias estándar pero sin `fastapi-cloud-cli`, puedes instalar con `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. + /// -## Guía Avanzada del Usuario +## Guía Avanzada del Usuario { #advanced-user-guide } También hay una **Guía Avanzada del Usuario** que puedes leer después de esta **Tutorial - Guía del Usuario**. diff --git a/docs/es/docs/tutorial/metadata.md b/docs/es/docs/tutorial/metadata.md index 1561e4ae33..3b65666c48 100644 --- a/docs/es/docs/tutorial/metadata.md +++ b/docs/es/docs/tutorial/metadata.md @@ -1,8 +1,8 @@ -# Metadata y URLs de Docs +# Metadata y URLs de Docs { #metadata-and-docs-urls } Puedes personalizar varias configuraciones de metadata en tu aplicación **FastAPI**. -## Metadata para la API +## Metadata para la API { #metadata-for-api } Puedes establecer los siguientes campos que se usan en la especificación OpenAPI y en las interfaces automáticas de documentación de la API: @@ -30,7 +30,7 @@ Con esta configuración, la documentación automática de la API se vería así: -## Identificador de licencia +## Identificador de licencia { #license-identifier } Desde OpenAPI 3.1.0 y FastAPI 0.99.0, también puedes establecer la `license_info` con un `identifier` en lugar de una `url`. @@ -38,7 +38,7 @@ Por ejemplo: {* ../../docs_src/metadata/tutorial001_1.py hl[31] *} -## Metadata para etiquetas +## Metadata para etiquetas { #metadata-for-tags } También puedes agregar metadata adicional para las diferentes etiquetas usadas para agrupar tus path operations con el parámetro `openapi_tags`. @@ -52,7 +52,7 @@ Cada diccionario puede contener: * `description`: un `str` con una breve descripción para la documentación externa. * `url` (**requerido**): un `str` con la URL para la documentación externa. -### Crear metadata para etiquetas +### Crear metadata para etiquetas { #create-metadata-for-tags } Probemos eso en un ejemplo con etiquetas para `users` y `items`. @@ -68,7 +68,7 @@ No tienes que agregar metadata para todas las etiquetas que uses. /// -### Usar tus etiquetas +### Usar tus etiquetas { #use-your-tags } Usa el parámetro `tags` con tus *path operations* (y `APIRouter`s) para asignarlas a diferentes etiquetas: @@ -80,19 +80,19 @@ Lee más sobre etiquetas en [Configuración de Path Operation](path-operation-co /// -### Revisa la documentación +### Revisa la documentación { #check-the-docs } Ahora, si revisas la documentación, mostrará toda la metadata adicional: -### Orden de las etiquetas +### Orden de las etiquetas { #order-of-tags } El orden de cada diccionario de metadata de etiqueta también define el orden mostrado en la interfaz de documentación. Por ejemplo, aunque `users` iría después de `items` en orden alfabético, se muestra antes porque agregamos su metadata como el primer diccionario en la list. -## URL de OpenAPI +## URL de OpenAPI { #openapi-url } Por defecto, el esquema OpenAPI se sirve en `/openapi.json`. @@ -104,7 +104,7 @@ Por ejemplo, para configurarlo para que se sirva en `/api/v1/openapi.json`: Si quieres deshabilitar el esquema OpenAPI completamente, puedes establecer `openapi_url=None`, eso también deshabilitará las interfaces de usuario de documentación que lo usan. -## URLs de Docs +## URLs de Docs { #docs-urls } Puedes configurar las dos interfaces de usuario de documentación incluidas: diff --git a/docs/es/docs/tutorial/path-operation-configuration.md b/docs/es/docs/tutorial/path-operation-configuration.md index 909cd69b99..858d295be3 100644 --- a/docs/es/docs/tutorial/path-operation-configuration.md +++ b/docs/es/docs/tutorial/path-operation-configuration.md @@ -1,4 +1,4 @@ -# Configuración de Path Operation +# Configuración de Path Operation { #path-operation-configuration } Hay varios parámetros que puedes pasar a tu *path operation decorator* para configurarlo. @@ -8,7 +8,7 @@ Ten en cuenta que estos parámetros se pasan directamente al *path operation dec /// -## Código de Estado del Response +## Código de Estado del Response { #response-status-code } Puedes definir el `status_code` (HTTP) que se utilizará en el response de tu *path operation*. @@ -28,7 +28,7 @@ También podrías usar `from starlette import status`. /// -## Tags +## Tags { #tags } Puedes añadir tags a tu *path operation*, pasando el parámetro `tags` con un `list` de `str` (comúnmente solo una `str`): @@ -38,7 +38,7 @@ Serán añadidas al esquema de OpenAPI y usadas por las interfaces de documentac -### Tags con Enums +### Tags con Enums { #tags-with-enums } Si tienes una gran aplicación, podrías terminar acumulando **varias tags**, y querrías asegurarte de que siempre uses la **misma tag** para *path operations* relacionadas. @@ -48,13 +48,13 @@ En estos casos, podría tener sentido almacenar las tags en un `Enum`. {* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *} -## Resumen y Descripción +## Resumen y Descripción { #summary-and-description } Puedes añadir un `summary` y `description`: {* ../../docs_src/path_operation_configuration/tutorial003_py310.py hl[18:19] *} -## Descripción desde docstring +## Descripción desde docstring { #description-from-docstring } Como las descripciones tienden a ser largas y cubrir múltiples líneas, puedes declarar la descripción de la *path operation* en la docstring de la función y **FastAPI** la leerá desde allí. @@ -66,7 +66,7 @@ Será usado en la documentación interactiva: -## Descripción del Response +## Descripción del Response { #response-description } Puedes especificar la descripción del response con el parámetro `response_description`: @@ -88,7 +88,7 @@ Entonces, si no proporcionas una, **FastAPI** generará automáticamente una de -## Deprecar una *path operation* +## Deprecar una *path operation* { #deprecate-a-path-operation } Si necesitas marcar una *path operation* como deprecated, pero sin eliminarla, pasa el parámetro `deprecated`: @@ -102,6 +102,6 @@ Revisa cómo lucen las *path operations* deprecadas y no deprecadas: -## Resumen +## Resumen { #recap } Puedes configurar y añadir metadatos a tus *path operations* fácilmente pasando parámetros a los *path operation decorators*. diff --git a/docs/es/docs/tutorial/query-param-models.md b/docs/es/docs/tutorial/query-param-models.md index 8338fc358c..e335cfe61e 100644 --- a/docs/es/docs/tutorial/query-param-models.md +++ b/docs/es/docs/tutorial/query-param-models.md @@ -1,4 +1,4 @@ -# Modelos de Parámetros Query +# Modelos de Parámetros Query { #query-parameter-models } Si tienes un grupo de **parámetros query** que están relacionados, puedes crear un **modelo de Pydantic** para declararlos. @@ -10,7 +10,7 @@ Esto es compatible desde la versión `0.115.0` de FastAPI. 🤓 /// -## Parámetros Query con un Modelo Pydantic +## Parámetros Query con un Modelo Pydantic { #query-parameters-with-a-pydantic-model } Declara los **parámetros query** que necesitas en un **modelo de Pydantic**, y luego declara el parámetro como `Query`: @@ -18,7 +18,7 @@ Declara los **parámetros query** que necesitas en un **modelo de Pydantic**, y **FastAPI** **extraerá** los datos para **cada campo** de los **parámetros query** en el request y te proporcionará el modelo de Pydantic que definiste. -## Revisa la Documentación +## Revisa la Documentación { #check-the-docs } Puedes ver los parámetros query en la UI de documentación en `/docs`: @@ -26,7 +26,7 @@ Puedes ver los parámetros query en la UI de documentación en `/docs`: -## Prohibir Parámetros Query Extras +## Prohibir Parámetros Query Extras { #forbid-extra-query-parameters } En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** los parámetros query que deseas recibir. @@ -57,7 +57,7 @@ Recibirán un response de **error** que les indica que el parámetro query `tool } ``` -## Resumen +## Resumen { #summary } Puedes usar **modelos de Pydantic** para declarar **parámetros query** en **FastAPI**. 😎 diff --git a/docs/es/docs/tutorial/query-params-str-validations.md b/docs/es/docs/tutorial/query-params-str-validations.md index 9cb76156f0..22e70cf199 100644 --- a/docs/es/docs/tutorial/query-params-str-validations.md +++ b/docs/es/docs/tutorial/query-params-str-validations.md @@ -1,4 +1,4 @@ -# Parámetros de Query y Validaciones de String +# Parámetros de Query y Validaciones de String { #query-parameters-and-string-validations } **FastAPI** te permite declarar información adicional y validación para tus parámetros. @@ -6,48 +6,28 @@ Tomemos esta aplicación como ejemplo: {* ../../docs_src/query_params_str_validations/tutorial001_py310.py hl[7] *} -El parámetro de query `q` es del tipo `Union[str, None]` (o `str | None` en Python 3.10), lo que significa que es de tipo `str` pero también podría ser `None`, y de hecho, el valor por defecto es `None`, así que FastAPI sabrá que no es requerido. +El parámetro de query `q` es de tipo `str | None`, lo que significa que es de tipo `str` pero también podría ser `None`, y de hecho, el valor por defecto es `None`, así que FastAPI sabrá que no es requerido. /// note | Nota FastAPI sabrá que el valor de `q` no es requerido por el valor por defecto `= None`. -El `Union` en `Union[str, None]` permitirá a tu editor darte un mejor soporte y detectar errores. +Tener `str | None` permitirá que tu editor te dé un mejor soporte y detecte errores. /// -## Validaciones adicionales +## Validaciones adicionales { #additional-validation } Vamos a hacer que, aunque `q` sea opcional, siempre que se proporcione, **su longitud no exceda los 50 caracteres**. -### Importar `Query` y `Annotated` +### Importar `Query` y `Annotated` { #import-query-and-annotated } Para lograr eso, primero importa: * `Query` desde `fastapi` -* `Annotated` desde `typing` (o desde `typing_extensions` en Python por debajo de 3.9) +* `Annotated` desde `typing` -//// tab | Python 3.10+ - -En Python 3.9 o superior, `Annotated` es parte de la biblioteca estándar, así que puedes importarlo desde `typing`. - -```Python hl_lines="1 3" -{!> ../../docs_src/query_params_str_validations/tutorial002_an_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -En versiones de Python por debajo de 3.9 importas `Annotated` desde `typing_extensions`. - -Ya estará instalado con FastAPI. - -```Python hl_lines="3-4" -{!> ../../docs_src/query_params_str_validations/tutorial002_an.py!} -``` - -//// +{* ../../docs_src/query_params_str_validations/tutorial002_an_py310.py hl[1,3] *} /// info | Información @@ -59,9 +39,9 @@ Asegúrate de [Actualizar la versión de FastAPI](../deployment/versions.md#upgr /// -## Usar `Annotated` en el tipo del parámetro `q` +## Usar `Annotated` en el tipo del parámetro `q` { #use-annotated-in-the-type-for-the-q-parameter } -¿Recuerdas que te dije antes que `Annotated` puede ser usado para agregar metadatos a tus parámetros en la [Introducción a Tipos de Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}? +¿Recuerdas que te dije antes que `Annotated` puede usarse para agregar metadatos a tus parámetros en la [Introducción a Tipos de Python](../python-types.md#type-hints-with-metadata-annotations){.internal-link target=_blank}? Ahora es el momento de usarlo con FastAPI. 🚀 @@ -105,7 +85,7 @@ Ambas versiones significan lo mismo, `q` es un parámetro que puede ser un `str` Ahora vamos a lo divertido. 🎉 -## Agregar `Query` a `Annotated` en el parámetro `q` +## Agregar `Query` a `Annotated` en el parámetro `q` { #add-query-to-annotated-in-the-q-parameter } Ahora que tenemos este `Annotated` donde podemos poner más información (en este caso algunas validaciones adicionales), agrega `Query` dentro de `Annotated`, y establece el parámetro `max_length` a `50`: @@ -127,9 +107,9 @@ FastAPI ahora: * Mostrará un **error claro** para el cliente cuando los datos no sean válidos * **Documentará** el parámetro en el OpenAPI esquema *path operation* (así aparecerá en la **UI de documentación automática**) -## Alternativa (antigua): `Query` como valor por defecto +## Alternativa (antigua): `Query` como valor por defecto { #alternative-old-query-as-the-default-value } -Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras `Query` como el valor por defecto de tu parámetro, en lugar de ponerlo en `Annotated`. Hay una alta probabilidad de que veas código usándolo alrededor, así que te lo explicaré. +Versiones anteriores de FastAPI (antes de 0.95.0) requerían que usaras `Query` como el valor por defecto de tu parámetro, en lugar de ponerlo en `Annotated`, hay una alta probabilidad de que veas código usándolo alrededor, así que te lo explicaré. /// tip | Consejo @@ -141,63 +121,32 @@ Así es como usarías `Query()` como el valor por defecto de tu parámetro de fu {* ../../docs_src/query_params_str_validations/tutorial002_py310.py hl[7] *} -Ya que en este caso (sin usar `Annotated`) debemos reemplazar el valor por defecto `None` en la función con `Query()`, ahora necesitamos establecer el valor por defecto con el parámetro `Query(default=None)`, esto sirve al mismo propósito de definir ese valor por defecto (al menos para FastAPI). +Como en este caso (sin usar `Annotated`) debemos reemplazar el valor por defecto `None` en la función con `Query()`, ahora necesitamos establecer el valor por defecto con el parámetro `Query(default=None)`, esto sirve al mismo propósito de definir ese valor por defecto (al menos para FastAPI). Entonces: -```Python -q: Union[str, None] = Query(default=None) -``` - -...hace que el parámetro sea opcional, con un valor por defecto de `None`, lo mismo que: - -```Python -q: Union[str, None] = None -``` - -Y en Python 3.10 y superior: - ```Python q: str | None = Query(default=None) ``` ...hace que el parámetro sea opcional, con un valor por defecto de `None`, lo mismo que: + ```Python q: str | None = None ``` -Pero las versiones de `Query` lo declaran explícitamente como un parámetro de query. - -/// info | Información - -Ten en cuenta que la parte más importante para hacer un parámetro opcional es la parte: - -```Python -= None -``` - -o la parte: - -```Python -= Query(default=None) -``` - -ya que usará ese `None` como el valor por defecto, y de esa manera hará el parámetro **no requerido**. - -La parte `Union[str, None]` permite que tu editor brinde un mejor soporte, pero no es lo que le dice a FastAPI que este parámetro no es requerido. - -/// +Pero la versión con `Query` lo declara explícitamente como un parámetro de query. Luego, podemos pasar más parámetros a `Query`. En este caso, el parámetro `max_length` que se aplica a los strings: ```Python -q: Union[str, None] = Query(default=None, max_length=50) +q: str | None = Query(default=None, max_length=50) ``` -Esto validará los datos, mostrará un error claro cuando los datos no sean válidos, y documentará el parámetro en el esquema del *path operation* de OpenaPI. +Esto validará los datos, mostrará un error claro cuando los datos no sean válidos, y documentará el parámetro en el esquema del *path operation* de OpenAPI. -### `Query` como valor por defecto o en `Annotated` +### `Query` como valor por defecto o en `Annotated` { #query-as-the-default-value-or-in-annotated } Ten en cuenta que cuando uses `Query` dentro de `Annotated` no puedes usar el parámetro `default` para `Query`. @@ -217,13 +166,13 @@ Así que utilizarías (preferentemente): q: Annotated[str, Query()] = "rick" ``` -...o en code bases más antiguos encontrarás: +...o en code bases más antiguas encontrarás: ```Python q: str = Query(default="rick") ``` -### Ventajas de `Annotated` +### Ventajas de `Annotated` { #advantages-of-annotated } **Usar `Annotated` es recomendado** en lugar del valor por defecto en los parámetros de función, es **mejor** por múltiples razones. 🤓 @@ -235,29 +184,29 @@ Cuando no usas `Annotated` y en su lugar usas el estilo de valor por defecto **( Dado que `Annotated` puede tener más de una anotación de metadato, ahora podrías incluso usar la misma función con otras herramientas, como Typer. 🚀 -## Agregar más validaciones +## Agregar más validaciones { #add-more-validations } También puedes agregar un parámetro `min_length`: {* ../../docs_src/query_params_str_validations/tutorial003_an_py310.py hl[10] *} -## Agregar expresiones regulares +## Agregar expresiones regulares { #add-regular-expressions } -Puedes definir una expresión regular `pattern` que el parámetro debe coincidir: +Puedes definir un expresión regular `pattern` que el parámetro debe coincidir: {* ../../docs_src/query_params_str_validations/tutorial004_an_py310.py hl[11] *} Este patrón específico de expresión regular comprueba que el valor recibido del parámetro: -* `^`: comience con los siguientes caracteres, no tiene caracteres antes. +* `^`: comienza con los siguientes caracteres, no tiene caracteres antes. * `fixedquery`: tiene el valor exacto `fixedquery`. * `$`: termina allí, no tiene más caracteres después de `fixedquery`. Si te sientes perdido con todas estas ideas de **"expresión regular"**, no te preocupes. Son un tema difícil para muchas personas. Aún puedes hacer muchas cosas sin necesitar expresiones regulares todavía. -Pero cuando las necesites y vayas a aprenderlas, ya sabes que puedes usarlas directamente en **FastAPI**. +Ahora sabes que cuando las necesites puedes usarlas en **FastAPI**. -### Pydantic v1 `regex` en lugar de `pattern` +### Pydantic v1 `regex` en lugar de `pattern` { #pydantic-v1-regex-instead-of-pattern } Antes de la versión 2 de Pydantic y antes de FastAPI 0.100.0, el parámetro se llamaba `regex` en lugar de `pattern`, pero ahora está en desuso. @@ -271,7 +220,7 @@ Todavía podrías ver algo de código que lo usa: Pero que sepas que esto está deprecado y debería actualizarse para usar el nuevo parámetro `pattern`. 🤓 -## Valores por defecto +## Valores por defecto { #default-values } Puedes, por supuesto, usar valores por defecto diferentes de `None`. @@ -285,7 +234,7 @@ Tener un valor por defecto de cualquier tipo, incluyendo `None`, hace que el par /// -## Parámetros requeridos +## Parámetros requeridos { #required-parameters } Cuando no necesitamos declarar más validaciones o metadatos, podemos hacer que el parámetro de query `q` sea requerido simplemente no declarando un valor por defecto, como: @@ -296,52 +245,28 @@ q: str en lugar de: ```Python -q: Union[str, None] = None +q: str | None = None ``` Pero ahora lo estamos declarando con `Query`, por ejemplo, como: -//// tab | Annotated - ```Python -q: Annotated[Union[str, None], Query(min_length=3)] = None +q: Annotated[str | None, Query(min_length=3)] = None ``` -//// - -//// tab | non-Annotated - -```Python -q: Union[str, None] = Query(default=None, min_length=3) -``` - -//// - Así que, cuando necesites declarar un valor como requerido mientras usas `Query`, simplemente puedes no declarar un valor por defecto: {* ../../docs_src/query_params_str_validations/tutorial006_an_py39.py hl[9] *} -### Requerido, puede ser `None` +### Requerido, puede ser `None` { #required-can-be-none } Puedes declarar que un parámetro puede aceptar `None`, pero que aún así es requerido. Esto obligaría a los clientes a enviar un valor, incluso si el valor es `None`. -Para hacer eso, puedes declarar que `None` es un tipo válido pero aún usar `...` como el valor por defecto: +Para hacer eso, puedes declarar que `None` es un tipo válido pero simplemente no declarar un valor por defecto: {* ../../docs_src/query_params_str_validations/tutorial006c_an_py310.py hl[9] *} -/// tip | Consejo - -Pydantic, que es lo que impulsa toda la validación y serialización de datos en FastAPI, tiene un comportamiento especial cuando usas `Optional` o `Union[Something, None]` sin un valor por defecto, puedes leer más al respecto en la documentación de Pydantic sobre Campos requeridos. - -/// - -/// tip | Consejo - -Recuerda que en la mayoría de los casos, cuando algo es requerido, puedes simplemente omitir el default, así que normalmente no tienes que usar `...`. - -/// - -## Lista de parámetros de Query / múltiples valores +## Lista de parámetros de Query / múltiples valores { #query-parameter-list-multiple-values } Cuando defines un parámetro de query explícitamente con `Query` también puedes declararlo para recibir una lista de valores, o dicho de otra manera, para recibir múltiples valores. @@ -378,9 +303,9 @@ La documentación interactiva de API se actualizará en consecuencia, para permi -### Lista de parámetros de Query / múltiples valores con valores por defecto +### Lista de parámetros de Query / múltiples valores con valores por defecto { #query-parameter-list-multiple-values-with-defaults } -Y también puedes definir un valor por defecto `list` de valores si no se proporcionan ninguno: +También puedes definir un valor por defecto `list` de valores si no se proporciona ninguno: {* ../../docs_src/query_params_str_validations/tutorial012_an_py39.py hl[9] *} @@ -401,9 +326,9 @@ el valor por defecto de `q` será: `["foo", "bar"]` y tu response será: } ``` -#### Usando solo `list` +#### Usando solo `list` { #using-just-list } -También puedes usar `list` directamente en lugar de `List[str]` (o `list[str]` en Python 3.9+): +También puedes usar `list` directamente en lugar de `list[str]`: {* ../../docs_src/query_params_str_validations/tutorial013_an_py39.py hl[9] *} @@ -411,11 +336,11 @@ También puedes usar `list` directamente en lugar de `List[str]` (o `list[str]` Ten en cuenta que en este caso, FastAPI no comprobará el contenido de la lista. -Por ejemplo, `List[int]` comprobaría (y documentaría) que el contenido de la lista son enteros. Pero `list` sola no lo haría. +Por ejemplo, `list[int]` comprobaría (y documentaría) que el contenido de la lista son enteros. Pero `list` sola no lo haría. /// -## Declarar más metadatos +## Declarar más metadatos { #declare-more-metadata } Puedes agregar más información sobre el parámetro. @@ -437,7 +362,7 @@ Y una `description`: {* ../../docs_src/query_params_str_validations/tutorial008_an_py310.py hl[14] *} -## Alias para parámetros +## Alias para parámetros { #alias-parameters } Imagina que quieres que el parámetro sea `item-query`. @@ -457,7 +382,7 @@ Entonces puedes declarar un `alias`, y ese alias será usado para encontrar el v {* ../../docs_src/query_params_str_validations/tutorial009_an_py310.py hl[9] *} -## Declarar parámetros obsoletos +## Declarar parámetros obsoletos { #deprecating-parameters } Ahora digamos que ya no te gusta este parámetro. @@ -471,13 +396,75 @@ La documentación lo mostrará así: -## Excluir parámetros de OpenAPI +## Excluir parámetros de OpenAPI { #exclude-parameters-from-openapi } Para excluir un parámetro de query del esquema de OpenAPI generado (y por lo tanto, de los sistemas de documentación automática), establece el parámetro `include_in_schema` de `Query` a `False`: {* ../../docs_src/query_params_str_validations/tutorial014_an_py310.py hl[10] *} -## Recapitulación +## Validación personalizada { #custom-validation } + +Podría haber casos donde necesites hacer alguna **validación personalizada** que no puede hacerse con los parámetros mostrados arriba. + +En esos casos, puedes usar una **función validadora personalizada** que se aplique después de la validación normal (por ejemplo, después de validar que el valor es un `str`). + +Puedes lograr eso usando `AfterValidator` de Pydantic dentro de `Annotated`. + +/// tip | Consejo + +Pydantic también tiene `BeforeValidator` y otros. 🤓 + +/// + +Por ejemplo, este validador personalizado comprueba que el ID del ítem empiece con `isbn-` para un número de libro ISBN o con `imdb-` para un ID de URL de película de IMDB: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py hl[5,16:19,24] *} + +/// info | Información + +Esto está disponible con Pydantic versión 2 o superior. 😎 + +/// + +/// tip | Consejo + +Si necesitas hacer cualquier tipo de validación que requiera comunicarte con algún **componente externo**, como una base de datos u otra API, deberías usar **Dependencias de FastAPI**, las aprenderás más adelante. + +Estos validadores personalizados son para cosas que pueden comprobarse **solo** con los **mismos datos** provistos en el request. + +/// + +### Entiende ese código { #understand-that-code } + +El punto importante es solo usar **`AfterValidator` con una función dentro de `Annotated`**. Si quieres, sáltate esta parte. 🤸 + +--- + +Pero si te da curiosidad este ejemplo de código específico y sigues entretenido, aquí tienes algunos detalles extra. + +#### String con `value.startswith()` { #string-with-value-startswith } + +¿Lo notaste? un string usando `value.startswith()` puede recibir una tupla, y comprobará cada valor en la tupla: + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[16:19] hl[17] *} + +#### Un ítem aleatorio { #a-random-item } + +Con `data.items()` obtenemos un objeto iterable con tuplas que contienen la clave y el valor para cada elemento del diccionario. + +Convertimos este objeto iterable en una `list` propiamente dicha con `list(data.items())`. + +Luego con `random.choice()` podemos obtener un **valor aleatorio** de la lista, así que obtenemos una tupla con `(id, name)`. Será algo como `("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")`. + +Luego **asignamos esos dos valores** de la tupla a las variables `id` y `name`. + +Así, si el usuario no proporcionó un ID de ítem, aún recibirá una sugerencia aleatoria. + +...hacemos todo esto en una **sola línea simple**. 🤯 ¿No te encanta Python? 🐍 + +{* ../../docs_src/query_params_str_validations/tutorial015_an_py310.py ln[22:30] hl[29] *} + +## Recapitulación { #recap } Puedes declarar validaciones y metadatos adicionales para tus parámetros. @@ -494,6 +481,8 @@ Validaciones específicas para strings: * `max_length` * `pattern` +Validaciones personalizadas usando `AfterValidator`. + En estos ejemplos viste cómo declarar validaciones para valores de tipo `str`. Mira los siguientes capítulos para aprender cómo declarar validaciones para otros tipos, como números. diff --git a/docs/es/docs/tutorial/request-files.md b/docs/es/docs/tutorial/request-files.md index 330523c7ef..cc99deb2e3 100644 --- a/docs/es/docs/tutorial/request-files.md +++ b/docs/es/docs/tutorial/request-files.md @@ -1,4 +1,4 @@ -# Archivos de Request +# Archivos de Request { #request-files } Puedes definir archivos que serán subidos por el cliente utilizando `File`. @@ -16,13 +16,13 @@ Esto es porque los archivos subidos se envían como "form data". /// -## Importar `File` +## Importar `File` { #import-file } Importa `File` y `UploadFile` desde `fastapi`: {* ../../docs_src/request_files/tutorial001_an_py39.py hl[3] *} -## Definir Parámetros `File` +## Definir Parámetros `File` { #define-file-parameters } Crea parámetros de archivo de la misma manera que lo harías para `Body` o `Form`: @@ -50,7 +50,7 @@ Ten en cuenta que esto significa que todo el contenido se almacenará en memoria Pero hay varios casos en los que podrías beneficiarte de usar `UploadFile`. -## Parámetros de Archivo con `UploadFile` +## Parámetros de Archivo con `UploadFile` { #file-parameters-with-uploadfile } Define un parámetro de archivo con un tipo de `UploadFile`: @@ -66,7 +66,7 @@ Usar `UploadFile` tiene varias ventajas sobre `bytes`: * Tiene una interfaz `async` parecida a un archivo. * Expone un objeto Python real `SpooledTemporaryFile` que puedes pasar directamente a otros paquetes que esperan un objeto parecido a un archivo. -### `UploadFile` +### `UploadFile` { #uploadfile } `UploadFile` tiene los siguientes atributos: @@ -109,7 +109,7 @@ El `UploadFile` de **FastAPI** hereda directamente del `UploadFile` de **Starlet /// -## Qué es "Form Data" +## Qué es "Form Data" { #what-is-form-data } La manera en que los forms de HTML (`
`) envían los datos al servidor normalmente utiliza una codificación "especial" para esos datos, es diferente de JSON. @@ -121,7 +121,7 @@ Los datos de los forms normalmente se codifican usando el "media type" `applicat Pero cuando el formulario incluye archivos, se codifica como `multipart/form-data`. Si usas `File`, **FastAPI** sabrá que tiene que obtener los archivos de la parte correcta del cuerpo. -Si deseas leer más sobre estas codificaciones y campos de formularios, dirígete a la MDN web docs para POST. +Si deseas leer más sobre estas codificaciones y campos de formularios, dirígete a la MDN web docs para POST. /// @@ -133,19 +133,19 @@ Esto no es una limitación de **FastAPI**, es parte del protocolo HTTP. /// -## Subida de Archivos Opcional +## Subida de Archivos Opcional { #optional-file-upload } Puedes hacer un archivo opcional utilizando anotaciones de tipos estándar y estableciendo un valor por defecto de `None`: {* ../../docs_src/request_files/tutorial001_02_an_py310.py hl[9,17] *} -## `UploadFile` con Metadatos Adicionales +## `UploadFile` con Metadatos Adicionales { #uploadfile-with-additional-metadata } También puedes usar `File()` con `UploadFile`, por ejemplo, para establecer metadatos adicionales: {* ../../docs_src/request_files/tutorial001_03_an_py39.py hl[9,15] *} -## Subidas de Múltiples Archivos +## Subidas de Múltiples Archivos { #multiple-file-uploads } Es posible subir varios archivos al mismo tiempo. @@ -165,12 +165,12 @@ También podrías usar `from starlette.responses import HTMLResponse`. /// -### Subidas de Múltiples Archivos con Metadatos Adicionales +### Subidas de Múltiples Archivos con Metadatos Adicionales { #multiple-file-uploads-with-additional-metadata } Y de la misma manera que antes, puedes usar `File()` para establecer parámetros adicionales, incluso para `UploadFile`: {* ../../docs_src/request_files/tutorial003_an_py39.py hl[11,18:20] *} -## Recapitulación +## Recapitulación { #recap } Usa `File`, `bytes` y `UploadFile` para declarar archivos que se subirán en el request, enviados como form data. diff --git a/docs/es/docs/tutorial/request-form-models.md b/docs/es/docs/tutorial/request-form-models.md index 9d5d7495a0..1f4668c840 100644 --- a/docs/es/docs/tutorial/request-form-models.md +++ b/docs/es/docs/tutorial/request-form-models.md @@ -1,4 +1,4 @@ -# Modelos de Formulario +# Modelos de Formulario { #form-models } Puedes usar **modelos de Pydantic** para declarar **campos de formulario** en FastAPI. @@ -20,7 +20,7 @@ Esto es compatible desde la versión `0.113.0` de FastAPI. 🤓 /// -## Modelos de Pydantic para Formularios +## Modelos de Pydantic para Formularios { #pydantic-models-for-forms } Solo necesitas declarar un **modelo de Pydantic** con los campos que quieres recibir como **campos de formulario**, y luego declarar el parámetro como `Form`: @@ -28,7 +28,7 @@ Solo necesitas declarar un **modelo de Pydantic** con los campos que quieres rec **FastAPI** **extraerá** los datos de **cada campo** de los **form data** en el request y te dará el modelo de Pydantic que definiste. -## Revisa la Documentación +## Revisa la Documentación { #check-the-docs } Puedes verificarlo en la interfaz de documentación en `/docs`: @@ -36,7 +36,7 @@ Puedes verificarlo en la interfaz de documentación en `/docs`: -## Prohibir Campos de Formulario Extra +## Prohibir Campos de Formulario Extra { #forbid-extra-form-fields } En algunos casos de uso especiales (probablemente no muy comunes), podrías querer **restringir** los campos de formulario a solo aquellos declarados en el modelo de Pydantic. Y **prohibir** cualquier campo **extra**. @@ -73,6 +73,6 @@ Recibirá un response de error indicando que el campo `extra` no está permitido } ``` -## Resumen +## Resumen { #summary } Puedes usar modelos de Pydantic para declarar campos de formulario en FastAPI. 😎 diff --git a/docs/es/docs/tutorial/request-forms-and-files.md b/docs/es/docs/tutorial/request-forms-and-files.md index 51dfbb3578..363553e862 100644 --- a/docs/es/docs/tutorial/request-forms-and-files.md +++ b/docs/es/docs/tutorial/request-forms-and-files.md @@ -1,4 +1,4 @@ -# Request Forms and Files +# Formularios y archivos del request { #request-forms-and-files } Puedes definir archivos y campos de formulario al mismo tiempo usando `File` y `Form`. @@ -14,11 +14,11 @@ $ pip install python-multipart /// -## Importar `File` y `Form` +## Importa `File` y `Form` { #import-file-and-form } {* ../../docs_src/request_forms_and_files/tutorial001_an_py39.py hl[3] *} -## Definir parámetros `File` y `Form` +## Define parámetros `File` y `Form` { #define-file-and-form-parameters } Crea parámetros de archivo y formulario de la misma manera que lo harías para `Body` o `Query`: @@ -36,6 +36,6 @@ Esto no es una limitación de **FastAPI**, es parte del protocolo HTTP. /// -## Resumen +## Resumen { #recap } Usa `File` y `Form` juntos cuando necesites recibir datos y archivos en el mismo request. diff --git a/docs/es/docs/tutorial/request-forms.md b/docs/es/docs/tutorial/request-forms.md index a9d62e660b..33061e6a1b 100644 --- a/docs/es/docs/tutorial/request-forms.md +++ b/docs/es/docs/tutorial/request-forms.md @@ -1,10 +1,10 @@ -# Form Data +# Datos de formulario { #form-data } Cuando necesitas recibir campos de formulario en lugar de JSON, puedes usar `Form`. /// info | Información -Para usar forms, primero instala `python-multipart`. +Para usar formularios, primero instala `python-multipart`. Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo, y luego instalarlo, por ejemplo: @@ -14,13 +14,13 @@ $ pip install python-multipart /// -## Importar `Form` +## Importar `Form` { #import-form } Importar `Form` desde `fastapi`: {* ../../docs_src/request_forms/tutorial001_an_py39.py hl[3] *} -## Definir parámetros de `Form` +## Definir parámetros de `Form` { #define-form-parameters } Crea parámetros de formulario de la misma manera que lo harías para `Body` o `Query`: @@ -28,7 +28,7 @@ Crea parámetros de formulario de la misma manera que lo harías para `Body` o ` Por ejemplo, en una de las formas en las que se puede usar la especificación OAuth2 (llamada "password flow") se requiere enviar un `username` y `password` como campos de formulario. -La especificación requiere que los campos se llamen exactamente `username` y `password`, y que se envíen como campos de formulario, no JSON. +La spec requiere que los campos se llamen exactamente `username` y `password`, y que se envíen como campos de formulario, no JSON. Con `Form` puedes declarar las mismas configuraciones que con `Body` (y `Query`, `Path`, `Cookie`), incluyendo validación, ejemplos, un alias (por ejemplo, `user-name` en lugar de `username`), etc. @@ -40,23 +40,23 @@ Con `Form` puedes declarar las mismas configuraciones que con `Body` (y `Query`, /// tip | Consejo -Para declarar bodies de forms, necesitas usar `Form` explícitamente, porque sin él, los parámetros se interpretarían como parámetros de query o como parámetros de body (JSON). +Para declarar bodies de formularios, necesitas usar `Form` explícitamente, porque sin él, los parámetros se interpretarían como parámetros de query o como parámetros de body (JSON). /// -## Sobre "Campos de Formulario" +## Sobre "Campos de formulario" { #about-form-fields } -La manera en que los forms HTML (`
`) envían los datos al servidor normalmente usa una codificación "especial" para esos datos, es diferente de JSON. +La manera en que los formularios HTML (`
`) envían los datos al servidor normalmente usa una codificación "especial" para esos datos, es diferente de JSON. **FastAPI** se encargará de leer esos datos del lugar correcto en lugar de JSON. /// note | Detalles técnicos -Los datos de forms normalmente se codifican usando el "media type" `application/x-www-form-urlencoded`. +Los datos de formularios normalmente se codifican usando el "media type" `application/x-www-form-urlencoded`. Pero cuando el formulario incluye archivos, se codifica como `multipart/form-data`. Leerás sobre la gestión de archivos en el próximo capítulo. -Si quieres leer más sobre estas codificaciones y campos de formulario, dirígete a la MDN web docs para POST. +Si quieres leer más sobre estas codificaciones y campos de formulario, dirígete a la MDN web docs para POST. /// @@ -68,6 +68,6 @@ Esto no es una limitación de **FastAPI**, es parte del protocolo HTTP. /// -## Recapitulación +## Recapitulación { #recap } Usa `Form` para declarar parámetros de entrada de datos de formulario. diff --git a/docs/es/docs/tutorial/response-model.md b/docs/es/docs/tutorial/response-model.md index 09682f51bc..eeafe249e1 100644 --- a/docs/es/docs/tutorial/response-model.md +++ b/docs/es/docs/tutorial/response-model.md @@ -1,8 +1,8 @@ -# Modelo de Response - Tipo de Retorno +# Modelo de Response - Tipo de Retorno { #response-model-return-type } Puedes declarar el tipo utilizado para el response anotando el **tipo de retorno** de la *path operation function*. -Puedes utilizar **anotaciones de tipos** de la misma manera que lo harías para datos de entrada en **parámetros** de función, puedes utilizar modelos de Pydantic, listas, diccionarios, valores escalares como enteros, booleanos, etc. +Puedes utilizar **anotaciones de tipos** de la misma manera que lo harías para datos de entrada en **parámetros** de función, puedes utilizar modelos de Pydantic, list, diccionarios, valores escalares como enteros, booleanos, etc. {* ../../docs_src/response_model/tutorial001_01_py310.py hl[16,21] *} @@ -19,7 +19,7 @@ Pero lo más importante: * **Limitará y filtrará** los datos de salida a lo que se define en el tipo de retorno. * Esto es particularmente importante para la **seguridad**, veremos más sobre eso a continuación. -## Parámetro `response_model` +## Parámetro `response_model` { #response-model-parameter } Hay algunos casos en los que necesitas o quieres devolver algunos datos que no son exactamente lo que declara el tipo. @@ -57,7 +57,7 @@ De esa manera le dices al editor que intencionalmente estás devolviendo cualqui /// -### Prioridad del `response_model` +### Prioridad del `response_model` { #response-model-priority } Si declaras tanto un tipo de retorno como un `response_model`, el `response_model` tomará prioridad y será utilizado por FastAPI. @@ -65,7 +65,7 @@ De esta manera puedes añadir anotaciones de tipos correctas a tus funciones inc También puedes usar `response_model=None` para desactivar la creación de un modelo de response para esa *path operation*, podrías necesitar hacerlo si estás añadiendo anotaciones de tipos para cosas que no son campos válidos de Pydantic, verás un ejemplo de eso en una de las secciones a continuación. -## Devolver los mismos datos de entrada +## Devolver los mismos datos de entrada { #return-the-same-input-data } Aquí estamos declarando un modelo `UserIn`, contendrá una contraseña en texto plano: @@ -105,7 +105,7 @@ Nunca almacenes la contraseña en texto plano de un usuario ni la envíes en un /// -## Añadir un modelo de salida +## Añadir un modelo de salida { #add-an-output-model } Podemos en cambio crear un modelo de entrada con la contraseña en texto plano y un modelo de salida sin ella: @@ -121,7 +121,7 @@ Aquí, aunque nuestra *path operation function* está devolviendo el mismo usuar Entonces, **FastAPI** se encargará de filtrar todos los datos que no estén declarados en el modelo de salida (usando Pydantic). -### `response_model` o Tipo de Retorno +### `response_model` o Tipo de Retorno { #response-model-or-return-type } En este caso, como los dos modelos son diferentes, si anotáramos el tipo de retorno de la función como `UserOut`, el editor y las herramientas se quejarían de que estamos devolviendo un tipo inválido, ya que son clases diferentes. @@ -129,7 +129,7 @@ Por eso en este ejemplo tenemos que declararlo en el parámetro `response_model` ...pero sigue leyendo abajo para ver cómo superar eso. -## Tipo de Retorno y Filtrado de Datos +## Tipo de Retorno y Filtrado de Datos { #return-type-and-data-filtering } Continuemos con el ejemplo anterior. Queríamos **anotar la función con un tipo**, pero queríamos poder devolver desde la función algo que en realidad incluya **más datos**. @@ -147,17 +147,17 @@ Con esto, obtenemos soporte de las herramientas, de los editores y mypy ya que e ¿Cómo funciona esto? Vamos a echarle un vistazo. 🤓 -### Anotaciones de Tipos y Herramientas +### Anotaciones de Tipos y Herramientas { #type-annotations-and-tooling } Primero vamos a ver cómo los editores, mypy y otras herramientas verían esto. `BaseUser` tiene los campos base. Luego `UserIn` hereda de `BaseUser` y añade el campo `password`, por lo que incluirá todos los campos de ambos modelos. -Anotamos el tipo de retorno de la función como `BaseUser`, pero en realidad estamos devolviendo una instancia de `UserIn`. +Anotamos el tipo de retorno de la función como `BaseUser`, pero en realidad estamos devolviendo un instance de `UserIn`. El editor, mypy y otras herramientas no se quejarán de esto porque, en términos de tipificación, `UserIn` es una subclase de `BaseUser`, lo que significa que es un tipo *válido* cuando se espera algo que es un `BaseUser`. -### Filtrado de Datos en FastAPI +### Filtrado de Datos en FastAPI { #fastapi-data-filtering } Ahora, para FastAPI, verá el tipo de retorno y se asegurará de que lo que devuelves incluya **solo** los campos que están declarados en el tipo. @@ -165,7 +165,7 @@ FastAPI realiza varias cosas internamente con Pydantic para asegurarse de que es De esta manera, puedes obtener lo mejor de ambos mundos: anotaciones de tipos con **soporte de herramientas** y **filtrado de datos**. -## Verlo en la documentación +## Verlo en la documentación { #see-it-in-the-docs } Cuando veas la documentación automática, puedes verificar que el modelo de entrada y el modelo de salida tendrán cada uno su propio JSON Schema: @@ -175,11 +175,11 @@ Y ambos modelos se utilizarán para la documentación interactiva de la API: -## Otras Anotaciones de Tipos de Retorno +## Otras Anotaciones de Tipos de Retorno { #other-return-type-annotations } Podría haber casos en los que devuelvas algo que no es un campo válido de Pydantic y lo anotes en la función, solo para obtener el soporte proporcionado por las herramientas (el editor, mypy, etc). -### Devolver un Response Directamente +### Devolver un Response Directamente { #return-a-response-directly } El caso más común sería [devolver un Response directamente como se explica más adelante en la documentación avanzada](../advanced/response-directly.md){.internal-link target=_blank}. @@ -189,7 +189,7 @@ Este caso simple es manejado automáticamente por FastAPI porque la anotación d Y las herramientas también estarán felices porque tanto `RedirectResponse` como `JSONResponse` son subclases de `Response`, por lo que la anotación del tipo es correcta. -### Anotar una Subclase de Response +### Anotar una Subclase de Response { #annotate-a-response-subclass } También puedes usar una subclase de `Response` en la anotación del tipo: @@ -197,7 +197,7 @@ También puedes usar una subclase de `Response` en la anotación del tipo: Esto también funcionará porque `RedirectResponse` es una subclase de `Response`, y FastAPI manejará automáticamente este caso simple. -### Anotaciones de Tipos de Retorno Inválidas +### Anotaciones de Tipos de Retorno Inválidas { #invalid-return-type-annotations } Pero cuando devuelves algún otro objeto arbitrario que no es un tipo válido de Pydantic (por ejemplo, un objeto de base de datos) y lo anotas así en la función, FastAPI intentará crear un modelo de response de Pydantic a partir de esa anotación de tipo, y fallará. @@ -207,7 +207,7 @@ Lo mismo sucedería si tuvieras algo como un =1.10.0,<2.0.0" - - name: Install Pydantic v2 - if: matrix.pydantic-version == 'pydantic-v2' - run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0" + - name: Install Pydantic + run: uv pip install "${{ matrix.pydantic-version }}" # TODO: Remove this once Python 3.8 is no longer supported - name: Install older AnyIO in Python 3.8 if: matrix.python-version == '3.8' @@ -124,7 +120,7 @@ jobs: if: matrix.coverage == 'coverage' uses: actions/upload-artifact@v5 with: - name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.pydantic-version }} + name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }} path: coverage include-hidden-files: true From c98485514c6691ba24d8ce5fd15e26de195a6f71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 17:00:34 +0000 Subject: [PATCH 077/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 64023930f7..e117833f83 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* 👷 Make Pydantic versions customizable in CI. PR [#14535](https://github.com/fastapi/fastapi/pull/14535) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix checkout GitHub Action fetch-depth for LLM translations, enable cron monthly. PR [#14531](https://github.com/fastapi/fastapi/pull/14531) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix Typer command for CI LLM translations. PR [#14530](https://github.com/fastapi/fastapi/pull/14530) by [@tiangolo](https://github.com/tiangolo). * 👷 Update LLM translation CI, add language matrix and extra commands, prepare for scheduled run. PR [#14529](https://github.com/fastapi/fastapi/pull/14529) by [@tiangolo](https://github.com/tiangolo). From c0e4b9cd679d70adda60940527ffd13ffac28fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 09:36:42 -0800 Subject: [PATCH 078/140] =?UTF-8?q?=F0=9F=91=B7=20Run=20Smokeshow=20always?= =?UTF-8?q?,=20even=20on=20test=20failures=20(#14538)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/smokeshow.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index 84c7430194..2ae7ef233d 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -13,7 +13,6 @@ env: jobs: smokeshow: - if: ${{ github.event.workflow_run.conclusion == 'success' }} runs-on: ubuntu-latest steps: @@ -24,12 +23,10 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.9' + python-version: '3.13' - name: Setup uv uses: astral-sh/setup-uv@v7 with: - version: "0.4.15" - enable-cache: true cache-dependency-glob: | requirements**.txt pyproject.toml From ec287a329b172780f8b87641d9a913d57df354ca Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 17:37:09 +0000 Subject: [PATCH 079/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index e117833f83..b9fb156266 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* 👷 Run Smokeshow always, even on test failures. PR [#14538](https://github.com/fastapi/fastapi/pull/14538) by [@tiangolo](https://github.com/tiangolo). * 👷 Make Pydantic versions customizable in CI. PR [#14535](https://github.com/fastapi/fastapi/pull/14535) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix checkout GitHub Action fetch-depth for LLM translations, enable cron monthly. PR [#14531](https://github.com/fastapi/fastapi/pull/14531) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix Typer command for CI LLM translations. PR [#14530](https://github.com/fastapi/fastapi/pull/14530) by [@tiangolo](https://github.com/tiangolo). From c68dc2d4190ebc5b9a820310dbfdde41437443ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 09:52:17 -0800 Subject: [PATCH 080/140] =?UTF-8?q?=F0=9F=91=B7=20Configure=20coverage,=20?= =?UTF-8?q?error=20on=20main=20tests,=20don't=20wait=20for=20Smokeshow=20(?= =?UTF-8?q?#14536)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f68622f27b..623807f8a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -154,7 +154,6 @@ jobs: merge-multiple: true - run: ls -la coverage - run: coverage combine coverage - - run: coverage report - run: coverage html --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML uses: actions/upload-artifact@v5 @@ -162,6 +161,7 @@ jobs: name: coverage-html path: htmlcov include-hidden-files: true + - run: coverage report --fail-under=100 # https://github.com/marketplace/actions/alls-green#why check: # This job does nothing and is only used for the branch protection From 98bee09a6a8c3b834a8a6d5223e4126307ef2048 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 17:52:40 +0000 Subject: [PATCH 081/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index b9fb156266..efdf7af88c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* 👷 Configure coverage, error on main tests, don't wait for Smokeshow. PR [#14536](https://github.com/fastapi/fastapi/pull/14536) by [@tiangolo](https://github.com/tiangolo). * 👷 Run Smokeshow always, even on test failures. PR [#14538](https://github.com/fastapi/fastapi/pull/14538) by [@tiangolo](https://github.com/tiangolo). * 👷 Make Pydantic versions customizable in CI. PR [#14535](https://github.com/fastapi/fastapi/pull/14535) by [@tiangolo](https://github.com/tiangolo). * 👷 Fix checkout GitHub Action fetch-depth for LLM translations, enable cron monthly. PR [#14531](https://github.com/fastapi/fastapi/pull/14531) by [@tiangolo](https://github.com/tiangolo). From c4fb93f9c2a3ce3505314cbcba06166d58e7e20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 12:32:09 -0800 Subject: [PATCH 082/140] =?UTF-8?q?=F0=9F=94=A7=20Update=20test=20workflow?= =?UTF-8?q?=20config,=20remove=20commented=20code=20(#14540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 623807f8a4..c430854265 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,10 +73,6 @@ jobs: python-version: "3.13" pydantic-version: "pydantic>=2.0.2,<3.0.0" coverage: coverage - # - os: ubuntu-latest - # python-version: "3.13" - # pydantic-version: "pydantic>=2.0.2,<2.7.0" - # coverage: coverage - os: ubuntu-latest python-version: "3.14" pydantic-version: "pydantic>=2.0.2,<3.0.0" From e5d0e78bd4ea503f11bee322ae6f98adb7b24807 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 20:32:33 +0000 Subject: [PATCH 083/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index efdf7af88c..9ba32625b3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -15,6 +15,7 @@ hide: ### Internal +* 🔧 Update test workflow config, remove commented code. PR [#14540](https://github.com/fastapi/fastapi/pull/14540) by [@tiangolo](https://github.com/tiangolo). * 👷 Configure coverage, error on main tests, don't wait for Smokeshow. PR [#14536](https://github.com/fastapi/fastapi/pull/14536) by [@tiangolo](https://github.com/tiangolo). * 👷 Run Smokeshow always, even on test failures. PR [#14538](https://github.com/fastapi/fastapi/pull/14538) by [@tiangolo](https://github.com/tiangolo). * 👷 Make Pydantic versions customizable in CI. PR [#14535](https://github.com/fastapi/fastapi/pull/14535) by [@tiangolo](https://github.com/tiangolo). From 9a035035429b9bbfac65486a39baef81075ba396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 12:32:40 -0800 Subject: [PATCH 084/140] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20pt=20(update-outdated)=20(#14537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/pt/docs/_llm-test.md | 4 +- docs/pt/docs/advanced/additional-responses.md | 4 +- .../pt/docs/advanced/advanced-dependencies.md | 2 +- docs/pt/docs/advanced/behind-a-proxy.md | 10 ++- docs/pt/docs/advanced/dataclasses.md | 6 +- docs/pt/docs/advanced/openapi-callbacks.md | 24 +++--- .../path-operation-advanced-configuration.md | 12 +-- docs/pt/docs/advanced/response-directly.md | 2 +- docs/pt/docs/advanced/settings.md | 8 +- docs/pt/docs/deployment/cloud.md | 16 +++- docs/pt/docs/deployment/index.md | 2 + docs/pt/docs/how-to/configure-swagger-ui.md | 2 +- .../docs/how-to/custom-request-and-route.md | 12 +-- docs/pt/docs/index.md | 62 +++++++++++++- docs/pt/docs/resources/index.md | 2 +- docs/pt/docs/tutorial/bigger-applications.md | 80 ++++--------------- docs/pt/docs/tutorial/cookie-param-models.md | 6 +- docs/pt/docs/tutorial/first-steps.md | 59 +++++++++++++- docs/pt/docs/tutorial/handling-errors.md | 50 +++++------- docs/pt/docs/tutorial/sql-databases.md | 2 +- docs/pt/docs/tutorial/testing.md | 70 +++------------- docs/pt/docs/virtual-environments.md | 20 +++++ 22 files changed, 246 insertions(+), 209 deletions(-) diff --git a/docs/pt/docs/_llm-test.md b/docs/pt/docs/_llm-test.md index 6aed4928ce..8d85dd670b 100644 --- a/docs/pt/docs/_llm-test.md +++ b/docs/pt/docs/_llm-test.md @@ -205,7 +205,7 @@ Aqui estão algumas coisas envolvidas em elementos HTML "abbr" (algumas são inv ### O abbr fornece uma explicação { #the-abbr-gives-an-explanation } * cluster -* Aprendizado Profundo +* Deep Learning ### O abbr fornece uma frase completa e uma explicação { #the-abbr-gives-a-full-phrase-and-an-explanation } @@ -273,7 +273,7 @@ Para algumas instruções específicas do idioma, veja, por exemplo, a seção ` * a documentação automática * Ciência de Dados -* Aprendizado Profundo +* Deep Learning * Aprendizado de Máquina * Injeção de Dependências * autenticação HTTP Basic diff --git a/docs/pt/docs/advanced/additional-responses.md b/docs/pt/docs/advanced/additional-responses.md index fef7f5cb1c..a1c1cfab0f 100644 --- a/docs/pt/docs/advanced/additional-responses.md +++ b/docs/pt/docs/advanced/additional-responses.md @@ -175,7 +175,7 @@ Você pode utilizar o mesmo parâmetro `responses` para adicionar diferentes med Por exemplo, você pode adicionar um media type adicional de `image/png`, declarando que a sua *operação de rota* pode retornar um objeto JSON (com o media type `application/json`) ou uma imagem PNG: -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} +{* ../../docs_src/additional_responses/tutorial002_py310.py hl[17:22,26] *} /// note | Nota @@ -237,7 +237,7 @@ Você pode utilizar essa técnica para reutilizar alguns retornos predefinidos n Por exemplo: -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} +{* ../../docs_src/additional_responses/tutorial004_py310.py hl[11:15,24] *} ## Mais informações sobre retornos OpenAPI { #more-information-about-openapi-responses } diff --git a/docs/pt/docs/advanced/advanced-dependencies.md b/docs/pt/docs/advanced/advanced-dependencies.md index 4f93531950..1ad9ea617f 100644 --- a/docs/pt/docs/advanced/advanced-dependencies.md +++ b/docs/pt/docs/advanced/advanced-dependencies.md @@ -144,7 +144,7 @@ Isso foi alterado na versão 0.110.0 para corrigir consumo de memória não trat ### Tarefas em Segundo Plano e Dependências com `yield`, Detalhes Técnicos { #background-tasks-and-dependencies-with-yield-technical-details } -Antes do FastAPI 0.106.0, lançar exceções após o `yield` não era possível, o código de saída em dependências com `yield` era executado depois que a resposta era enviada, então [Tratadores de Exceções](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} já teriam sido executados. +Antes do FastAPI 0.106.0, lançar exceções após o `yield` não era possível, o código de saída em dependências com `yield` era executado depois que a resposta era enviada, então [Tratadores de Exceções](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} já teriam sido executados. Isso foi projetado assim principalmente para permitir o uso dos mesmos objetos "yielded" por dependências dentro de tarefas em segundo plano, porque o código de saída seria executado depois que as tarefas em segundo plano fossem concluídas. diff --git a/docs/pt/docs/advanced/behind-a-proxy.md b/docs/pt/docs/advanced/behind-a-proxy.md index 77c413aeed..97ac5c16f5 100644 --- a/docs/pt/docs/advanced/behind-a-proxy.md +++ b/docs/pt/docs/advanced/behind-a-proxy.md @@ -228,7 +228,7 @@ Passar o `root_path` para `FastAPI` seria o equivalente a passar a opção de li Tenha em mente que o servidor (Uvicorn) não usará esse `root_path` para nada além de passá-lo para a aplicação. -Mas se você acessar com seu navegador http://127.0.0.1:8000/app você verá a resposta normal: +Mas se você acessar com seu navegador http://127.0.0.1:8000/app você verá a resposta normal: ```JSON { @@ -443,6 +443,14 @@ A interface de documentação interagirá com o servidor que você selecionar. /// +/// note | Detalhes Técnicos + +A propriedade `servers` na especificação OpenAPI é opcional. + +Se você não especificar o parâmetro `servers` e `root_path` for igual a `/`, a propriedade `servers` no OpenAPI gerado será totalmente omitida por padrão, o que equivale a um único servidor com valor de `url` igual a `/`. + +/// + ### Desabilitar servidor automático de `root_path` { #disable-automatic-server-from-root-path } Se você não quiser que o **FastAPI** inclua um servidor automático usando o `root_path`, você pode usar o parâmetro `root_path_in_servers=False`: diff --git a/docs/pt/docs/advanced/dataclasses.md b/docs/pt/docs/advanced/dataclasses.md index 88783278d6..6467376967 100644 --- a/docs/pt/docs/advanced/dataclasses.md +++ b/docs/pt/docs/advanced/dataclasses.md @@ -4,7 +4,7 @@ FastAPI é construído em cima do **Pydantic**, e eu tenho mostrado como usar mo Mas o FastAPI também suporta o uso de `dataclasses` da mesma forma: -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} +{* ../../docs_src/dataclasses/tutorial001_py310.py hl[1,6:11,18:19] *} Isso ainda é suportado graças ao **Pydantic**, pois ele tem suporte interno para `dataclasses`. @@ -32,7 +32,7 @@ Mas se você tem um monte de dataclasses por aí, este é um truque legal para u Você também pode usar `dataclasses` no parâmetro `response_model`: -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} +{* ../../docs_src/dataclasses/tutorial002_py310.py hl[1,6:12,18] *} A dataclass será automaticamente convertida para uma dataclass Pydantic. @@ -48,7 +48,7 @@ Em alguns casos, você ainda pode ter que usar a versão do Pydantic das `datacl Nesse caso, você pode simplesmente trocar as `dataclasses` padrão por `pydantic.dataclasses`, que é um substituto direto: -{* ../../docs_src/dataclasses/tutorial003.py hl[1,5,8:11,14:17,23:25,28] *} +{* ../../docs_src/dataclasses/tutorial003_py310.py hl[1,4,7:10,13:16,22:24,27] *} 1. Ainda importamos `field` das `dataclasses` padrão. diff --git a/docs/pt/docs/advanced/openapi-callbacks.md b/docs/pt/docs/advanced/openapi-callbacks.md index 12309df811..57c8c5e81d 100644 --- a/docs/pt/docs/advanced/openapi-callbacks.md +++ b/docs/pt/docs/advanced/openapi-callbacks.md @@ -1,8 +1,8 @@ # Callbacks na OpenAPI { #openapi-callbacks } -Você poderia criar uma API com uma *operação de rota* que poderia acionar uma solicitação a uma *API externa* criada por outra pessoa (provavelmente o mesmo desenvolvedor que estaria *usando* sua API). +Você poderia criar uma API com uma *operação de rota* que poderia acionar um request a uma *API externa* criada por outra pessoa (provavelmente o mesmo desenvolvedor que estaria *usando* sua API). -O processo que acontece quando sua aplicação de API chama a *API externa* é chamado de "callback". Porque o software que o desenvolvedor externo escreveu envia uma solicitação para sua API e então sua API *chama de volta*, enviando uma solicitação para uma *API externa* (que provavelmente foi criada pelo mesmo desenvolvedor). +O processo que acontece quando sua aplicação de API chama a *API externa* é chamado de "callback". Porque o software que o desenvolvedor externo escreveu envia um request para sua API e então sua API *chama de volta*, enviando um request para uma *API externa* (que provavelmente foi criada pelo mesmo desenvolvedor). Nesse caso, você poderia querer documentar como essa API externa *deveria* ser. Que *operação de rota* ela deveria ter, que corpo ela deveria esperar, que resposta ela deveria retornar, etc. @@ -14,14 +14,14 @@ Imagine que você desenvolve um aplicativo que permite criar faturas. Essas faturas terão um `id`, `title` (opcional), `customer` e `total`. -O usuário da sua API (um desenvolvedor externo) criará uma fatura na sua API com uma solicitação POST. +O usuário da sua API (um desenvolvedor externo) criará uma fatura na sua API com um request POST. Então sua API irá (vamos imaginar): * Enviar a fatura para algum cliente do desenvolvedor externo. * Coletar o dinheiro. * Enviar a notificação de volta para o usuário da API (o desenvolvedor externo). - * Isso será feito enviando uma solicitação POST (de *sua API*) para alguma *API externa* fornecida por esse desenvolvedor externo (este é o "callback"). + * Isso será feito enviando um request POST (de *sua API*) para alguma *API externa* fornecida por esse desenvolvedor externo (este é o "callback"). ## O aplicativo **FastAPI** normal { #the-normal-fastapi-app } @@ -31,7 +31,7 @@ Ele terá uma *operação de rota* que receberá um corpo `Invoice`, e um parâm Essa parte é bastante normal, a maior parte do código provavelmente já é familiar para você: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[7:11,34:51] *} /// tip | Dica @@ -54,7 +54,7 @@ callback_url = "https://example.com/api/v1/invoices/events/" httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) ``` -Mas possivelmente a parte mais importante do callback é garantir que o usuário da sua API (o desenvolvedor externo) implemente a *API externa* corretamente, de acordo com os dados que *sua API* vai enviar no corpo da solicitação do callback, etc. +Mas possivelmente a parte mais importante do callback é garantir que o usuário da sua API (o desenvolvedor externo) implemente a *API externa* corretamente, de acordo com os dados que *sua API* vai enviar no corpo do request do callback, etc. Então, o que faremos a seguir é adicionar o código para documentar como essa *API externa* deve ser para receber o callback de *sua API*. @@ -64,7 +64,7 @@ Esse exemplo não implementa o callback em si (que poderia ser apenas uma linha /// tip | Dica -O callback real é apenas uma solicitação HTTP. +O callback real é apenas um request HTTP. Ao implementar o callback por conta própria, você pode usar algo como HTTPX ou Requests. @@ -90,7 +90,7 @@ Adotar temporariamente esse ponto de vista (do *desenvolvedor externo*) pode aju Primeiro crie um novo `APIRouter` que conterá um ou mais callbacks. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[1,23] *} ### Crie a *operação de rota* do callback { #create-the-callback-path-operation } @@ -101,7 +101,7 @@ Ela deve parecer exatamente como uma *operação de rota* normal do FastAPI: * Ela provavelmente deveria ter uma declaração do corpo que deveria receber, por exemplo, `body: InvoiceEvent`. * E também poderia ter uma declaração da resposta que deveria retornar, por exemplo, `response_model=InvoiceEventReceived`. -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[14:16,19:20,26:30] *} Há 2 diferenças principais de uma *operação de rota* normal: @@ -118,7 +118,7 @@ Nesse caso, é a `str`: "{$callback_url}/invoices/{$request.body.id}" ``` -Então, se o usuário da sua API (o desenvolvedor externo) enviar uma solicitação para *sua API* para: +Então, se o usuário da sua API (o desenvolvedor externo) enviar um request para *sua API* para: ``` https://yourapi.com/invoices/?callback_url=https://www.external.org/events @@ -134,7 +134,7 @@ com um corpo JSON de: } ``` -então *sua API* processará a fatura e, em algum momento posterior, enviará uma solicitação de callback para o `callback_url` (a *API externa*): +então *sua API* processará a fatura e, em algum momento posterior, enviará um request de callback para o `callback_url` (a *API externa*): ``` https://www.external.org/events/invoices/2expen51ve @@ -169,7 +169,7 @@ Nesse ponto você tem a(s) *operação(ões) de rota de callback* necessária(s) Agora use o parâmetro `callbacks` no decorador da *operação de rota da sua API* para passar o atributo `.routes` (que é na verdade apenas uma `list` de rotas/*operações de path*) do roteador de callback: -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} +{* ../../docs_src/openapi_callbacks/tutorial001_py310.py hl[33] *} /// tip | Dica diff --git a/docs/pt/docs/advanced/path-operation-advanced-configuration.md b/docs/pt/docs/advanced/path-operation-advanced-configuration.md index cd20158921..12505a9a87 100644 --- a/docs/pt/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/pt/docs/advanced/path-operation-advanced-configuration.md @@ -16,7 +16,7 @@ Você precisa ter certeza que ele é único para cada operação. ### Utilizando o nome da *função de operação de rota* como o operationId { #using-the-path-operation-function-name-as-the-operationid } -Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operationId` em cada *operação de rota* utilizando o `APIRoute.name` dela. +Se você quiser utilizar o nome das funções da sua API como `operationId`s, você pode iterar sobre todos esses nomes e sobrescrever o `operation_id` em cada *operação de rota* utilizando o `APIRoute.name` dela. Você deve fazer isso depois de adicionar todas as suas *operações de rota*. @@ -50,7 +50,7 @@ Adicionar um `\f` (um caractere de escape para alimentação de formulário) faz Ele não será mostrado na documentação, mas outras ferramentas (como o Sphinx) serão capazes de utilizar o resto do texto. -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial004_py310.py hl[17:27] *} ## Respostas Adicionais { #additional-responses } @@ -155,13 +155,13 @@ Por exemplo, nesta aplicação nós não usamos a funcionalidade integrada ao Fa //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[17:22, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} //// @@ -179,13 +179,13 @@ E então no nosso código, nós analisamos o conteúdo YAML diretamente, e estam //// tab | Pydantic v2 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} //// //// tab | Pydantic v1 -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1.py hl[26:33] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} //// diff --git a/docs/pt/docs/advanced/response-directly.md b/docs/pt/docs/advanced/response-directly.md index 3bda46a049..0c0144e9a1 100644 --- a/docs/pt/docs/advanced/response-directly.md +++ b/docs/pt/docs/advanced/response-directly.md @@ -34,7 +34,7 @@ Por exemplo, você não pode colocar um modelo do Pydantic em uma `JSONResponse` Para esses casos, você pode usar o `jsonable_encoder` para converter seus dados antes de repassá-los para a resposta: -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} +{* ../../docs_src/response_directly/tutorial001_py310.py hl[5:6,20:21] *} /// note | Detalhes Técnicos diff --git a/docs/pt/docs/advanced/settings.md b/docs/pt/docs/advanced/settings.md index 81fc9082c4..cbde3a27c0 100644 --- a/docs/pt/docs/advanced/settings.md +++ b/docs/pt/docs/advanced/settings.md @@ -148,7 +148,7 @@ Isso pode ser especialmente útil durante os testes, pois é muito fácil sobres Vindo do exemplo anterior, seu arquivo `config.py` poderia ser assim: -{* ../../docs_src/settings/app02/config.py hl[10] *} +{* ../../docs_src/settings/app02_an_py39/config.py hl[10] *} Perceba que agora não criamos uma instância padrão `settings = Settings()`. @@ -174,7 +174,7 @@ E então podemos exigi-la na *função de operação de rota* como dependência Então seria muito fácil fornecer um objeto de configurações diferente durante os testes criando uma sobrescrita de dependência para `get_settings`: -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} +{* ../../docs_src/settings/app02_an_py39/test_main.py hl[9:10,13,21] *} Na sobrescrita da dependência definimos um novo valor para `admin_email` ao criar o novo objeto `Settings`, e então retornamos esse novo objeto. @@ -217,7 +217,7 @@ E então atualizar seu `config.py` com: //// tab | Pydantic v2 -{* ../../docs_src/settings/app03_an/config.py hl[9] *} +{* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip | Dica @@ -229,7 +229,7 @@ O atributo `model_config` é usado apenas para configuração do Pydantic. Você //// tab | Pydantic v1 -{* ../../docs_src/settings/app03_an/config_pv1.py hl[9:10] *} +{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} /// tip | Dica diff --git a/docs/pt/docs/deployment/cloud.md b/docs/pt/docs/deployment/cloud.md index 3049a6ad00..419fd76265 100644 --- a/docs/pt/docs/deployment/cloud.md +++ b/docs/pt/docs/deployment/cloud.md @@ -4,13 +4,21 @@ Você pode usar praticamente **qualquer provedor de nuvem** para implantar seu a Na maioria dos casos, os principais provedores de nuvem têm tutoriais para implantar o FastAPI com eles. +## FastAPI Cloud { #fastapi-cloud } + +**FastAPI Cloud** é desenvolvido pelo mesmo autor e equipe por trás do **FastAPI**. + +Ele simplifica o processo de **criar**, **implantar** e **acessar** uma API com o mínimo de esforço. + +Traz a mesma **experiência do desenvolvedor** de criar aplicações com FastAPI para **implantá-las** na nuvem. 🎉 + +FastAPI Cloud é o patrocinador principal e provedor de financiamento dos projetos de código aberto *FastAPI and friends*. ✨ + ## Provedores de Nuvem - Patrocinadores { #cloud-providers-sponsors } -Alguns provedores de nuvem ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, o que garante o **desenvolvimento** contínuo e saudável do FastAPI e seu **ecossistema**. +Alguns outros provedores de nuvem ✨ [**patrocinam o FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ também. 🙇 -E isso mostra seu verdadeiro comprometimento com o FastAPI e sua **comunidade** (você), pois eles não querem apenas fornecer a você um **bom serviço**, mas também querem ter certeza de que você tenha um **framework bom e saudável**, o FastAPI. 🙇 - -Talvez você queira experimentar os serviços deles e seguir os tutoriais: +Você também pode considerá-los para seguir seus tutoriais e experimentar seus serviços: * Render * Railway diff --git a/docs/pt/docs/deployment/index.md b/docs/pt/docs/deployment/index.md index 92cd4323a9..6a6c21804d 100644 --- a/docs/pt/docs/deployment/index.md +++ b/docs/pt/docs/deployment/index.md @@ -16,6 +16,8 @@ Há várias maneiras de fazer isso, dependendo do seu caso de uso específico e Você pode **implantar um servidor** por conta própria usando uma combinação de ferramentas, pode usar um **serviço em nuvem** que faça parte do trabalho por você, entre outras opções. +Por exemplo, nós, a equipe por trás do FastAPI, criamos **FastAPI Cloud**, para tornar a implantação de aplicações FastAPI na nuvem o mais simples possível, com a mesma experiência de desenvolvimento de trabalhar com o FastAPI. + Vou mostrar alguns dos principais conceitos que você provavelmente deve ter em mente ao implantar uma aplicação **FastAPI** (embora a maior parte se aplique a qualquer outro tipo de aplicação web). Você verá mais detalhes para ter em mente e algumas das técnicas para fazer isso nas próximas seções. ✨ diff --git a/docs/pt/docs/how-to/configure-swagger-ui.md b/docs/pt/docs/how-to/configure-swagger-ui.md index ecf85a6eef..163316932f 100644 --- a/docs/pt/docs/how-to/configure-swagger-ui.md +++ b/docs/pt/docs/how-to/configure-swagger-ui.md @@ -40,7 +40,7 @@ O FastAPI inclui alguns parâmetros de configuração padrão apropriados para a Inclui estas configurações padrão: -{* ../../fastapi/openapi/docs.py ln[8:23] hl[17:23] *} +{* ../../fastapi/openapi/docs.py ln[9:24] hl[18:24] *} Você pode substituir qualquer um deles definindo um valor diferente no argumento `swagger_ui_parameters`. diff --git a/docs/pt/docs/how-to/custom-request-and-route.md b/docs/pt/docs/how-to/custom-request-and-route.md index c623dd8a06..b4ea1c282b 100644 --- a/docs/pt/docs/how-to/custom-request-and-route.md +++ b/docs/pt/docs/how-to/custom-request-and-route.md @@ -42,7 +42,7 @@ Se não houver `gzip` no cabeçalho, ele não tentará descomprimir o corpo. Dessa forma, a mesma classe de rota pode lidar com requisições comprimidas ou não comprimidas. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[9:16] *} ### Criar uma classe `GzipRoute` personalizada { #create-a-custom-gziproute-class } @@ -54,7 +54,7 @@ Esse método retorna uma função. E essa função é o que irá receber uma req Aqui nós usamos para criar um `GzipRequest` a partir da requisição original. -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} +{* ../../docs_src/custom_request_and_route/tutorial001_an_py310.py hl[19:27] *} /// note | Detalhes Técnicos @@ -92,18 +92,18 @@ Também podemos usar essa mesma abordagem para acessar o corpo da requisição e Tudo que precisamos fazer é manipular a requisição dentro de um bloco `try`/`except`: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[14,16] *} Se uma exceção ocorrer, a instância `Request` ainda estará em escopo, então podemos ler e fazer uso do corpo da requisição ao lidar com o erro: -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} +{* ../../docs_src/custom_request_and_route/tutorial002_an_py310.py hl[17:19] *} ## Classe `APIRoute` personalizada em um router { #custom-apiroute-class-in-a-router } Você também pode definir o parâmetro `route_class` de uma `APIRouter`: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[26] *} Nesse exemplo, as *operações de rota* sob o `router` irão usar a classe `TimedRoute` personalizada, e terão um cabeçalho extra `X-Response-Time` na resposta com o tempo que levou para gerar a resposta: -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} +{* ../../docs_src/custom_request_and_route/tutorial003_py310.py hl[13:20] *} diff --git a/docs/pt/docs/index.md b/docs/pt/docs/index.md index 43bae7a101..0428c3a798 100644 --- a/docs/pt/docs/index.md +++ b/docs/pt/docs/index.md @@ -52,14 +52,20 @@ Os recursos chave são: -{% if sponsors %} +### Patrocinador Keystone { #keystone-sponsor } + +{% for sponsor in sponsors.keystone -%} + +{% endfor -%} + +### Patrocinadores Ouro e Prata { #gold-and-silver-sponsors } + {% for sponsor in sponsors.gold -%} {% endfor -%} {%- for sponsor in sponsors.silver -%} {% endfor %} -{% endif %} @@ -444,6 +450,58 @@ Para um exemplo mais completo incluindo mais recursos, veja FastAPI Cloud, inscreva-se na lista de espera se ainda não o fez. 🚀 + +Se você já tem uma conta na **FastAPI Cloud** (nós convidamos você da lista de espera 😉), pode implantar sua aplicação com um único comando. + +Antes de implantar, certifique-se de que está autenticado: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Depois, implemente sua aplicação: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +É isso! Agora você pode acessar sua aplicação nesse URL. ✨ + +#### Sobre a FastAPI Cloud { #about-fastapi-cloud } + +**FastAPI Cloud** é construída pelo mesmo autor e equipe por trás do **FastAPI**. + +Ela simplifica o processo de **construir**, **implantar** e **acessar** uma API com esforço mínimo. + +Traz a mesma **experiência do desenvolvedor** de construir aplicações com FastAPI para **implantá-las** na nuvem. 🎉 + +A FastAPI Cloud é a principal patrocinadora e financiadora dos projetos open source do ecossistema *FastAPI and friends*. ✨ + +#### Implante em outros provedores de nuvem { #deploy-to-other-cloud-providers } + +FastAPI é open source e baseado em padrões. Você pode implantar aplicações FastAPI em qualquer provedor de nuvem que escolher. + +Siga os tutoriais do seu provedor de nuvem para implantar aplicações FastAPI com eles. 🤓 + ## Performance { #performance } Testes de performance da _Independent TechEmpower_ mostram aplicações **FastAPI** rodando sob Uvicorn como um dos _frameworks_ Python mais rápidos disponíveis, somente atrás de Starlette e Uvicorn (utilizados internamente pelo FastAPI). (*) diff --git a/docs/pt/docs/resources/index.md b/docs/pt/docs/resources/index.md index 0ac95342ba..24cea95641 100644 --- a/docs/pt/docs/resources/index.md +++ b/docs/pt/docs/resources/index.md @@ -1,3 +1,3 @@ # Recursos { #resources } -Material complementar, links externos, artigos e muito mais. ✈️ +Material complementar, links externos e mais. ✈️ diff --git a/docs/pt/docs/tutorial/bigger-applications.md b/docs/pt/docs/tutorial/bigger-applications.md index c479eb5d97..9dec7b1968 100644 --- a/docs/pt/docs/tutorial/bigger-applications.md +++ b/docs/pt/docs/tutorial/bigger-applications.md @@ -85,9 +85,7 @@ Você pode criar as *operações de rotas* para esse módulo usando o `APIRouter você o importa e cria uma "instância" da mesma maneira que faria com a classe `FastAPI`: -```Python hl_lines="1 3" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *} ### *Operações de Rota* com `APIRouter` { #path-operations-with-apirouter } @@ -95,9 +93,7 @@ E então você o utiliza para declarar suas *operações de rota*. Utilize-o da mesma maneira que utilizaria a classe `FastAPI`: -```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *} Você pode pensar em `APIRouter` como uma classe "mini `FastAPI`". @@ -121,35 +117,7 @@ Então, as colocamos em seu próprio módulo de `dependencies` (`app/dependencie Agora usaremos uma dependência simples para ler um cabeçalho `X-Token` personalizado: -//// tab | Python 3.9+ - -```Python hl_lines="3 6-8" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an_py39/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 5-7" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app_an/dependencies.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Prefira usar a versão `Annotated` se possível. - -/// - -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!> ../../docs_src/bigger_applications/app/dependencies.py!} -``` - -//// +{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *} /// tip | Dica @@ -181,9 +149,7 @@ Sabemos que todas as *operações de rota* neste módulo têm o mesmo: Então, em vez de adicionar tudo isso a cada *operação de rota*, podemos adicioná-lo ao `APIRouter`. -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *} Como o caminho de cada *operação de rota* deve começar com `/`, como em: @@ -199,7 +165,7 @@ Então, o prefixo neste caso é `/items`. Também podemos adicionar uma lista de `tags` e `responses` extras que serão aplicadas a todas as *operações de rota* incluídas neste roteador. -E podemos adicionar uma lista de `dependencies` que serão adicionadas a todas as *operações de rota* no roteador e serão executadas/resolvidas para cada solicitação feita a elas. +E podemos adicionar uma lista de `dependencies` que serão adicionadas a todas as *operações de rota* no roteador e serão executadas/resolvidas para cada request feita a elas. /// tip | Dica @@ -242,9 +208,7 @@ E precisamos obter a função de dependência do módulo `app.dependencies`, o a Então usamos uma importação relativa com `..` para as dependências: -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *} #### Como funcionam as importações relativas { #how-relative-imports-work } @@ -315,9 +279,7 @@ Não estamos adicionando o prefixo `/items` nem `tags=["items"]` a cada *operaç Mas ainda podemos adicionar _mais_ `tags` que serão aplicadas a uma *operação de rota* específica, e também algumas `responses` extras específicas para essa *operação de rota*: -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *} /// tip | Dica @@ -343,17 +305,13 @@ Você importa e cria uma classe `FastAPI` normalmente. E podemos até declarar [dependências globais](dependencies/global-dependencies.md){.internal-link target=_blank} que serão combinadas com as dependências para cada `APIRouter`: -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *} ### Importe o `APIRouter` { #import-the-apirouter } Agora importamos os outros submódulos que possuem `APIRouter`s: -```Python hl_lines="4-5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *} Como os arquivos `app/routers/users.py` e `app/routers/items.py` são submódulos que fazem parte do mesmo pacote Python `app`, podemos usar um único ponto `.` para importá-los usando "importações relativas". @@ -416,17 +374,13 @@ o `router` de `users` sobrescreveria o de `items` e não poderíamos usá-los ao Então, para poder usar ambos no mesmo arquivo, importamos os submódulos diretamente: -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *} ### Inclua os `APIRouter`s para `usuários` e `itens` { #include-the-apirouters-for-users-and-items } Agora, vamos incluir os `router`s dos submódulos `users` e `items`: -```Python hl_lines="10-11" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *} /// info | Informação @@ -466,17 +420,13 @@ Ele contém um `APIRouter` com algumas *operações de rota* de administração Para este exemplo, será super simples. Mas digamos que, como ele é compartilhado com outros projetos na organização, não podemos modificá-lo e adicionar um `prefix`, `dependencies`, `tags`, etc. diretamente ao `APIRouter`: -```Python hl_lines="3" title="app/internal/admin.py" -{!../../docs_src/bigger_applications/app/internal/admin.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *} Mas ainda queremos definir um `prefix` personalizado ao incluir o `APIRouter` para que todas as suas *operações de rota* comecem com `/admin`, queremos protegê-lo com as `dependencies` que já temos para este projeto e queremos incluir `tags` e `responses`. Podemos declarar tudo isso sem precisar modificar o `APIRouter` original passando esses parâmetros para `app.include_router()`: -```Python hl_lines="14-17" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *} Dessa forma, o `APIRouter` original permanecerá inalterado, para que possamos compartilhar o mesmo arquivo `app/internal/admin.py` com outros projetos na organização. @@ -497,9 +447,7 @@ Também podemos adicionar *operações de rota* diretamente ao aplicativo `FastA Aqui fazemos isso... só para mostrar que podemos 🤷: -```Python hl_lines="21-23" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` +{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *} e funcionará corretamente, junto com todas as outras *operações de rota* adicionadas com `app.include_router()`. diff --git a/docs/pt/docs/tutorial/cookie-param-models.md b/docs/pt/docs/tutorial/cookie-param-models.md index 470544f999..73c94050fb 100644 --- a/docs/pt/docs/tutorial/cookie-param-models.md +++ b/docs/pt/docs/tutorial/cookie-param-models.md @@ -48,11 +48,9 @@ Em alguns casos especiais (provavelmente não muito comuns), você pode querer * Agora a sua API possui o poder de controlar o seu próprio consentimento de cookie. 🤪🍪 +Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer campo `extra`: - Você pode utilizar a configuração do modelo Pydantic para `proibir` qualquer campo `extra`. - - -{* ../../docs_src/cookie_param_models/tutorial002_an_py39.py hl[10] *} +{* ../../docs_src/cookie_param_models/tutorial002_an_py310.py hl[10] *} Se o cliente tentar enviar alguns **cookies extras**, eles receberão um retorno de **erro**. diff --git a/docs/pt/docs/tutorial/first-steps.md b/docs/pt/docs/tutorial/first-steps.md index 32d286fb24..40813c21e2 100644 --- a/docs/pt/docs/tutorial/first-steps.md +++ b/docs/pt/docs/tutorial/first-steps.md @@ -143,6 +143,42 @@ E existem dezenas de alternativas, todas baseadas em OpenAPI. Você pode facilme Você também pode usá-lo para gerar código automaticamente para clientes que se comunicam com sua API. Por exemplo, aplicativos front-end, móveis ou IoT. +### Faça o deploy da sua aplicação (opcional) { #deploy-your-app-optional } + +Você pode, opcionalmente, fazer o deploy da sua aplicação FastAPI na FastAPI Cloud; acesse e entre na lista de espera, se ainda não entrou. 🚀 + +Se você já tem uma conta na **FastAPI Cloud** (nós convidamos você da lista de espera 😉), pode fazer o deploy da sua aplicação com um único comando. + +Antes do deploy, certifique-se de que está autenticado: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +Em seguida, faça o deploy da sua aplicação: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +É isso! Agora você pode acessar sua aplicação nessa URL. ✨ + ## Recapitulando, passo a passo { #recap-step-by-step } ### Passo 1: importe `FastAPI` { #step-1-import-fastapi } @@ -310,10 +346,30 @@ Se você não sabe a diferença, verifique o [Async: *"Com pressa?"*](../async.m Você pode retornar um `dict`, `list` e valores singulares como `str`, `int`, etc. -Você também pode devolver modelos Pydantic (você verá mais sobre isso mais tarde). +Você também pode devolver modelos Pydantic ( você verá mais sobre isso mais tarde). Existem muitos outros objetos e modelos que serão convertidos automaticamente para JSON (incluindo ORMs, etc). Tente usar seus favoritos, é altamente provável que já sejam compatíveis. +### Passo 6: Faça o deploy { #step-6-deploy-it } + +Faça o deploy da sua aplicação para a **FastAPI Cloud** com um comando: `fastapi deploy`. 🎉 + +#### Sobre o FastAPI Cloud { #about-fastapi-cloud } + +A **FastAPI Cloud** é construída pelo mesmo autor e equipe por trás do **FastAPI**. + +Ela simplifica o processo de **construir**, **fazer deploy** e **acessar** uma API com o mínimo de esforço. + +Traz a mesma **experiência do desenvolvedor** de criar aplicações com FastAPI para **fazer o deploy** delas na nuvem. 🎉 + +A FastAPI Cloud é a principal patrocinadora e financiadora dos projetos open source do ecossistema *FastAPI and friends*. ✨ + +#### Faça o deploy em outros provedores de nuvem { #deploy-to-other-cloud-providers } + +FastAPI é open source e baseado em padrões. Você pode fazer deploy de aplicações FastAPI em qualquer provedor de nuvem que preferir. + +Siga os tutoriais do seu provedor de nuvem para fazer deploy de aplicações FastAPI com eles. 🤓 + ## Recapitulando { #recap } * Importe `FastAPI`. @@ -321,3 +377,4 @@ Existem muitos outros objetos e modelos que serão convertidos automaticamente p * Escreva um **decorador de operação de rota** usando decoradores como `@app.get("/")`. * Defina uma **função de operação de rota**; por exemplo, `def root(): ...`. * Execute o servidor de desenvolvimento usando o comando `fastapi dev`. +* Opcionalmente, faça o deploy da sua aplicação com `fastapi deploy`. diff --git a/docs/pt/docs/tutorial/handling-errors.md b/docs/pt/docs/tutorial/handling-errors.md index a2cfcf9638..7b895ccdf3 100644 --- a/docs/pt/docs/tutorial/handling-errors.md +++ b/docs/pt/docs/tutorial/handling-errors.md @@ -11,7 +11,6 @@ Pode ser que você precise comunicar ao cliente que: * O item que o cliente está tentando acessar não existe. * etc. - Nesses casos, você normalmente retornaria um **HTTP status code** próximo ao status code na faixa do status code **400** (do 400 ao 499). Isso é bastante similar ao caso do HTTP status code 200 (do 200 ao 299). Esses "200" status codes significam que, de algum modo, houve sucesso na requisição. @@ -28,7 +27,7 @@ Para retornar ao cliente *responses* HTTP com erros, use o `HTTPException`. {* ../../docs_src/handling_errors/tutorial001.py hl[1] *} -### Lance o `HTTPException` no seu código. { #raise-an-httpexception-in-your-code } +### Lance o `HTTPException` no seu código { #raise-an-httpexception-in-your-code } `HTTPException`, ao fundo, nada mais é do que a conjunção entre uma exceção comum do Python e informações adicionais relevantes para APIs. @@ -82,7 +81,7 @@ Mas caso você precise, para um cenário mais complexo, você pode adicionar hea ## Instale manipuladores de exceções customizados { #install-custom-exception-handlers } -Você pode adicionar manipuladores de exceção customizados com a mesma seção de utilidade de exceções presentes no Starlette +Você pode adicionar manipuladores de exceção customizados com a mesma seção de utilidade de exceções presentes no Starlette. Digamos que você tenha uma exceção customizada `UnicornException` que você (ou uma biblioteca que você use) precise lançar (`raise`). @@ -102,7 +101,7 @@ Dessa forma você receberá um erro "limpo", com o HTTP status code `418` e um J /// note | Detalhes Técnicos -Você também pode usar `from starlette.requests import Request` and `from starlette.responses import JSONResponse`. +Você também pode usar `from starlette.requests import Request` e `from starlette.responses import JSONResponse`. **FastAPI** disponibiliza o mesmo `starlette.responses` através do `fastapi.responses` por conveniência ao desenvolvedor. Contudo, a maior parte das respostas disponíveis vem diretamente do Starlette. O mesmo acontece com o `Request`. @@ -112,7 +111,7 @@ Você também pode usar `from starlette.requests import Request` and `from starl **FastAPI** tem alguns manipuladores padrão de exceções. -Esses manipuladores são os responsáveis por retornar o JSON padrão de respostas quando você lança (`raise`) o `HTTPException` e quando a requisição tem dados invalidos. +Esses manipuladores são os responsáveis por retornar o JSON padrão de respostas quando você lança (`raise`) o `HTTPException` e quando a requisição tem dados inválidos. Você pode sobrescrever esses manipuladores de exceção com os seus próprios manipuladores. @@ -126,7 +125,7 @@ Para sobrescrevê-lo, importe o `RequestValidationError` e use-o com o `@app.exc O manipulador de exceções receberá um `Request` e a exceção. -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *} Se você for ao `/items/foo`, em vez de receber o JSON padrão com o erro: @@ -148,36 +147,17 @@ Se você for ao `/items/foo`, em vez de receber o JSON padrão com o erro: você receberá a versão em texto: ``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) +Erros de validação: +Campo: ('path', 'item_id'), Erro: A entrada deve ser um inteiro válido; não foi possível interpretar a string como um inteiro ``` -#### `RequestValidationError` vs `ValidationError` { #requestvalidationerror-vs-validationerror } - -/// warning | Atenção - -Você pode pular estes detalhes técnicos caso eles não sejam importantes para você neste momento. - -/// - -`RequestValidationError` é uma subclasse do `ValidationError` existente no Pydantic. - -**FastAPI** faz uso dele para que você veja o erro no seu log, caso você utilize um modelo de Pydantic em `response_model`, e seus dados tenham erro. - -Contudo, o cliente ou usuário não terão acesso a ele. Ao contrário, o cliente receberá um "Internal Server Error" com o HTTP status code `500`. - -E assim deve ser porque seria um bug no seu código ter o `ValidationError` do Pydantic na sua *response*, ou em qualquer outro lugar do seu código (que não na requisição do cliente). - -E enquanto você conserta o bug, os clientes / usuários não deveriam ter acesso às informações internas do erro, porque, desse modo, haveria exposição de uma vulnerabilidade de segurança. - ### Sobrescreva o manipulador de erro `HTTPException` { #override-the-httpexception-error-handler } -Do mesmo modo, você pode sobreescrever o `HTTPException`. +Do mesmo modo, você pode sobrescrever o `HTTPException`. Por exemplo, você pode querer retornar uma *response* em *plain text* ao invés de um JSON para os seguintes erros: -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} +{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *} /// note | Detalhes Técnicos @@ -187,11 +167,19 @@ Você pode usar `from starlette.responses import PlainTextResponse`. /// -### Use o body do `RequestValidationError`. { #use-the-requestvalidationerror-body } +/// warning | Atenção + +Tenha em mente que o `RequestValidationError` contém as informações do nome do arquivo e da linha onde o erro de validação acontece, para que você possa mostrá-las nos seus logs com as informações relevantes, se quiser. + +Mas isso significa que, se você simplesmente convertê-lo para uma string e retornar essa informação diretamente, você pode acabar vazando um pouco de informação sobre o seu sistema; por isso, aqui o código extrai e mostra cada erro de forma independente. + +/// + +### Use o body do `RequestValidationError` { #use-the-requestvalidationerror-body } O `RequestValidationError` contém o `body` que ele recebeu de dados inválidos. -Você pode utilizá-lo enquanto desenvolve seu app para conectar o *body* e debugá-lo, e assim retorná-lo ao usuário, etc. +Você pode utilizá-lo enquanto desenvolve seu app para registrar o *body* e debugá-lo, e assim retorná-lo ao usuário, etc. {* ../../docs_src/handling_errors/tutorial005.py hl[14] *} diff --git a/docs/pt/docs/tutorial/sql-databases.md b/docs/pt/docs/tutorial/sql-databases.md index b1c7a3fa75..543e164e9f 100644 --- a/docs/pt/docs/tutorial/sql-databases.md +++ b/docs/pt/docs/tutorial/sql-databases.md @@ -65,7 +65,7 @@ Existem algumas diferenças: * `Field(primary_key=True)` informa ao SQLModel que o `id` é a **chave primária** no banco de dados SQL (você pode aprender mais sobre chaves primárias SQL na documentação do SQLModel). - Ao ter o tipo como `int | None`, o SQLModel saberá que essa coluna deve ser um `INTEGER` no banco de dados SQL e que ela deve ser `NULLABLE`. + **Nota:** Usamos `int | None` para o campo de chave primária para que, no código Python, possamos *criar um objeto sem um `id`* (`id=None`), assumindo que o banco de dados irá *gerá-lo ao salvar*. O SQLModel entende que o banco de dados fornecerá o `id` e *define a coluna como um `INTEGER` não nulo* no esquema do banco de dados. Veja a documentação do SQLModel sobre chaves primárias para detalhes. * `Field(index=True)` informa ao SQLModel que ele deve criar um **índice SQL** para essa coluna, o que permitirá buscas mais rápidas no banco de dados ao ler dados filtrados por essa coluna. diff --git a/docs/pt/docs/tutorial/testing.md b/docs/pt/docs/tutorial/testing.md index a1821e660f..03f1981a3c 100644 --- a/docs/pt/docs/tutorial/testing.md +++ b/docs/pt/docs/tutorial/testing.md @@ -1,6 +1,6 @@ # Testando { #testing } -Graças ao Starlette, testar aplicativos **FastAPI** é fácil e agradável. +Graças ao Starlette, testar aplicações **FastAPI** é fácil e agradável. Ele é baseado no HTTPX, que por sua vez é projetado com base em Requests, por isso é muito familiar e intuitivo. @@ -22,7 +22,7 @@ $ pip install httpx Importe `TestClient`. -Crie um `TestClient` passando seu aplicativo **FastAPI** para ele. +Crie um `TestClient` passando sua aplicação **FastAPI** para ele. Crie funções com um nome que comece com `test_` (essa é a convenção padrão do `pytest`). @@ -52,7 +52,7 @@ Você também pode usar `from starlette.testclient import TestClient`. /// tip | Dica -Se você quiser chamar funções `async` em seus testes além de enviar solicitações ao seu aplicativo FastAPI (por exemplo, funções de banco de dados assíncronas), dê uma olhada em [Testes assíncronos](../advanced/async-tests.md){.internal-link target=_blank} no tutorial avançado. +Se você quiser chamar funções `async` em seus testes além de enviar solicitações à sua aplicação FastAPI (por exemplo, funções de banco de dados assíncronas), dê uma olhada em [Testes assíncronos](../advanced/async-tests.md){.internal-link target=_blank} no tutorial avançado. /// @@ -60,9 +60,9 @@ Se você quiser chamar funções `async` em seus testes além de enviar solicita Em uma aplicação real, você provavelmente teria seus testes em um arquivo diferente. -E seu aplicativo **FastAPI** também pode ser composto de vários arquivos/módulos, etc. +E sua aplicação **FastAPI** também pode ser composta de vários arquivos/módulos, etc. -### Arquivo do aplicativo **FastAPI** { #fastapi-app-file } +### Arquivo da aplicação **FastAPI** { #fastapi-app-file } Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicações maiores](bigger-applications.md){.internal-link target=_blank}: @@ -73,7 +73,7 @@ Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicaç │   └── main.py ``` -No arquivo `main.py` você tem seu aplicativo **FastAPI**: +No arquivo `main.py` você tem sua aplicação **FastAPI**: {* ../../docs_src/app_testing/main.py *} @@ -100,7 +100,7 @@ Como esse arquivo está no mesmo pacote, você pode usar importações relativas Agora vamos estender este exemplo e adicionar mais detalhes para ver como testar diferentes partes. -### Arquivo de aplicativo **FastAPI** estendido { #extended-fastapi-app-file } +### Arquivo de aplicação **FastAPI** estendido { #extended-fastapi-app-file } Vamos continuar com a mesma estrutura de arquivo de antes: @@ -112,7 +112,7 @@ Vamos continuar com a mesma estrutura de arquivo de antes: │   └── test_main.py ``` -Digamos que agora o arquivo `main.py` com seu aplicativo **FastAPI** tenha algumas outras **operações de rotas**. +Digamos que agora o arquivo `main.py` com sua aplicação **FastAPI** tenha algumas outras **operações de rotas**. Ele tem uma operação `GET` que pode retornar um erro. @@ -120,63 +120,13 @@ Ele tem uma operação `POST` que pode retornar vários erros. Ambas as *operações de rotas* requerem um cabeçalho `X-Token`. -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py310/main.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/app_testing/app_b_an_py39/main.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/app_testing/app_b_an/main.py!} -``` - -//// - -//// tab | Python 3.10+ non-Annotated - -/// tip | Dica - -Prefira usar a versão `Annotated` se possível. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b_py310/main.py!} -``` - -//// - -//// tab | Python 3.8+ non-Annotated - -/// tip | Dica - -Prefira usar a versão `Annotated` se possível. - -/// - -```Python -{!> ../../docs_src/app_testing/app_b/main.py!} -``` - -//// +{* ../../docs_src/app_testing/app_b_an_py310/main.py *} ### Arquivo de teste estendido { #extended-testing-file } Você pode então atualizar `test_main.py` com os testes estendidos: -{* ../../docs_src/app_testing/app_b/test_main.py *} +{* ../../docs_src/app_testing/app_b_an_py310/test_main.py *} Sempre que você precisar que o cliente passe informações na requisição e não souber como, você pode pesquisar (no Google) como fazer isso no `httpx`, ou até mesmo como fazer isso com `requests`, já que o design do HTTPX é baseado no design do Requests. diff --git a/docs/pt/docs/virtual-environments.md b/docs/pt/docs/virtual-environments.md index 244f532b5c..5736f71095 100644 --- a/docs/pt/docs/virtual-environments.md +++ b/docs/pt/docs/virtual-environments.md @@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip +/// tip | Dica + +Às vezes, você pode receber um erro **`No module named pip`** ao tentar atualizar o pip. + +Se isso acontecer, instale e atualize o pip usando o comando abaixo: + +
+ +```console +$ python -m ensurepip --upgrade + +---> 100% +``` + +
+ +Esse comando instalará o pip caso ele ainda não esteja instalado e também garante que a versão instalada do pip seja pelo menos tão recente quanto a disponível em `ensurepip`. + +/// + ## Adicionar `.gitignore` { #add-gitignore } Se você estiver usando **Git** (você deveria), adicione um arquivo `.gitignore` para excluir tudo em seu `.venv` do Git. From 01bb87275b603957ac24a03b94d4716629ad7d20 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 20:33:42 +0000 Subject: [PATCH 085/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 9ba32625b3..cbf5e9c93a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Update translations for pt (update-outdated). PR [#14537](https://github.com/fastapi/fastapi/pull/14537) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (update-outdated). PR [#14532](https://github.com/fastapi/fastapi/pull/14532) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (add-missing). PR [#14533](https://github.com/fastapi/fastapi/pull/14533) by [@tiangolo](https://github.com/tiangolo). * 🌐 Remove translations for removed docs. PR [#14516](https://github.com/fastapi/fastapi/pull/14516) by [@tiangolo](https://github.com/tiangolo). From 4a6a87674ae049935d73f800768891bba8ed50c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 12:44:10 -0800 Subject: [PATCH 086/140] =?UTF-8?q?=F0=9F=94=A5=20Remove=20translation=20t?= =?UTF-8?q?o=20emoji=20to=20simplify=20the=20new=20setup=20with=20LLM=20au?= =?UTF-8?q?totranslations=20(#14541)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/em/docs/advanced/additional-responses.md | 247 ------ .../docs/advanced/additional-status-codes.md | 41 - .../em/docs/advanced/advanced-dependencies.md | 65 -- docs/em/docs/advanced/async-tests.md | 93 --- docs/em/docs/advanced/behind-a-proxy.md | 359 --------- docs/em/docs/advanced/custom-response.md | 303 -------- docs/em/docs/advanced/dataclasses.md | 97 --- docs/em/docs/advanced/events.md | 163 ---- docs/em/docs/advanced/generate-clients.md | 238 ------ docs/em/docs/advanced/index.md | 27 - docs/em/docs/advanced/middleware.md | 95 --- docs/em/docs/advanced/openapi-callbacks.md | 186 ----- .../path-operation-advanced-configuration.md | 172 ----- .../advanced/response-change-status-code.md | 31 - docs/em/docs/advanced/response-cookies.md | 51 -- docs/em/docs/advanced/response-directly.md | 65 -- docs/em/docs/advanced/response-headers.md | 41 - .../docs/advanced/security/http-basic-auth.md | 107 --- docs/em/docs/advanced/security/index.md | 19 - .../docs/advanced/security/oauth2-scopes.md | 274 ------- docs/em/docs/advanced/settings.md | 396 ---------- docs/em/docs/advanced/sub-applications.md | 67 -- docs/em/docs/advanced/templates.md | 84 -- docs/em/docs/advanced/testing-dependencies.md | 53 -- docs/em/docs/advanced/testing-events.md | 5 - docs/em/docs/advanced/testing-websockets.md | 13 - .../docs/advanced/using-request-directly.md | 56 -- docs/em/docs/advanced/websockets.md | 186 ----- docs/em/docs/advanced/wsgi.md | 35 - docs/em/docs/alternatives.md | 485 ------------ docs/em/docs/async.md | 442 ----------- docs/em/docs/benchmarks.md | 34 - docs/em/docs/deployment/concepts.md | 323 -------- docs/em/docs/deployment/docker.md | 731 ------------------ docs/em/docs/deployment/https.md | 199 ----- docs/em/docs/deployment/index.md | 21 - docs/em/docs/deployment/manually.md | 159 ---- docs/em/docs/deployment/server-workers.md | 181 ----- docs/em/docs/deployment/versions.md | 93 --- docs/em/docs/features.md | 201 ----- docs/em/docs/help-fastapi.md | 269 ------- docs/em/docs/history-design-future.md | 79 -- docs/em/docs/how-to/conditional-openapi.md | 56 -- .../docs/how-to/custom-request-and-route.md | 109 --- docs/em/docs/how-to/extending-openapi.md | 83 -- docs/em/docs/how-to/graphql.md | 60 -- docs/em/docs/index.md | 474 ------------ docs/em/docs/project-generation.md | 84 -- docs/em/docs/python-types.md | 542 ------------- docs/em/docs/tutorial/background-tasks.md | 84 -- docs/em/docs/tutorial/bigger-applications.md | 530 ------------- docs/em/docs/tutorial/body-fields.md | 60 -- docs/em/docs/tutorial/body-multiple-params.md | 171 ---- docs/em/docs/tutorial/body-nested-models.md | 247 ------ docs/em/docs/tutorial/body-updates.md | 100 --- docs/em/docs/tutorial/body.md | 162 ---- docs/em/docs/tutorial/cookie-params.md | 35 - docs/em/docs/tutorial/cors.md | 85 -- docs/em/docs/tutorial/debugging.md | 113 --- .../dependencies/classes-as-dependencies.md | 180 ----- ...pendencies-in-path-operation-decorators.md | 69 -- .../dependencies/dependencies-with-yield.md | 232 ------ .../dependencies/global-dependencies.md | 15 - docs/em/docs/tutorial/dependencies/index.md | 212 ----- .../tutorial/dependencies/sub-dependencies.md | 86 --- docs/em/docs/tutorial/encoder.md | 35 - docs/em/docs/tutorial/extra-data-types.md | 62 -- docs/em/docs/tutorial/extra-models.md | 211 ----- docs/em/docs/tutorial/first-steps.md | 335 -------- docs/em/docs/tutorial/handling-errors.md | 257 ------ docs/em/docs/tutorial/header-params.md | 91 --- docs/em/docs/tutorial/index.md | 83 -- docs/em/docs/tutorial/metadata.md | 111 --- docs/em/docs/tutorial/middleware.md | 66 -- .../tutorial/path-operation-configuration.md | 107 --- .../path-params-numeric-validations.md | 117 --- docs/em/docs/tutorial/path-params.md | 256 ------ .../tutorial/query-params-str-validations.md | 320 -------- docs/em/docs/tutorial/query-params.md | 187 ----- docs/em/docs/tutorial/request-files.md | 172 ----- .../docs/tutorial/request-forms-and-files.md | 37 - docs/em/docs/tutorial/request-forms.md | 69 -- docs/em/docs/tutorial/response-model.md | 340 -------- docs/em/docs/tutorial/response-status-code.md | 101 --- docs/em/docs/tutorial/schema-extra-example.md | 110 --- docs/em/docs/tutorial/security/first-steps.md | 197 ----- .../tutorial/security/get-current-user.md | 105 --- docs/em/docs/tutorial/security/index.md | 106 --- docs/em/docs/tutorial/security/oauth2-jwt.md | 275 ------- .../docs/tutorial/security/simple-oauth2.md | 289 ------- docs/em/docs/tutorial/static-files.md | 40 - docs/em/docs/tutorial/testing.md | 185 ----- docs/em/mkdocs.yml | 1 - 93 files changed, 14840 deletions(-) delete mode 100644 docs/em/docs/advanced/additional-responses.md delete mode 100644 docs/em/docs/advanced/additional-status-codes.md delete mode 100644 docs/em/docs/advanced/advanced-dependencies.md delete mode 100644 docs/em/docs/advanced/async-tests.md delete mode 100644 docs/em/docs/advanced/behind-a-proxy.md delete mode 100644 docs/em/docs/advanced/custom-response.md delete mode 100644 docs/em/docs/advanced/dataclasses.md delete mode 100644 docs/em/docs/advanced/events.md delete mode 100644 docs/em/docs/advanced/generate-clients.md delete mode 100644 docs/em/docs/advanced/index.md delete mode 100644 docs/em/docs/advanced/middleware.md delete mode 100644 docs/em/docs/advanced/openapi-callbacks.md delete mode 100644 docs/em/docs/advanced/path-operation-advanced-configuration.md delete mode 100644 docs/em/docs/advanced/response-change-status-code.md delete mode 100644 docs/em/docs/advanced/response-cookies.md delete mode 100644 docs/em/docs/advanced/response-directly.md delete mode 100644 docs/em/docs/advanced/response-headers.md delete mode 100644 docs/em/docs/advanced/security/http-basic-auth.md delete mode 100644 docs/em/docs/advanced/security/index.md delete mode 100644 docs/em/docs/advanced/security/oauth2-scopes.md delete mode 100644 docs/em/docs/advanced/settings.md delete mode 100644 docs/em/docs/advanced/sub-applications.md delete mode 100644 docs/em/docs/advanced/templates.md delete mode 100644 docs/em/docs/advanced/testing-dependencies.md delete mode 100644 docs/em/docs/advanced/testing-events.md delete mode 100644 docs/em/docs/advanced/testing-websockets.md delete mode 100644 docs/em/docs/advanced/using-request-directly.md delete mode 100644 docs/em/docs/advanced/websockets.md delete mode 100644 docs/em/docs/advanced/wsgi.md delete mode 100644 docs/em/docs/alternatives.md delete mode 100644 docs/em/docs/async.md delete mode 100644 docs/em/docs/benchmarks.md delete mode 100644 docs/em/docs/deployment/concepts.md delete mode 100644 docs/em/docs/deployment/docker.md delete mode 100644 docs/em/docs/deployment/https.md delete mode 100644 docs/em/docs/deployment/index.md delete mode 100644 docs/em/docs/deployment/manually.md delete mode 100644 docs/em/docs/deployment/server-workers.md delete mode 100644 docs/em/docs/deployment/versions.md delete mode 100644 docs/em/docs/features.md delete mode 100644 docs/em/docs/help-fastapi.md delete mode 100644 docs/em/docs/history-design-future.md delete mode 100644 docs/em/docs/how-to/conditional-openapi.md delete mode 100644 docs/em/docs/how-to/custom-request-and-route.md delete mode 100644 docs/em/docs/how-to/extending-openapi.md delete mode 100644 docs/em/docs/how-to/graphql.md delete mode 100644 docs/em/docs/index.md delete mode 100644 docs/em/docs/project-generation.md delete mode 100644 docs/em/docs/python-types.md delete mode 100644 docs/em/docs/tutorial/background-tasks.md delete mode 100644 docs/em/docs/tutorial/bigger-applications.md delete mode 100644 docs/em/docs/tutorial/body-fields.md delete mode 100644 docs/em/docs/tutorial/body-multiple-params.md delete mode 100644 docs/em/docs/tutorial/body-nested-models.md delete mode 100644 docs/em/docs/tutorial/body-updates.md delete mode 100644 docs/em/docs/tutorial/body.md delete mode 100644 docs/em/docs/tutorial/cookie-params.md delete mode 100644 docs/em/docs/tutorial/cors.md delete mode 100644 docs/em/docs/tutorial/debugging.md delete mode 100644 docs/em/docs/tutorial/dependencies/classes-as-dependencies.md delete mode 100644 docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md delete mode 100644 docs/em/docs/tutorial/dependencies/dependencies-with-yield.md delete mode 100644 docs/em/docs/tutorial/dependencies/global-dependencies.md delete mode 100644 docs/em/docs/tutorial/dependencies/index.md delete mode 100644 docs/em/docs/tutorial/dependencies/sub-dependencies.md delete mode 100644 docs/em/docs/tutorial/encoder.md delete mode 100644 docs/em/docs/tutorial/extra-data-types.md delete mode 100644 docs/em/docs/tutorial/extra-models.md delete mode 100644 docs/em/docs/tutorial/first-steps.md delete mode 100644 docs/em/docs/tutorial/handling-errors.md delete mode 100644 docs/em/docs/tutorial/header-params.md delete mode 100644 docs/em/docs/tutorial/index.md delete mode 100644 docs/em/docs/tutorial/metadata.md delete mode 100644 docs/em/docs/tutorial/middleware.md delete mode 100644 docs/em/docs/tutorial/path-operation-configuration.md delete mode 100644 docs/em/docs/tutorial/path-params-numeric-validations.md delete mode 100644 docs/em/docs/tutorial/path-params.md delete mode 100644 docs/em/docs/tutorial/query-params-str-validations.md delete mode 100644 docs/em/docs/tutorial/query-params.md delete mode 100644 docs/em/docs/tutorial/request-files.md delete mode 100644 docs/em/docs/tutorial/request-forms-and-files.md delete mode 100644 docs/em/docs/tutorial/request-forms.md delete mode 100644 docs/em/docs/tutorial/response-model.md delete mode 100644 docs/em/docs/tutorial/response-status-code.md delete mode 100644 docs/em/docs/tutorial/schema-extra-example.md delete mode 100644 docs/em/docs/tutorial/security/first-steps.md delete mode 100644 docs/em/docs/tutorial/security/get-current-user.md delete mode 100644 docs/em/docs/tutorial/security/index.md delete mode 100644 docs/em/docs/tutorial/security/oauth2-jwt.md delete mode 100644 docs/em/docs/tutorial/security/simple-oauth2.md delete mode 100644 docs/em/docs/tutorial/static-files.md delete mode 100644 docs/em/docs/tutorial/testing.md delete mode 100644 docs/em/mkdocs.yml diff --git a/docs/em/docs/advanced/additional-responses.md b/docs/em/docs/advanced/additional-responses.md deleted file mode 100644 index 655fc7ab6f..0000000000 --- a/docs/em/docs/advanced/additional-responses.md +++ /dev/null @@ -1,247 +0,0 @@ -# 🌖 📨 🗄 - -/// warning - -👉 👍 🏧 ❔. - -🚥 👆 ▶️ ⏮️ **FastAPI**, 👆 💪 🚫 💪 👉. - -/// - -👆 💪 📣 🌖 📨, ⏮️ 🌖 👔 📟, 🔉 🆎, 📛, ♒️. - -👈 🌖 📨 🔜 🔌 🗄 🔗, 👫 🔜 😑 🛠️ 🩺. - -✋️ 👈 🌖 📨 👆 ✔️ ⚒ 💭 👆 📨 `Response` 💖 `JSONResponse` 🔗, ⏮️ 👆 👔 📟 & 🎚. - -## 🌖 📨 ⏮️ `model` - -👆 💪 🚶‍♀️ 👆 *➡ 🛠️ 👨‍🎨* 🔢 `responses`. - -⚫️ 📨 `dict`, 🔑 👔 📟 🔠 📨, 💖 `200`, & 💲 🎏 `dict`Ⓜ ⏮️ ℹ 🔠 👫. - -🔠 👈 📨 `dict`Ⓜ 💪 ✔️ 🔑 `model`, ⚗ Pydantic 🏷, 💖 `response_model`. - -**FastAPI** 🔜 ✊ 👈 🏷, 🏗 🚮 🎻 🔗 & 🔌 ⚫️ ☑ 🥉 🗄. - -🖼, 📣 ➕1️⃣ 📨 ⏮️ 👔 📟 `404` & Pydantic 🏷 `Message`, 👆 💪 ✍: - -{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} - -/// note - -✔️ 🤯 👈 👆 ✔️ 📨 `JSONResponse` 🔗. - -/// - -/// info - -`model` 🔑 🚫 🍕 🗄. - -**FastAPI** 🔜 ✊ Pydantic 🏷 ⚪️➡️ 📤, 🏗 `JSON Schema`, & 🚮 ⚫️ ☑ 🥉. - -☑ 🥉: - -* 🔑 `content`, 👈 ✔️ 💲 ➕1️⃣ 🎻 🎚 (`dict`) 👈 🔌: - * 🔑 ⏮️ 📻 🆎, ✅ `application/json`, 👈 🔌 💲 ➕1️⃣ 🎻 🎚, 👈 🔌: - * 🔑 `schema`, 👈 ✔️ 💲 🎻 🔗 ⚪️➡️ 🏷, 📥 ☑ 🥉. - * **FastAPI** 🚮 🔗 📥 🌐 🎻 🔗 ➕1️⃣ 🥉 👆 🗄 ↩️ ✅ ⚫️ 🔗. 👉 🌌, 🎏 🈸 & 👩‍💻 💪 ⚙️ 👈 🎻 🔗 🔗, 🚚 👻 📟 ⚡ 🧰, ♒️. - -/// - -🏗 📨 🗄 👉 *➡ 🛠️* 🔜: - -```JSON hl_lines="3-12" -{ - "responses": { - "404": { - "description": "Additional Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Message" - } - } - } - }, - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item" - } - } - } - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - } - } - } -} -``` - -🔗 🔗 ➕1️⃣ 🥉 🔘 🗄 🔗: - -```JSON hl_lines="4-16" -{ - "components": { - "schemas": { - "Message": { - "title": "Message", - "required": [ - "message" - ], - "type": "object", - "properties": { - "message": { - "title": "Message", - "type": "string" - } - } - }, - "Item": { - "title": "Item", - "required": [ - "id", - "value" - ], - "type": "object", - "properties": { - "id": { - "title": "Id", - "type": "string" - }, - "value": { - "title": "Value", - "type": "string" - } - } - }, - "ValidationError": { - "title": "ValidationError", - "required": [ - "loc", - "msg", - "type" - ], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "type": "string" - } - }, - "msg": { - "title": "Message", - "type": "string" - }, - "type": { - "title": "Error Type", - "type": "string" - } - } - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": { - "$ref": "#/components/schemas/ValidationError" - } - } - } - } - } - } -} -``` - -## 🌖 🔉 🆎 👑 📨 - -👆 💪 ⚙️ 👉 🎏 `responses` 🔢 🚮 🎏 🔉 🆎 🎏 👑 📨. - -🖼, 👆 💪 🚮 🌖 📻 🆎 `image/png`, 📣 👈 👆 *➡ 🛠️* 💪 📨 🎻 🎚 (⏮️ 📻 🆎 `application/json`) ⚖️ 🇩🇴 🖼: - -{* ../../docs_src/additional_responses/tutorial002.py hl[19:24,28] *} - -/// note - -👀 👈 👆 ✔️ 📨 🖼 ⚙️ `FileResponse` 🔗. - -/// - -/// info - -🚥 👆 ✔ 🎏 📻 🆎 🎯 👆 `responses` 🔢, FastAPI 🔜 🤔 📨 ✔️ 🎏 📻 🆎 👑 📨 🎓 (🔢 `application/json`). - -✋️ 🚥 👆 ✔️ ✔ 🛃 📨 🎓 ⏮️ `None` 🚮 📻 🆎, FastAPI 🔜 ⚙️ `application/json` 🙆 🌖 📨 👈 ✔️ 👨‍💼 🏷. - -/// - -## 🌀 ℹ - -👆 💪 🌀 📨 ℹ ⚪️➡️ 💗 🥉, 🔌 `response_model`, `status_code`, & `responses` 🔢. - -👆 💪 📣 `response_model`, ⚙️ 🔢 👔 📟 `200` (⚖️ 🛃 1️⃣ 🚥 👆 💪), & ⤴️ 📣 🌖 ℹ 👈 🎏 📨 `responses`, 🔗 🗄 🔗. - -**FastAPI** 🔜 🚧 🌖 ℹ ⚪️➡️ `responses`, & 🌀 ⚫️ ⏮️ 🎻 🔗 ⚪️➡️ 👆 🏷. - -🖼, 👆 💪 📣 📨 ⏮️ 👔 📟 `404` 👈 ⚙️ Pydantic 🏷 & ✔️ 🛃 `description`. - -& 📨 ⏮️ 👔 📟 `200` 👈 ⚙️ 👆 `response_model`, ✋️ 🔌 🛃 `example`: - -{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} - -⚫️ 🔜 🌐 🌀 & 🔌 👆 🗄, & 🎦 🛠️ 🩺: - - - -## 🌀 🔢 📨 & 🛃 🕐 - -👆 💪 💚 ✔️ 🔁 📨 👈 ✔ 📚 *➡ 🛠️*, ✋️ 👆 💚 🌀 👫 ⏮️ 🛃 📨 💚 🔠 *➡ 🛠️*. - -📚 💼, 👆 💪 ⚙️ 🐍 ⚒ "🏗" `dict` ⏮️ `**dict_to_unpack`: - -```Python -old_dict = { - "old key": "old value", - "second old key": "second old value", -} -new_dict = {**old_dict, "new key": "new value"} -``` - -📥, `new_dict` 🔜 🔌 🌐 🔑-💲 👫 ⚪️➡️ `old_dict` ➕ 🆕 🔑-💲 👫: - -```Python -{ - "old key": "old value", - "second old key": "second old value", - "new key": "new value", -} -``` - -👆 💪 ⚙️ 👈 ⚒ 🏤-⚙️ 🔢 📨 👆 *➡ 🛠️* & 🌀 👫 ⏮️ 🌖 🛃 🕐. - -🖼: - -{* ../../docs_src/additional_responses/tutorial004.py hl[13:17,26] *} - -## 🌖 ℹ 🔃 🗄 📨 - -👀 ⚫️❔ ⚫️❔ 👆 💪 🔌 📨, 👆 💪 ✅ 👉 📄 🗄 🔧: - -* 🗄 📨 🎚, ⚫️ 🔌 `Response Object`. -* 🗄 📨 🎚, 👆 💪 🔌 🕳 ⚪️➡️ 👉 🔗 🔠 📨 🔘 👆 `responses` 🔢. ✅ `description`, `headers`, `content` (🔘 👉 👈 👆 📣 🎏 🔉 🆎 & 🎻 🔗), & `links`. diff --git a/docs/em/docs/advanced/additional-status-codes.md b/docs/em/docs/advanced/additional-status-codes.md deleted file mode 100644 index 907c7e68ee..0000000000 --- a/docs/em/docs/advanced/additional-status-codes.md +++ /dev/null @@ -1,41 +0,0 @@ -# 🌖 👔 📟 - -🔢, **FastAPI** 🔜 📨 📨 ⚙️ `JSONResponse`, 🚮 🎚 👆 📨 ⚪️➡️ 👆 *➡ 🛠️* 🔘 👈 `JSONResponse`. - -⚫️ 🔜 ⚙️ 🔢 👔 📟 ⚖️ 1️⃣ 👆 ⚒ 👆 *➡ 🛠️*. - -## 🌖 👔 📟 - -🚥 👆 💚 📨 🌖 👔 📟 ↖️ ⚪️➡️ 👑 1️⃣, 👆 💪 👈 🛬 `Response` 🔗, 💖 `JSONResponse`, & ⚒ 🌖 👔 📟 🔗. - -🖼, ➡️ 💬 👈 👆 💚 ✔️ *➡ 🛠️* 👈 ✔ ℹ 🏬, & 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣0️⃣ "👌" 🕐❔ 🏆. - -✋️ 👆 💚 ⚫️ 🚫 🆕 🏬. & 🕐❔ 🏬 🚫 🔀 ⏭, ⚫️ ✍ 👫, & 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣1️⃣ "✍". - -🏆 👈, 🗄 `JSONResponse`, & 📨 👆 🎚 📤 🔗, ⚒ `status_code` 👈 👆 💚: - -{* ../../docs_src/additional_status_codes/tutorial001.py hl[4,25] *} - -/// warning - -🕐❔ 👆 📨 `Response` 🔗, 💖 🖼 🔛, ⚫️ 🔜 📨 🔗. - -⚫️ 🏆 🚫 🎻 ⏮️ 🏷, ♒️. - -⚒ 💭 ⚫️ ✔️ 📊 👆 💚 ⚫️ ✔️, & 👈 💲 ☑ 🎻 (🚥 👆 ⚙️ `JSONResponse`). - -/// - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import JSONResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `status`. - -/// - -## 🗄 & 🛠️ 🩺 - -🚥 👆 📨 🌖 👔 📟 & 📨 🔗, 👫 🏆 🚫 🔌 🗄 🔗 (🛠️ 🩺), ↩️ FastAPI 🚫 ✔️ 🌌 💭 ⏪ ⚫️❔ 👆 🚶 📨. - -✋️ 👆 💪 📄 👈 👆 📟, ⚙️: [🌖 📨](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/advanced-dependencies.md b/docs/em/docs/advanced/advanced-dependencies.md deleted file mode 100644 index 3404c26872..0000000000 --- a/docs/em/docs/advanced/advanced-dependencies.md +++ /dev/null @@ -1,65 +0,0 @@ -# 🏧 🔗 - -## 🔗 🔗 - -🌐 🔗 👥 ✔️ 👀 🔧 🔢 ⚖️ 🎓. - -✋️ 📤 💪 💼 🌐❔ 👆 💚 💪 ⚒ 🔢 🔛 🔗, 🍵 ✔️ 📣 📚 🎏 🔢 ⚖️ 🎓. - -➡️ 🌈 👈 👥 💚 ✔️ 🔗 👈 ✅ 🚥 🔢 🔢 `q` 🔌 🔧 🎚. - -✋️ 👥 💚 💪 🔗 👈 🔧 🎚. - -## "🇧🇲" 👐 - -🐍 📤 🌌 ⚒ 👐 🎓 "🇧🇲". - -🚫 🎓 ⚫️ (❔ ⏪ 🇧🇲), ✋️ 👐 👈 🎓. - -👈, 👥 📣 👩‍🔬 `__call__`: - -{* ../../docs_src/dependencies/tutorial011.py hl[10] *} - -👉 💼, 👉 `__call__` ⚫️❔ **FastAPI** 🔜 ⚙️ ✅ 🌖 🔢 & 🎧-🔗, & 👉 ⚫️❔ 🔜 🤙 🚶‍♀️ 💲 🔢 👆 *➡ 🛠️ 🔢* ⏪. - -## 🔗 👐 - -& 🔜, 👥 💪 ⚙️ `__init__` 📣 🔢 👐 👈 👥 💪 ⚙️ "🔗" 🔗: - -{* ../../docs_src/dependencies/tutorial011.py hl[7] *} - -👉 💼, **FastAPI** 🏆 🚫 ⏱ 👆 ⚖️ 💅 🔃 `__init__`, 👥 🔜 ⚙️ ⚫️ 🔗 👆 📟. - -## ✍ 👐 - -👥 💪 ✍ 👐 👉 🎓 ⏮️: - -{* ../../docs_src/dependencies/tutorial011.py hl[16] *} - -& 👈 🌌 👥 💪 "🔗" 👆 🔗, 👈 🔜 ✔️ `"bar"` 🔘 ⚫️, 🔢 `checker.fixed_content`. - -## ⚙️ 👐 🔗 - -⤴️, 👥 💪 ⚙️ 👉 `checker` `Depends(checker)`, ↩️ `Depends(FixedContentQueryChecker)`, ↩️ 🔗 👐, `checker`, 🚫 🎓 ⚫️. - -& 🕐❔ ❎ 🔗, **FastAPI** 🔜 🤙 👉 `checker` 💖: - -```Python -checker(q="somequery") -``` - -...& 🚶‍♀️ ⚫️❔ 👈 📨 💲 🔗 👆 *➡ 🛠️ 🔢* 🔢 `fixed_content_included`: - -{* ../../docs_src/dependencies/tutorial011.py hl[20] *} - -/// tip - -🌐 👉 💪 😑 🎭. & ⚫️ 💪 🚫 📶 🆑 ❔ ⚫️ ⚠. - -👫 🖼 😫 🙅, ✋️ 🎦 ❔ ⚫️ 🌐 👷. - -📃 🔃 💂‍♂, 📤 🚙 🔢 👈 🛠️ 👉 🎏 🌌. - -🚥 👆 🤔 🌐 👉, 👆 ⏪ 💭 ❔ 👈 🚙 🧰 💂‍♂ 👷 🔘. - -/// diff --git a/docs/em/docs/advanced/async-tests.md b/docs/em/docs/advanced/async-tests.md deleted file mode 100644 index 283d4aa097..0000000000 --- a/docs/em/docs/advanced/async-tests.md +++ /dev/null @@ -1,93 +0,0 @@ -# 🔁 💯 - -👆 ✔️ ⏪ 👀 ❔ 💯 👆 **FastAPI** 🈸 ⚙️ 🚚 `TestClient`. 🆙 🔜, 👆 ✔️ 🕴 👀 ❔ ✍ 🔁 💯, 🍵 ⚙️ `async` 🔢. - -➖ 💪 ⚙️ 🔁 🔢 👆 💯 💪 ⚠, 🖼, 🕐❔ 👆 🔬 👆 💽 🔁. 🌈 👆 💚 💯 📨 📨 👆 FastAPI 🈸 & ⤴️ ✔ 👈 👆 👩‍💻 ⏪ ✍ ☑ 💽 💽, ⏪ ⚙️ 🔁 💽 🗃. - -➡️ 👀 ❔ 👥 💪 ⚒ 👈 👷. - -## pytest.mark.anyio - -🚥 👥 💚 🤙 🔁 🔢 👆 💯, 👆 💯 🔢 ✔️ 🔁. AnyIO 🚚 👌 📁 👉, 👈 ✔ 👥 ✔ 👈 💯 🔢 🤙 🔁. - -## 🇸🇲 - -🚥 👆 **FastAPI** 🈸 ⚙️ 😐 `def` 🔢 ↩️ `async def`, ⚫️ `async` 🈸 🔘. - -`TestClient` 🔨 🎱 🔘 🤙 🔁 FastAPI 🈸 👆 😐 `def` 💯 🔢, ⚙️ 🐩 ✳. ✋️ 👈 🎱 🚫 👷 🚫🔜 🕐❔ 👥 ⚙️ ⚫️ 🔘 🔁 🔢. 🏃 👆 💯 🔁, 👥 💪 🙅‍♂ 📏 ⚙️ `TestClient` 🔘 👆 💯 🔢. - -`TestClient` ⚓️ 🔛 🇸🇲, & ↩️, 👥 💪 ⚙️ ⚫️ 🔗 💯 🛠️. - -## 🖼 - -🙅 🖼, ➡️ 🤔 📁 📊 🎏 1️⃣ 🔬 [🦏 🈸](../tutorial/bigger-applications.md){.internal-link target=_blank} & [🔬](../tutorial/testing.md){.internal-link target=_blank}: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -│   └── test_main.py -``` - -📁 `main.py` 🔜 ✔️: - -{* ../../docs_src/async_tests/main.py *} - -📁 `test_main.py` 🔜 ✔️ 💯 `main.py`, ⚫️ 💪 👀 💖 👉 🔜: - -{* ../../docs_src/async_tests/test_main.py *} - -## 🏃 ⚫️ - -👆 💪 🏃 👆 💯 🐌 📨: - -
- -```console -$ pytest - ----> 100% -``` - -
- -## ℹ - -📑 `@pytest.mark.anyio` 💬 ✳ 👈 👉 💯 🔢 🔜 🤙 🔁: - -{* ../../docs_src/async_tests/test_main.py hl[7] *} - -/// tip - -🗒 👈 💯 🔢 🔜 `async def` ↩️ `def` ⏭ 🕐❔ ⚙️ `TestClient`. - -/// - -⤴️ 👥 💪 ✍ `AsyncClient` ⏮️ 📱, & 📨 🔁 📨 ⚫️, ⚙️ `await`. - -{* ../../docs_src/async_tests/test_main.py hl[9:12] *} - -👉 🌓: - -```Python -response = client.get('/') -``` - -...👈 👥 ⚙️ ⚒ 👆 📨 ⏮️ `TestClient`. - -/// tip - -🗒 👈 👥 ⚙️ 🔁/⌛ ⏮️ 🆕 `AsyncClient` - 📨 🔁. - -/// - -## 🎏 🔁 🔢 🤙 - -🔬 🔢 🔜 🔁, 👆 💪 🔜 🤙 (& `await`) 🎏 `async` 🔢 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 👆 💯, ⚫️❔ 👆 🔜 🤙 👫 🙆 🙆 👆 📟. - -/// tip - -🚥 👆 ⚔ `RuntimeError: Task attached to a different loop` 🕐❔ 🛠️ 🔁 🔢 🤙 👆 💯 (✅ 🕐❔ ⚙️ ✳ MotorClient) 💭 🔗 🎚 👈 💪 🎉 ➰ 🕴 🏞 🔁 🔢, ✅ `'@app.on_event("startup")` ⏲. - -/// diff --git a/docs/em/docs/advanced/behind-a-proxy.md b/docs/em/docs/advanced/behind-a-proxy.md deleted file mode 100644 index 8b14152c9d..0000000000 --- a/docs/em/docs/advanced/behind-a-proxy.md +++ /dev/null @@ -1,359 +0,0 @@ -# ⛅ 🗳 - -⚠, 👆 5️⃣📆 💪 ⚙️ **🗳** 💽 💖 Traefik ⚖️ 👌 ⏮️ 📳 👈 🚮 ➕ ➡ 🔡 👈 🚫 👀 👆 🈸. - -👫 💼 👆 💪 ⚙️ `root_path` 🔗 👆 🈸. - -`root_path` 🛠️ 🚚 🔫 🔧 (👈 FastAPI 🏗 🔛, 🔘 💃). - -`root_path` ⚙️ 🍵 👫 🎯 💼. - -& ⚫️ ⚙️ 🔘 🕐❔ 🗜 🎧-🈸. - -## 🗳 ⏮️ 🎞 ➡ 🔡 - -✔️ 🗳 ⏮️ 🎞 ➡ 🔡, 👉 💼, ⛓ 👈 👆 💪 📣 ➡ `/app` 👆 📟, ✋️ ⤴️, 👆 🚮 🧽 🔛 🔝 (🗳) 👈 🔜 🚮 👆 **FastAPI** 🈸 🔽 ➡ 💖 `/api/v1`. - -👉 💼, ⏮️ ➡ `/app` 🔜 🤙 🍦 `/api/v1/app`. - -✋️ 🌐 👆 📟 ✍ 🤔 📤 `/app`. - -& 🗳 🔜 **"❎"** **➡ 🔡** 🔛 ✈ ⏭ 📶 📨 Uvicorn, 🚧 👆 🈸 🤔 👈 ⚫️ 🍦 `/app`, 👈 👆 🚫 ✔️ ℹ 🌐 👆 📟 🔌 🔡 `/api/v1`. - -🆙 📥, 🌐 🔜 👷 🛎. - -✋️ ⤴️, 🕐❔ 👆 📂 🛠️ 🩺 🎚 (🕸), ⚫️ 🔜 ⌛ 🤚 🗄 🔗 `/openapi.json`, ↩️ `/api/v1/openapi.json`. - -, 🕸 (👈 🏃 🖥) 🔜 🔄 🏆 `/openapi.json` & 🚫🔜 💪 🤚 🗄 🔗. - -↩️ 👥 ✔️ 🗳 ⏮️ ➡ 🔡 `/api/v1` 👆 📱, 🕸 💪 ☕ 🗄 🔗 `/api/v1/openapi.json`. - -```mermaid -graph LR - -browser("Browser") -proxy["Proxy on http://0.0.0.0:9999/api/v1/app"] -server["Server on http://127.0.0.1:8000/app"] - -browser --> proxy -proxy --> server -``` - -/// tip - -📢 `0.0.0.0` 🛎 ⚙️ ⛓ 👈 📋 👂 🔛 🌐 📢 💪 👈 🎰/💽. - -/// - -🩺 🎚 🔜 💪 🗄 🔗 📣 👈 👉 🛠️ `server` 🔎 `/api/v1` (⛅ 🗳). 🖼: - -```JSON hl_lines="4-8" -{ - "openapi": "3.0.2", - // More stuff here - "servers": [ - { - "url": "/api/v1" - } - ], - "paths": { - // More stuff here - } -} -``` - -👉 🖼, "🗳" 💪 🕳 💖 **Traefik**. & 💽 🔜 🕳 💖 **Uvicorn**, 🏃‍♂ 👆 FastAPI 🈸. - -### 🚚 `root_path` - -🏆 👉, 👆 💪 ⚙️ 📋 ⏸ 🎛 `--root-path` 💖: - -
- -```console -$ uvicorn main:app --root-path /api/v1 - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -🚥 👆 ⚙️ Hypercorn, ⚫️ ✔️ 🎛 `--root-path`. - -/// note | 📡 ℹ - -🔫 🔧 🔬 `root_path` 👉 ⚙️ 💼. - - & `--root-path` 📋 ⏸ 🎛 🚚 👈 `root_path`. - -/// - -### ✅ ⏮️ `root_path` - -👆 💪 🤚 ⏮️ `root_path` ⚙️ 👆 🈸 🔠 📨, ⚫️ 🍕 `scope` 📖 (👈 🍕 🔫 🔌). - -📥 👥 ✅ ⚫️ 📧 🎦 🎯. - -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} - -⤴️, 🚥 👆 ▶️ Uvicorn ⏮️: - -
- -```console -$ uvicorn main:app --root-path /api/v1 - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -📨 🔜 🕳 💖: - -```JSON -{ - "message": "Hello World", - "root_path": "/api/v1" -} -``` - -### ⚒ `root_path` FastAPI 📱 - -👐, 🚥 👆 🚫 ✔️ 🌌 🚚 📋 ⏸ 🎛 💖 `--root-path` ⚖️ 🌓, 👆 💪 ⚒ `root_path` 🔢 🕐❔ 🏗 👆 FastAPI 📱: - -{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} - -🚶‍♀️ `root_path` `FastAPI` 🔜 🌓 🚶‍♀️ `--root-path` 📋 ⏸ 🎛 Uvicorn ⚖️ Hypercorn. - -### 🔃 `root_path` - -✔️ 🤯 👈 💽 (Uvicorn) 🏆 🚫 ⚙️ 👈 `root_path` 🕳 🙆 🌘 🚶‍♀️ ⚫️ 📱. - -✋️ 🚥 👆 🚶 ⏮️ 👆 🖥 http://127.0.0.1:8000/app 👆 🔜 👀 😐 📨: - -```JSON -{ - "message": "Hello World", - "root_path": "/api/v1" -} -``` - -, ⚫️ 🏆 🚫 ⌛ 🔐 `http://127.0.0.1:8000/api/v1/app`. - -Uvicorn 🔜 ⌛ 🗳 🔐 Uvicorn `http://127.0.0.1:8000/app`, & ⤴️ ⚫️ 🔜 🗳 🎯 🚮 ➕ `/api/v1` 🔡 🔛 🔝. - -## 🔃 🗳 ⏮️ 🎞 ➡ 🔡 - -✔️ 🤯 👈 🗳 ⏮️ 🎞 ➡ 🔡 🕴 1️⃣ 🌌 🔗 ⚫️. - -🎲 📚 💼 🔢 🔜 👈 🗳 🚫 ✔️ 🏚 ➡ 🔡. - -💼 💖 👈 (🍵 🎞 ➡ 🔡), 🗳 🔜 👂 🔛 🕳 💖 `https://myawesomeapp.com`, & ⤴️ 🚥 🖥 🚶 `https://myawesomeapp.com/api/v1/app` & 👆 💽 (✅ Uvicorn) 👂 🔛 `http://127.0.0.1:8000` 🗳 (🍵 🎞 ➡ 🔡) 🔜 🔐 Uvicorn 🎏 ➡: `http://127.0.0.1:8000/api/v1/app`. - -## 🔬 🌐 ⏮️ Traefik - -👆 💪 💪 🏃 🥼 🌐 ⏮️ 🎞 ➡ 🔡 ⚙️ Traefik. - -⏬ Traefik, ⚫️ 👁 💱, 👆 💪 ⚗ 🗜 📁 & 🏃 ⚫️ 🔗 ⚪️➡️ 📶. - -⤴️ ✍ 📁 `traefik.toml` ⏮️: - -```TOML hl_lines="3" -[entryPoints] - [entryPoints.http] - address = ":9999" - -[providers] - [providers.file] - filename = "routes.toml" -``` - -👉 💬 Traefik 👂 🔛 ⛴ 9️⃣9️⃣9️⃣9️⃣ & ⚙️ ➕1️⃣ 📁 `routes.toml`. - -/// tip - -👥 ⚙️ ⛴ 9️⃣9️⃣9️⃣9️⃣ ↩️ 🐩 🇺🇸🔍 ⛴ 8️⃣0️⃣ 👈 👆 🚫 ✔️ 🏃 ⚫️ ⏮️ 📡 (`sudo`) 😌. - -/// - -🔜 ✍ 👈 🎏 📁 `routes.toml`: - -```TOML hl_lines="5 12 20" -[http] - [http.middlewares] - - [http.middlewares.api-stripprefix.stripPrefix] - prefixes = ["/api/v1"] - - [http.routers] - - [http.routers.app-http] - entryPoints = ["http"] - service = "app" - rule = "PathPrefix(`/api/v1`)" - middlewares = ["api-stripprefix"] - - [http.services] - - [http.services.app] - [http.services.app.loadBalancer] - [[http.services.app.loadBalancer.servers]] - url = "http://127.0.0.1:8000" -``` - -👉 📁 🔗 Traefik ⚙️ ➡ 🔡 `/api/v1`. - -& ⤴️ ⚫️ 🔜 ❎ 🚮 📨 👆 Uvicorn 🏃‍♂ 🔛 `http://127.0.0.1:8000`. - -🔜 ▶️ Traefik: - -
- -```console -$ ./traefik --configFile=traefik.toml - -INFO[0000] Configuration loaded from file: /home/user/awesomeapi/traefik.toml -``` - -
- -& 🔜 ▶️ 👆 📱 ⏮️ Uvicorn, ⚙️ `--root-path` 🎛: - -
- -```console -$ uvicorn main:app --root-path /api/v1 - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -### ✅ 📨 - -🔜, 🚥 👆 🚶 📛 ⏮️ ⛴ Uvicorn: http://127.0.0.1:8000/app, 👆 🔜 👀 😐 📨: - -```JSON -{ - "message": "Hello World", - "root_path": "/api/v1" -} -``` - -/// tip - -👀 👈 ✋️ 👆 🔐 ⚫️ `http://127.0.0.1:8000/app` ⚫️ 🎦 `root_path` `/api/v1`, ✊ ⚪️➡️ 🎛 `--root-path`. - -/// - -& 🔜 📂 📛 ⏮️ ⛴ Traefik, ✅ ➡ 🔡: http://127.0.0.1:9999/api/v1/app. - -👥 🤚 🎏 📨: - -```JSON -{ - "message": "Hello World", - "root_path": "/api/v1" -} -``` - -✋️ 👉 🕰 📛 ⏮️ 🔡 ➡ 🚚 🗳: `/api/v1`. - -↗️, 💭 📥 👈 👱 🔜 🔐 📱 🔘 🗳, ⏬ ⏮️ ➡ 🔡 `/app/v1` "☑" 1️⃣. - -& ⏬ 🍵 ➡ 🔡 (`http://127.0.0.1:8000/app`), 🚚 Uvicorn 🔗, 🔜 🎯 _🗳_ (Traefik) 🔐 ⚫️. - -👈 🎦 ❔ 🗳 (Traefik) ⚙️ ➡ 🔡 & ❔ 💽 (Uvicorn) ⚙️ `root_path` ⚪️➡️ 🎛 `--root-path`. - -### ✅ 🩺 🎚 - -✋️ 📥 🎊 🍕. 👶 - -"🛂" 🌌 🔐 📱 🔜 🔘 🗳 ⏮️ ➡ 🔡 👈 👥 🔬. , 👥 🔜 ⌛, 🚥 👆 🔄 🩺 🎚 🍦 Uvicorn 🔗, 🍵 ➡ 🔡 📛, ⚫️ 🏆 🚫 👷, ↩️ ⚫️ ⌛ 🔐 🔘 🗳. - -👆 💪 ✅ ⚫️ http://127.0.0.1:8000/docs: - - - -✋️ 🚥 👥 🔐 🩺 🎚 "🛂" 📛 ⚙️ 🗳 ⏮️ ⛴ `9999`, `/api/v1/docs`, ⚫️ 👷 ☑ ❗ 👶 - -👆 💪 ✅ ⚫️ http://127.0.0.1:9999/api/v1/docs: - - - -▶️️ 👥 💚 ⚫️. 👶 👶 - -👉 ↩️ FastAPI ⚙️ 👉 `root_path` ✍ 🔢 `server` 🗄 ⏮️ 📛 🚚 `root_path`. - -## 🌖 💽 - -/// warning - -👉 🌅 🏧 ⚙️ 💼. 💭 🆓 🚶 ⚫️. - -/// - -🔢, **FastAPI** 🔜 ✍ `server` 🗄 🔗 ⏮️ 📛 `root_path`. - -✋️ 👆 💪 🚚 🎏 🎛 `servers`, 🖼 🚥 👆 💚 *🎏* 🩺 🎚 🔗 ⏮️ 🏗 & 🏭 🌐. - -🚥 👆 🚶‍♀️ 🛃 📇 `servers` & 📤 `root_path` (↩️ 👆 🛠️ 👨‍❤‍👨 ⛅ 🗳), **FastAPI** 🔜 📩 "💽" ⏮️ 👉 `root_path` ▶️ 📇. - -🖼: - -{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} - -🔜 🏗 🗄 🔗 💖: - -```JSON hl_lines="5-7" -{ - "openapi": "3.0.2", - // More stuff here - "servers": [ - { - "url": "/api/v1" - }, - { - "url": "https://stag.example.com", - "description": "Staging environment" - }, - { - "url": "https://prod.example.com", - "description": "Production environment" - } - ], - "paths": { - // More stuff here - } -} -``` - -/// tip - -👀 🚘-🏗 💽 ⏮️ `url` 💲 `/api/v1`, ✊ ⚪️➡️ `root_path`. - -/// - -🩺 🎚 http://127.0.0.1:9999/api/v1/docs ⚫️ 🔜 👀 💖: - - - -/// tip - -🩺 🎚 🔜 🔗 ⏮️ 💽 👈 👆 🖊. - -/// - -### ❎ 🏧 💽 ⚪️➡️ `root_path` - -🚥 👆 🚫 💚 **FastAPI** 🔌 🏧 💽 ⚙️ `root_path`, 👆 💪 ⚙️ 🔢 `root_path_in_servers=False`: - -{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} - -& ⤴️ ⚫️ 🏆 🚫 🔌 ⚫️ 🗄 🔗. - -## 🗜 🎧-🈸 - -🚥 👆 💪 🗻 🎧-🈸 (🔬 [🎧 🈸 - 🗻](sub-applications.md){.internal-link target=_blank}) ⏪ ⚙️ 🗳 ⏮️ `root_path`, 👆 💪 ⚫️ 🛎, 👆 🔜 ⌛. - -FastAPI 🔜 🔘 ⚙️ `root_path` 🎆, ⚫️ 🔜 👷. 👶 diff --git a/docs/em/docs/advanced/custom-response.md b/docs/em/docs/advanced/custom-response.md deleted file mode 100644 index ab95b3e7be..0000000000 --- a/docs/em/docs/advanced/custom-response.md +++ /dev/null @@ -1,303 +0,0 @@ -# 🛃 📨 - 🕸, 🎏, 📁, 🎏 - -🔢, **FastAPI** 🔜 📨 📨 ⚙️ `JSONResponse`. - -👆 💪 🔐 ⚫️ 🛬 `Response` 🔗 👀 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}. - -✋️ 🚥 👆 📨 `Response` 🔗, 📊 🏆 🚫 🔁 🗜, & 🧾 🏆 🚫 🔁 🏗 (🖼, 🔌 🎯 "📻 🆎", 🇺🇸🔍 🎚 `Content-Type` 🍕 🏗 🗄). - -✋️ 👆 💪 📣 `Response` 👈 👆 💚 ⚙️, *➡ 🛠️ 👨‍🎨*. - -🎚 👈 👆 📨 ⚪️➡️ 👆 *➡ 🛠️ 🔢* 🔜 🚮 🔘 👈 `Response`. - -& 🚥 👈 `Response` ✔️ 🎻 📻 🆎 (`application/json`), 💖 💼 ⏮️ `JSONResponse` & `UJSONResponse`, 💽 👆 📨 🔜 🔁 🗜 (& ⛽) ⏮️ 🙆 Pydantic `response_model` 👈 👆 📣 *➡ 🛠️ 👨‍🎨*. - -/// note - -🚥 👆 ⚙️ 📨 🎓 ⏮️ 🙅‍♂ 📻 🆎, FastAPI 🔜 ⌛ 👆 📨 ✔️ 🙅‍♂ 🎚, ⚫️ 🔜 🚫 📄 📨 📁 🚮 🏗 🗄 🩺. - -/// - -## ⚙️ `ORJSONResponse` - -🖼, 🚥 👆 ✊ 🎭, 👆 💪 ❎ & ⚙️ `orjson` & ⚒ 📨 `ORJSONResponse`. - -🗄 `Response` 🎓 (🎧-🎓) 👆 💚 ⚙️ & 📣 ⚫️ *➡ 🛠️ 👨‍🎨*. - -⭕ 📨, 📨 `Response` 🔗 🌅 ⏩ 🌘 🛬 📖. - -👉 ↩️ 🔢, FastAPI 🔜 ✔ 🔠 🏬 🔘 & ⚒ 💭 ⚫️ 🎻 ⏮️ 🎻, ⚙️ 🎏 [🎻 🔗 🔢](../tutorial/encoder.md){.internal-link target=_blank} 🔬 🔰. 👉 ⚫️❔ ✔ 👆 📨 **❌ 🎚**, 🖼 💽 🏷. - -✋️ 🚥 👆 🎯 👈 🎚 👈 👆 🛬 **🎻 ⏮️ 🎻**, 👆 💪 🚶‍♀️ ⚫️ 🔗 📨 🎓 & ❎ ➕ 🌥 👈 FastAPI 🔜 ✔️ 🚶‍♀️ 👆 📨 🎚 🔘 `jsonable_encoder` ⏭ 🚶‍♀️ ⚫️ 📨 🎓. - -{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} - -/// info - -🔢 `response_class` 🔜 ⚙️ 🔬 "📻 🆎" 📨. - -👉 💼, 🇺🇸🔍 🎚 `Content-Type` 🔜 ⚒ `application/json`. - - & ⚫️ 🔜 📄 ✅ 🗄. - -/// - -/// tip - -`ORJSONResponse` ⏳ 🕴 💪 FastAPI, 🚫 💃. - -/// - -## 🕸 📨 - -📨 📨 ⏮️ 🕸 🔗 ⚪️➡️ **FastAPI**, ⚙️ `HTMLResponse`. - -* 🗄 `HTMLResponse`. -* 🚶‍♀️ `HTMLResponse` 🔢 `response_class` 👆 *➡ 🛠️ 👨‍🎨*. - -{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} - -/// info - -🔢 `response_class` 🔜 ⚙️ 🔬 "📻 🆎" 📨. - -👉 💼, 🇺🇸🔍 🎚 `Content-Type` 🔜 ⚒ `text/html`. - - & ⚫️ 🔜 📄 ✅ 🗄. - -/// - -### 📨 `Response` - -👀 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}, 👆 💪 🔐 📨 🔗 👆 *➡ 🛠️*, 🛬 ⚫️. - -🎏 🖼 ⚪️➡️ 🔛, 🛬 `HTMLResponse`, 💪 👀 💖: - -{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} - -/// warning - -`Response` 📨 🔗 👆 *➡ 🛠️ 🔢* 🏆 🚫 📄 🗄 (🖼, `Content-Type` 🏆 🚫 📄) & 🏆 🚫 ⭐ 🏧 🎓 🩺. - -/// - -/// info - -↗️, ☑ `Content-Type` 🎚, 👔 📟, ♒️, 🔜 👟 ⚪️➡️ `Response` 🎚 👆 📨. - -/// - -### 📄 🗄 & 🔐 `Response` - -🚥 👆 💚 🔐 📨 ⚪️➡️ 🔘 🔢 ✋️ 🎏 🕰 📄 "📻 🆎" 🗄, 👆 💪 ⚙️ `response_class` 🔢 & 📨 `Response` 🎚. - -`response_class` 🔜 ⤴️ ⚙️ 🕴 📄 🗄 *➡ 🛠️*, ✋️ 👆 `Response` 🔜 ⚙️. - -#### 📨 `HTMLResponse` 🔗 - -🖼, ⚫️ 💪 🕳 💖: - -{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} - -👉 🖼, 🔢 `generate_html_response()` ⏪ 🏗 & 📨 `Response` ↩️ 🛬 🕸 `str`. - -🛬 🏁 🤙 `generate_html_response()`, 👆 ⏪ 🛬 `Response` 👈 🔜 🔐 🔢 **FastAPI** 🎭. - -✋️ 👆 🚶‍♀️ `HTMLResponse` `response_class` 💁‍♂️, **FastAPI** 🔜 💭 ❔ 📄 ⚫️ 🗄 & 🎓 🩺 🕸 ⏮️ `text/html`: - - - -## 💪 📨 - -📥 💪 📨. - -✔️ 🤯 👈 👆 💪 ⚙️ `Response` 📨 🕳 🙆, ⚖️ ✍ 🛃 🎧-🎓. - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import HTMLResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. - -/// - -### `Response` - -👑 `Response` 🎓, 🌐 🎏 📨 😖 ⚪️➡️ ⚫️. - -👆 💪 📨 ⚫️ 🔗. - -⚫️ 🚫 📄 🔢: - -* `content` - `str` ⚖️ `bytes`. -* `status_code` - `int` 🇺🇸🔍 👔 📟. -* `headers` - `dict` 🎻. -* `media_type` - `str` 🤝 📻 🆎. 🤶 Ⓜ. `"text/html"`. - -FastAPI (🤙 💃) 🔜 🔁 🔌 🎚-📐 🎚. ⚫️ 🔜 🔌 🎚-🆎 🎚, ⚓️ 🔛 = & 🔁 = ✍ 🆎. - -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} - -### `HTMLResponse` - -✊ ✍ ⚖️ 🔢 & 📨 🕸 📨, 👆 ✍ 🔛. - -### `PlainTextResponse` - -✊ ✍ ⚖️ 🔢 & 📨 ✅ ✍ 📨. - -{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} - -### `JSONResponse` - -✊ 💽 & 📨 `application/json` 🗜 📨. - -👉 🔢 📨 ⚙️ **FastAPI**, 👆 ✍ 🔛. - -### `ORJSONResponse` - -⏩ 🎛 🎻 📨 ⚙️ `orjson`, 👆 ✍ 🔛. - -### `UJSONResponse` - -🎛 🎻 📨 ⚙️ `ujson`. - -/// warning - -`ujson` 🌘 💛 🌘 🐍 🏗-🛠️ ❔ ⚫️ 🍵 📐-💼. - -/// - -{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} - -/// tip - -⚫️ 💪 👈 `ORJSONResponse` 💪 ⏩ 🎛. - -/// - -### `RedirectResponse` - -📨 🇺🇸🔍 ❎. ⚙️ 3️⃣0️⃣7️⃣ 👔 📟 (🍕 ❎) 🔢. - -👆 💪 📨 `RedirectResponse` 🔗: - -{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} - ---- - -⚖️ 👆 💪 ⚙️ ⚫️ `response_class` 🔢: - - -{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} - -🚥 👆 👈, ⤴️ 👆 💪 📨 📛 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢. - -👉 💼, `status_code` ⚙️ 🔜 🔢 1️⃣ `RedirectResponse`, ❔ `307`. - ---- - -👆 💪 ⚙️ `status_code` 🔢 🌀 ⏮️ `response_class` 🔢: - -{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} - -### `StreamingResponse` - -✊ 🔁 🚂 ⚖️ 😐 🚂/🎻 & 🎏 📨 💪. - -{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} - -#### ⚙️ `StreamingResponse` ⏮️ 📁-💖 🎚 - -🚥 👆 ✔️ 📁-💖 🎚 (✅ 🎚 📨 `open()`), 👆 💪 ✍ 🚂 🔢 🔁 🤭 👈 📁-💖 🎚. - -👈 🌌, 👆 🚫 ✔️ ✍ ⚫️ 🌐 🥇 💾, & 👆 💪 🚶‍♀️ 👈 🚂 🔢 `StreamingResponse`, & 📨 ⚫️. - -👉 🔌 📚 🗃 🔗 ⏮️ ☁ 💾, 📹 🏭, & 🎏. - -```{ .python .annotate hl_lines="2 10-12 14" } -{!../../docs_src/custom_response/tutorial008.py!} -``` - -1️⃣. 👉 🚂 🔢. ⚫️ "🚂 🔢" ↩️ ⚫️ 🔌 `yield` 📄 🔘. -2️⃣. ⚙️ `with` 🍫, 👥 ⚒ 💭 👈 📁-💖 🎚 📪 ⏮️ 🚂 🔢 🔨. , ⏮️ ⚫️ 🏁 📨 📨. -3️⃣. 👉 `yield from` 💬 🔢 🔁 🤭 👈 👜 🌟 `file_like`. & ⤴️, 🔠 🍕 🔁, 🌾 👈 🍕 👟 ⚪️➡️ 👉 🚂 🔢. - - , ⚫️ 🚂 🔢 👈 📨 "🏭" 👷 🕳 🙆 🔘. - - 🔨 ⚫️ 👉 🌌, 👥 💪 🚮 ⚫️ `with` 🍫, & 👈 🌌, 🚚 👈 ⚫️ 📪 ⏮️ 🏁. - -/// tip - -👀 👈 📥 👥 ⚙️ 🐩 `open()` 👈 🚫 🐕‍🦺 `async` & `await`, 👥 📣 ➡ 🛠️ ⏮️ 😐 `def`. - -/// - -### `FileResponse` - -🔁 🎏 📁 📨. - -✊ 🎏 ⚒ ❌ 🔗 🌘 🎏 📨 🆎: - -* `path` - 📁 📁 🎏. -* `headers` - 🙆 🛃 🎚 🔌, 📖. -* `media_type` - 🎻 🤝 📻 🆎. 🚥 🔢, 📁 ⚖️ ➡ 🔜 ⚙️ 🔑 📻 🆎. -* `filename` - 🚥 ⚒, 👉 🔜 🔌 📨 `Content-Disposition`. - -📁 📨 🔜 🔌 ☑ `Content-Length`, `Last-Modified` & `ETag` 🎚. - -{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} - -👆 💪 ⚙️ `response_class` 🔢: - -{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} - -👉 💼, 👆 💪 📨 📁 ➡ 🔗 ⚪️➡️ 👆 *➡ 🛠️* 🔢. - -## 🛃 📨 🎓 - -👆 💪 ✍ 👆 👍 🛃 📨 🎓, 😖 ⚪️➡️ `Response` & ⚙️ ⚫️. - -🖼, ➡️ 💬 👈 👆 💚 ⚙️ `orjson`, ✋️ ⏮️ 🛃 ⚒ 🚫 ⚙️ 🔌 `ORJSONResponse` 🎓. - -➡️ 💬 👆 💚 ⚫️ 📨 🔂 & 📁 🎻, 👆 💚 ⚙️ Orjson 🎛 `orjson.OPT_INDENT_2`. - -👆 💪 ✍ `CustomORJSONResponse`. 👑 👜 👆 ✔️ ✍ `Response.render(content)` 👩‍🔬 👈 📨 🎚 `bytes`: - -{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} - -🔜 ↩️ 🛬: - -```json -{"message": "Hello World"} -``` - -...👉 📨 🔜 📨: - -```json -{ - "message": "Hello World" -} -``` - -↗️, 👆 🔜 🎲 🔎 🌅 👍 🌌 ✊ 📈 👉 🌘 ❕ 🎻. 👶 - -## 🔢 📨 🎓 - -🕐❔ 🏗 **FastAPI** 🎓 👐 ⚖️ `APIRouter` 👆 💪 ✔ ❔ 📨 🎓 ⚙️ 🔢. - -🔢 👈 🔬 👉 `default_response_class`. - -🖼 🔛, **FastAPI** 🔜 ⚙️ `ORJSONResponse` 🔢, 🌐 *➡ 🛠️*, ↩️ `JSONResponse`. - -{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} - -/// tip - -👆 💪 🔐 `response_class` *➡ 🛠️* ⏭. - -/// - -## 🌖 🧾 - -👆 💪 📣 📻 🆎 & 📚 🎏 ℹ 🗄 ⚙️ `responses`: [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/dataclasses.md b/docs/em/docs/advanced/dataclasses.md deleted file mode 100644 index 4dd597262a..0000000000 --- a/docs/em/docs/advanced/dataclasses.md +++ /dev/null @@ -1,97 +0,0 @@ -# ⚙️ 🎻 - -FastAPI 🏗 🔛 🔝 **Pydantic**, & 👤 ✔️ 🌏 👆 ❔ ⚙️ Pydantic 🏷 📣 📨 & 📨. - -✋️ FastAPI 🐕‍🦺 ⚙️ `dataclasses` 🎏 🌌: - -{* ../../docs_src/dataclasses/tutorial001.py hl[1,7:12,19:20] *} - -👉 🐕‍🦺 👏 **Pydantic**, ⚫️ ✔️ 🔗 🐕‍🦺 `dataclasses`. - -, ⏮️ 📟 🔛 👈 🚫 ⚙️ Pydantic 🎯, FastAPI ⚙️ Pydantic 🗜 📚 🐩 🎻 Pydantic 👍 🍛 🎻. - -& ↗️, ⚫️ 🐕‍🦺 🎏: - -* 💽 🔬 -* 💽 🛠️ -* 💽 🧾, ♒️. - -👉 👷 🎏 🌌 ⏮️ Pydantic 🏷. & ⚫️ 🤙 🏆 🎏 🌌 🔘, ⚙️ Pydantic. - -/// info - -✔️ 🤯 👈 🎻 💪 🚫 🌐 Pydantic 🏷 💪. - -, 👆 5️⃣📆 💪 ⚙️ Pydantic 🏷. - -✋️ 🚥 👆 ✔️ 📚 🎻 🤥 🤭, 👉 👌 🎱 ⚙️ 👫 🏋️ 🕸 🛠️ ⚙️ FastAPI. 👶 - -/// - -## 🎻 `response_model` - -👆 💪 ⚙️ `dataclasses` `response_model` 🔢: - -{* ../../docs_src/dataclasses/tutorial002.py hl[1,7:13,19] *} - -🎻 🔜 🔁 🗜 Pydantic 🎻. - -👉 🌌, 🚮 🔗 🔜 🎦 🆙 🛠️ 🩺 👩‍💻 🔢: - - - -## 🎻 🔁 📊 📊 - -👆 💪 🌀 `dataclasses` ⏮️ 🎏 🆎 ✍ ⚒ 🐦 📊 📊. - -💼, 👆 💪 ✔️ ⚙️ Pydantic ⏬ `dataclasses`. 🖼, 🚥 👆 ✔️ ❌ ⏮️ 🔁 🏗 🛠️ 🧾. - -👈 💼, 👆 💪 🎯 💱 🐩 `dataclasses` ⏮️ `pydantic.dataclasses`, ❔ 💧-♻: - -```{ .python .annotate hl_lines="1 5 8-11 14-17 23-25 28" } -{!../../docs_src/dataclasses/tutorial003.py!} -``` - -1️⃣. 👥 🗄 `field` ⚪️➡️ 🐩 `dataclasses`. - -2️⃣. `pydantic.dataclasses` 💧-♻ `dataclasses`. - -3️⃣. `Author` 🎻 🔌 📇 `Item` 🎻. - -4️⃣. `Author` 🎻 ⚙️ `response_model` 🔢. - -5️⃣. 👆 💪 ⚙️ 🎏 🐩 🆎 ✍ ⏮️ 🎻 📨 💪. - - 👉 💼, ⚫️ 📇 `Item` 🎻. - -6️⃣. 📥 👥 🛬 📖 👈 🔌 `items` ❔ 📇 🎻. - - FastAPI 🎯 💽 🎻. - -7️⃣. 📥 `response_model` ⚙️ 🆎 ✍ 📇 `Author` 🎻. - - 🔄, 👆 💪 🌀 `dataclasses` ⏮️ 🐩 🆎 ✍. - -8️⃣. 👀 👈 👉 *➡ 🛠️ 🔢* ⚙️ 🥔 `def` ↩️ `async def`. - - 🕧, FastAPI 👆 💪 🌀 `def` & `async def` 💪. - - 🚥 👆 💪 ↗️ 🔃 🕐❔ ⚙️ ❔, ✅ 👅 📄 _"🏃 ❓" _ 🩺 🔃 `async` & `await`. - -9️⃣. 👉 *➡ 🛠️ 🔢* 🚫 🛬 🎻 (👐 ⚫️ 💪), ✋️ 📇 📖 ⏮️ 🔗 💽. - - FastAPI 🔜 ⚙️ `response_model` 🔢 (👈 🔌 🎻) 🗜 📨. - -👆 💪 🌀 `dataclasses` ⏮️ 🎏 🆎 ✍ 📚 🎏 🌀 📨 🏗 📊 📊. - -✅-📟 ✍ 💁‍♂ 🔛 👀 🌅 🎯 ℹ. - -## 💡 🌅 - -👆 💪 🌀 `dataclasses` ⏮️ 🎏 Pydantic 🏷, 😖 ⚪️➡️ 👫, 🔌 👫 👆 👍 🏷, ♒️. - -💡 🌅, ✅ Pydantic 🩺 🔃 🎻. - -## ⏬ - -👉 💪 ↩️ FastAPI ⏬ `0.67.0`. 👶 diff --git a/docs/em/docs/advanced/events.md b/docs/em/docs/advanced/events.md deleted file mode 100644 index dcaac710e1..0000000000 --- a/docs/em/docs/advanced/events.md +++ /dev/null @@ -1,163 +0,0 @@ -# 🔆 🎉 - -👆 💪 🔬 ⚛ (📟) 👈 🔜 🛠️ ⏭ 🈸 **▶️ 🆙**. 👉 ⛓ 👈 👉 📟 🔜 🛠️ **🕐**, **⏭** 🈸 **▶️ 📨 📨**. - -🎏 🌌, 👆 💪 🔬 ⚛ (📟) 👈 🔜 🛠️ 🕐❔ 🈸 **🤫 🔽**. 👉 💼, 👉 📟 🔜 🛠️ **🕐**, **⏮️** ✔️ 🍵 🎲 **📚 📨**. - -↩️ 👉 📟 🛠️ ⏭ 🈸 **▶️** ✊ 📨, & ▶️️ ⏮️ ⚫️ **🏁** 🚚 📨, ⚫️ 📔 🎂 🈸 **🔆** (🔤 "🔆" 🔜 ⚠ 🥈 👶). - -👉 💪 📶 ⚠ ⚒ 🆙 **ℹ** 👈 👆 💪 ⚙️ 🎂 📱, & 👈 **💰** 👪 📨, &/⚖️ 👈 👆 💪 **🧹 🆙** ⏮️. 🖼, 💽 🔗 🎱, ⚖️ 🚚 🔗 🎰 🏫 🏷. - -## ⚙️ 💼 - -➡️ ▶️ ⏮️ 🖼 **⚙️ 💼** & ⤴️ 👀 ❔ ❎ ⚫️ ⏮️ 👉. - -➡️ 🌈 👈 👆 ✔️ **🎰 🏫 🏷** 👈 👆 💚 ⚙️ 🍵 📨. 👶 - -🎏 🏷 🔗 👪 📨,, ⚫️ 🚫 1️⃣ 🏷 📍 📨, ⚖️ 1️⃣ 📍 👩‍💻 ⚖️ 🕳 🎏. - -➡️ 🌈 👈 🚚 🏷 💪 **✊ 🕰**, ↩️ ⚫️ ✔️ ✍ 📚 **💽 ⚪️➡️ 💾**. 👆 🚫 💚 ⚫️ 🔠 📨. - -👆 💪 📐 ⚫️ 🔝 🎚 🕹/📁, ✋️ 👈 🔜 ⛓ 👈 ⚫️ 🔜 **📐 🏷** 🚥 👆 🏃‍♂ 🙅 🏧 💯, ⤴️ 👈 💯 🔜 **🐌** ↩️ ⚫️ 🔜 ✔️ ⌛ 🏷 📐 ⏭ 💆‍♂ 💪 🏃 🔬 🍕 📟. - -👈 ⚫️❔ 👥 🔜 ❎, ➡️ 📐 🏷 ⏭ 📨 🍵, ✋️ 🕴 ▶️️ ⏭ 🈸 ▶️ 📨 📨, 🚫 ⏪ 📟 ➖ 📐. - -## 🔆 - -👆 💪 🔬 👉 *🕴* & *🤫* ⚛ ⚙️ `lifespan` 🔢 `FastAPI` 📱, & "🔑 👨‍💼" (👤 🔜 🎦 👆 ⚫️❔ 👈 🥈). - -➡️ ▶️ ⏮️ 🖼 & ⤴️ 👀 ⚫️ ℹ. - -👥 ✍ 🔁 🔢 `lifespan()` ⏮️ `yield` 💖 👉: - -{* ../../docs_src/events/tutorial003.py hl[16,19] *} - -📥 👥 ⚖ 😥 *🕴* 🛠️ 🚚 🏷 🚮 (❌) 🏷 🔢 📖 ⏮️ 🎰 🏫 🏷 ⏭ `yield`. 👉 📟 🔜 🛠️ **⏭** 🈸 **▶️ ✊ 📨**, ⏮️ *🕴*. - -& ⤴️, ▶️️ ⏮️ `yield`, 👥 🚚 🏷. 👉 📟 🔜 🛠️ **⏮️** 🈸 **🏁 🚚 📨**, ▶️️ ⏭ *🤫*. 👉 💪, 🖼, 🚀 ℹ 💖 💾 ⚖️ 💻. - -/// tip - -`shutdown` 🔜 🔨 🕐❔ 👆 **⛔️** 🈸. - -🎲 👆 💪 ▶️ 🆕 ⏬, ⚖️ 👆 🤚 🎡 🏃 ⚫️. 🤷 - -/// - -### 🔆 🔢 - -🥇 👜 👀, 👈 👥 ⚖ 🔁 🔢 ⏮️ `yield`. 👉 📶 🎏 🔗 ⏮️ `yield`. - -{* ../../docs_src/events/tutorial003.py hl[14:19] *} - -🥇 🍕 🔢, ⏭ `yield`, 🔜 🛠️ **⏭** 🈸 ▶️. - -& 🍕 ⏮️ `yield` 🔜 🛠️ **⏮️** 🈸 ✔️ 🏁. - -### 🔁 🔑 👨‍💼 - -🚥 👆 ✅, 🔢 🎀 ⏮️ `@asynccontextmanager`. - -👈 🗜 🔢 🔘 🕳 🤙 "**🔁 🔑 👨‍💼**". - -{* ../../docs_src/events/tutorial003.py hl[1,13] *} - -**🔑 👨‍💼** 🐍 🕳 👈 👆 💪 ⚙️ `with` 📄, 🖼, `open()` 💪 ⚙️ 🔑 👨‍💼: - -```Python -with open("file.txt") as file: - file.read() -``` - -⏮️ ⏬ 🐍, 📤 **🔁 🔑 👨‍💼**. 👆 🔜 ⚙️ ⚫️ ⏮️ `async with`: - -```Python -async with lifespan(app): - await do_stuff() -``` - -🕐❔ 👆 ✍ 🔑 👨‍💼 ⚖️ 🔁 🔑 👨‍💼 💖 🔛, ⚫️❔ ⚫️ 🔨 👈, ⏭ 🛬 `with` 🍫, ⚫️ 🔜 🛠️ 📟 ⏭ `yield`, & ⏮️ ❎ `with` 🍫, ⚫️ 🔜 🛠️ 📟 ⏮️ `yield`. - -👆 📟 🖼 🔛, 👥 🚫 ⚙️ ⚫️ 🔗, ✋️ 👥 🚶‍♀️ ⚫️ FastAPI ⚫️ ⚙️ ⚫️. - -`lifespan` 🔢 `FastAPI` 📱 ✊ **🔁 🔑 👨‍💼**, 👥 💪 🚶‍♀️ 👆 🆕 `lifespan` 🔁 🔑 👨‍💼 ⚫️. - -{* ../../docs_src/events/tutorial003.py hl[22] *} - -## 🎛 🎉 (😢) - -/// warning - -👍 🌌 🍵 *🕴* & *🤫* ⚙️ `lifespan` 🔢 `FastAPI` 📱 🔬 🔛. - -👆 💪 🎲 🚶 👉 🍕. - -/// - -📤 🎛 🌌 🔬 👉 ⚛ 🛠️ ⏮️ *🕴* & ⏮️ *🤫*. - -👆 💪 🔬 🎉 🐕‍🦺 (🔢) 👈 💪 🛠️ ⏭ 🈸 ▶️ 🆙, ⚖️ 🕐❔ 🈸 🤫 🔽. - -👫 🔢 💪 📣 ⏮️ `async def` ⚖️ 😐 `def`. - -### `startup` 🎉 - -🚮 🔢 👈 🔜 🏃 ⏭ 🈸 ▶️, 📣 ⚫️ ⏮️ 🎉 `"startup"`: - -{* ../../docs_src/events/tutorial001.py hl[8] *} - -👉 💼, `startup` 🎉 🐕‍🦺 🔢 🔜 🔢 🏬 "💽" ( `dict`) ⏮️ 💲. - -👆 💪 🚮 🌅 🌘 1️⃣ 🎉 🐕‍🦺 🔢. - -& 👆 🈸 🏆 🚫 ▶️ 📨 📨 ⏭ 🌐 `startup` 🎉 🐕‍🦺 ✔️ 🏁. - -### `shutdown` 🎉 - -🚮 🔢 👈 🔜 🏃 🕐❔ 🈸 🤫 🔽, 📣 ⚫️ ⏮️ 🎉 `"shutdown"`: - -{* ../../docs_src/events/tutorial002.py hl[6] *} - -📥, `shutdown` 🎉 🐕‍🦺 🔢 🔜 ✍ ✍ ⏸ `"Application shutdown"` 📁 `log.txt`. - -/// info - -`open()` 🔢, `mode="a"` ⛓ "🎻",, ⏸ 🔜 🚮 ⏮️ ⚫️❔ 🔛 👈 📁, 🍵 📁 ⏮️ 🎚. - -/// - -/// tip - -👀 👈 👉 💼 👥 ⚙️ 🐩 🐍 `open()` 🔢 👈 🔗 ⏮️ 📁. - -, ⚫️ 🔌 👤/🅾 (🔢/🔢), 👈 🚚 "⌛" 👜 ✍ 💾. - -✋️ `open()` 🚫 ⚙️ `async` & `await`. - -, 👥 📣 🎉 🐕‍🦺 🔢 ⏮️ 🐩 `def` ↩️ `async def`. - -/// - -/// info - -👆 💪 ✍ 🌅 🔃 👫 🎉 🐕‍🦺 💃 🎉' 🩺. - -/// - -### `startup` & `shutdown` 👯‍♂️ - -📤 ↕ 🤞 👈 ⚛ 👆 *🕴* & *🤫* 🔗, 👆 💪 💚 ▶️ 🕳 & ⤴️ 🏁 ⚫️, 📎 ℹ & ⤴️ 🚀 ⚫️, ♒️. - -🔨 👈 👽 🔢 👈 🚫 💰 ⚛ ⚖️ 🔢 👯‍♂️ 🌅 ⚠ 👆 🔜 💪 🏪 💲 🌐 🔢 ⚖️ 🎏 🎱. - -↩️ 👈, ⚫️ 🔜 👍 ↩️ ⚙️ `lifespan` 🔬 🔛. - -## 📡 ℹ - -📡 ℹ 😟 🤓. 👶 - -🔘, 🔫 📡 🔧, 👉 🍕 🔆 🛠️, & ⚫️ 🔬 🎉 🤙 `startup` & `shutdown`. - -## 🎧 🈸 - -👶 ✔️ 🤯 👈 👫 🔆 🎉 (🕴 & 🤫) 🔜 🕴 🛠️ 👑 🈸, 🚫 [🎧 🈸 - 🗻](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/generate-clients.md b/docs/em/docs/advanced/generate-clients.md deleted file mode 100644 index a680c9051e..0000000000 --- a/docs/em/docs/advanced/generate-clients.md +++ /dev/null @@ -1,238 +0,0 @@ -# 🏗 👩‍💻 - -**FastAPI** ⚓️ 🔛 🗄 🔧, 👆 🤚 🏧 🔗 ⏮️ 📚 🧰, 🔌 🏧 🛠️ 🩺 (🚚 🦁 🎚). - -1️⃣ 🎯 📈 👈 🚫 🎯 ⭐ 👈 👆 💪 **🏗 👩‍💻** (🕣 🤙 **📱** ) 👆 🛠️, 📚 🎏 **🛠️ 🇪🇸**. - -## 🗄 👩‍💻 🚂 - -📤 📚 🧰 🏗 👩‍💻 ⚪️➡️ **🗄**. - -⚠ 🧰 🗄 🚂. - -🚥 👆 🏗 **🕸**, 📶 😌 🎛 🗄-📕-🇦🇪. - -## 🏗 📕 🕸 👩‍💻 - -➡️ ▶️ ⏮️ 🙅 FastAPI 🈸: - -{* ../../docs_src/generate_clients/tutorial001.py hl[9:11,14:15,18,19,23] *} - -👀 👈 *➡ 🛠️* 🔬 🏷 👫 ⚙️ 📨 🚀 & 📨 🚀, ⚙️ 🏷 `Item` & `ResponseMessage`. - -### 🛠️ 🩺 - -🚥 👆 🚶 🛠️ 🩺, 👆 🔜 👀 👈 ⚫️ ✔️ **🔗** 📊 📨 📨 & 📨 📨: - - - -👆 💪 👀 👈 🔗 ↩️ 👫 📣 ⏮️ 🏷 📱. - -👈 ℹ 💪 📱 **🗄 🔗**, & ⤴️ 🎦 🛠️ 🩺 (🦁 🎚). - -& 👈 🎏 ℹ ⚪️➡️ 🏷 👈 🔌 🗄 ⚫️❔ 💪 ⚙️ **🏗 👩‍💻 📟**. - -### 🏗 📕 👩‍💻 - -🔜 👈 👥 ✔️ 📱 ⏮️ 🏷, 👥 💪 🏗 👩‍💻 📟 🕸. - -#### ❎ `openapi-ts` - -👆 💪 ❎ `openapi-ts` 👆 🕸 📟 ⏮️: - -
- -```console -$ npm install @hey-api/openapi-ts --save-dev - ----> 100% -``` - -
- -#### 🏗 👩‍💻 📟 - -🏗 👩‍💻 📟 👆 💪 ⚙️ 📋 ⏸ 🈸 `openapi-ts` 👈 🔜 🔜 ❎. - -↩️ ⚫️ ❎ 🇧🇿 🏗, 👆 🎲 🚫🔜 💪 🤙 👈 📋 🔗, ✋️ 👆 🔜 🚮 ⚫️ 🔛 👆 `package.json` 📁. - -⚫️ 💪 👀 💖 👉: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} -``` - -⏮️ ✔️ 👈 ☕ `generate-client` ✍ 📤, 👆 💪 🏃 ⚫️ ⏮️: - -
- -```console -$ npm run generate-client - -frontend-app@1.0.0 generate-client /home/user/code/frontend-app -> openapi-ts --input http://localhost:8000/openapi.json --output ./src/client --client axios -``` - -
- -👈 📋 🔜 🏗 📟 `./src/client` & 🔜 ⚙️ `axios` (🕸 🇺🇸🔍 🗃) 🔘. - -### 🔄 👅 👩‍💻 📟 - -🔜 👆 💪 🗄 & ⚙️ 👩‍💻 📟, ⚫️ 💪 👀 💖 👉, 👀 👈 👆 🤚 ✍ 👩‍🔬: - - - -👆 🔜 🤚 ✍ 🚀 📨: - - - -/// tip - -👀 ✍ `name` & `price`, 👈 🔬 FastAPI 🈸, `Item` 🏷. - -/// - -👆 🔜 ✔️ ⏸ ❌ 📊 👈 👆 📨: - - - -📨 🎚 🔜 ✔️ ✍: - - - -## FastAPI 📱 ⏮️ 🔖 - -📚 💼 👆 FastAPI 📱 🔜 🦏, & 👆 🔜 🎲 ⚙️ 🔖 🎏 🎏 👪 *➡ 🛠️*. - -🖼, 👆 💪 ✔️ 📄 **🏬** & ➕1️⃣ 📄 **👩‍💻**, & 👫 💪 👽 🔖: - - -{* ../../docs_src/generate_clients/tutorial002.py hl[23,28,36] *} - -### 🏗 📕 👩‍💻 ⏮️ 🔖 - -🚥 👆 🏗 👩‍💻 FastAPI 📱 ⚙️ 🔖, ⚫️ 🔜 🛎 🎏 👩‍💻 📟 ⚓️ 🔛 🔖. - -👉 🌌 👆 🔜 💪 ✔️ 👜 ✔ & 👪 ☑ 👩‍💻 📟: - - - -👉 💼 👆 ✔️: - -* `ItemsService` -* `UsersService` - -### 👩‍💻 👩‍🔬 📛 - -▶️️ 🔜 🏗 👩‍🔬 📛 💖 `createItemItemsPost` 🚫 👀 📶 🧹: - -```TypeScript -ItemsService.createItemItemsPost({name: "Plumbus", price: 5}) -``` - -...👈 ↩️ 👩‍💻 🚂 ⚙️ 🗄 🔗 **🛠️ 🆔** 🔠 *➡ 🛠️*. - -🗄 🚚 👈 🔠 🛠️ 🆔 😍 🤭 🌐 *➡ 🛠️*, FastAPI ⚙️ **🔢 📛**, **➡**, & **🇺🇸🔍 👩‍🔬/🛠️** 🏗 👈 🛠️ 🆔, ↩️ 👈 🌌 ⚫️ 💪 ⚒ 💭 👈 🛠️ 🆔 😍. - -✋️ 👤 🔜 🎦 👆 ❔ 📉 👈 ⏭. 👶 - -## 🛃 🛠️ 🆔 & 👍 👩‍🔬 📛 - -👆 💪 **🔀** 🌌 👫 🛠️ 🆔 **🏗** ⚒ 👫 🙅 & ✔️ **🙅 👩‍🔬 📛** 👩‍💻. - -👉 💼 👆 🔜 ✔️ 🚚 👈 🔠 🛠️ 🆔 **😍** 🎏 🌌. - -🖼, 👆 💪 ⚒ 💭 👈 🔠 *➡ 🛠️* ✔️ 🔖, & ⤴️ 🏗 🛠️ 🆔 ⚓️ 🔛 **🔖** & *➡ 🛠️* **📛** (🔢 📛). - -### 🛃 🏗 😍 🆔 🔢 - -FastAPI ⚙️ **😍 🆔** 🔠 *➡ 🛠️*, ⚫️ ⚙️ **🛠️ 🆔** & 📛 🙆 💪 🛃 🏷, 📨 ⚖️ 📨. - -👆 💪 🛃 👈 🔢. ⚫️ ✊ `APIRoute` & 🔢 🎻. - -🖼, 📥 ⚫️ ⚙️ 🥇 🔖 (👆 🔜 🎲 ✔️ 🕴 1️⃣ 🔖) & *➡ 🛠️* 📛 (🔢 📛). - -👆 💪 ⤴️ 🚶‍♀️ 👈 🛃 🔢 **FastAPI** `generate_unique_id_function` 🔢: - -{* ../../docs_src/generate_clients/tutorial003.py hl[8:9,12] *} - -### 🏗 📕 👩‍💻 ⏮️ 🛃 🛠️ 🆔 - -🔜 🚥 👆 🏗 👩‍💻 🔄, 👆 🔜 👀 👈 ⚫️ ✔️ 📉 👩‍🔬 📛: - - - -👆 👀, 👩‍🔬 📛 🔜 ✔️ 🔖 & ⤴️ 🔢 📛, 🔜 👫 🚫 🔌 ℹ ⚪️➡️ 📛 ➡ & 🇺🇸🔍 🛠️. - -### 🗜 🗄 🔧 👩‍💻 🚂 - -🏗 📟 ✔️ **❎ ℹ**. - -👥 ⏪ 💭 👈 👉 👩‍🔬 🔗 **🏬** ↩️ 👈 🔤 `ItemsService` (✊ ⚪️➡️ 🔖), ✋️ 👥 ✔️ 📛 🔡 👩‍🔬 📛 💁‍♂️. 👶 - -👥 🔜 🎲 💚 🚧 ⚫️ 🗄 🏢, 👈 🔜 🚚 👈 🛠️ 🆔 **😍**. - -✋️ 🏗 👩‍💻 👥 💪 **🔀** 🗄 🛠️ 🆔 ▶️️ ⏭ 🏭 👩‍💻, ⚒ 👈 👩‍🔬 📛 👌 & **🧹**. - -👥 💪 ⏬ 🗄 🎻 📁 `openapi.json` & ⤴️ 👥 💪 **❎ 👈 🔡 🔖** ⏮️ ✍ 💖 👉: - -{* ../../docs_src/generate_clients/tutorial004.py *} - -⏮️ 👈, 🛠️ 🆔 🔜 📁 ⚪️➡️ 👜 💖 `items-get_items` `get_items`, 👈 🌌 👩‍💻 🚂 💪 🏗 🙅 👩‍🔬 📛. - -### 🏗 📕 👩‍💻 ⏮️ 🗜 🗄 - -🔜 🔚 🏁 📁 `openapi.json`, 👆 🔜 🔀 `package.json` ⚙️ 👈 🇧🇿 📁, 🖼: - -```JSON hl_lines="7" -{ - "name": "frontend-app", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "generate-client": "openapi-ts --input ./openapi.json --output ./src/client --client axios" - }, - "author": "", - "license": "", - "devDependencies": { - "@hey-api/openapi-ts": "^0.27.38", - "typescript": "^4.6.2" - } -} -``` - -⏮️ 🏭 🆕 👩‍💻, 👆 🔜 🔜 ✔️ **🧹 👩‍🔬 📛**, ⏮️ 🌐 **✍**, **⏸ ❌**, ♒️: - - - -## 💰 - -🕐❔ ⚙️ 🔁 🏗 👩‍💻 👆 🔜 **✍** : - -* 👩‍🔬. -* 📨 🚀 💪, 🔢 🔢, ♒️. -* 📨 🚀. - -👆 🔜 ✔️ **⏸ ❌** 🌐. - -& 🕐❔ 👆 ℹ 👩‍💻 📟, & **♻** 🕸, ⚫️ 🔜 ✔️ 🙆 🆕 *➡ 🛠️* 💪 👩‍🔬, 🗝 🕐 ❎, & 🙆 🎏 🔀 🔜 🎨 🔛 🏗 📟. 👶 - -👉 ⛓ 👈 🚥 🕳 🔀 ⚫️ 🔜 **🎨** 🔛 👩‍💻 📟 🔁. & 🚥 👆 **🏗** 👩‍💻 ⚫️ 🔜 ❌ 👅 🚥 👆 ✔️ 🙆 **🔖** 📊 ⚙️. - -, 👆 🔜 **🔍 📚 ❌** 📶 ⏪ 🛠️ 🛵 ↩️ ✔️ ⌛ ❌ 🎦 🆙 👆 🏁 👩‍💻 🏭 & ⤴️ 🔄 ℹ 🌐❔ ⚠. 👶 diff --git a/docs/em/docs/advanced/index.md b/docs/em/docs/advanced/index.md deleted file mode 100644 index 48ef8e46d0..0000000000 --- a/docs/em/docs/advanced/index.md +++ /dev/null @@ -1,27 +0,0 @@ -# 🏧 👩‍💻 🦮 - -## 🌖 ⚒ - -👑 [🔰 - 👩‍💻 🦮](../tutorial/index.md){.internal-link target=_blank} 🔜 🥃 🤝 👆 🎫 🔘 🌐 👑 ⚒ **FastAPI**. - -⏭ 📄 👆 🔜 👀 🎏 🎛, 📳, & 🌖 ⚒. - -/// tip - -⏭ 📄 **🚫 🎯 "🏧"**. - - & ⚫️ 💪 👈 👆 ⚙️ 💼, ⚗ 1️⃣ 👫. - -/// - -## ✍ 🔰 🥇 - -👆 💪 ⚙️ 🏆 ⚒ **FastAPI** ⏮️ 💡 ⚪️➡️ 👑 [🔰 - 👩‍💻 🦮](../tutorial/index.md){.internal-link target=_blank}. - -& ⏭ 📄 🤔 👆 ⏪ ✍ ⚫️, & 🤔 👈 👆 💭 👈 👑 💭. - -## 🏎.🅾 ↗️ - -🚥 👆 🔜 💖 ✊ 🏧-🔰 ↗️ 🔗 👉 📄 🩺, 👆 💪 💚 ✅: 💯-💾 🛠️ ⏮️ FastAPI & ☁ **🏎.🅾**. - -👫 ⏳ 🩸 1️⃣0️⃣ 💯 🌐 💰 🛠️ **FastAPI**. 👶 👶 diff --git a/docs/em/docs/advanced/middleware.md b/docs/em/docs/advanced/middleware.md deleted file mode 100644 index 22d707062f..0000000000 --- a/docs/em/docs/advanced/middleware.md +++ /dev/null @@ -1,95 +0,0 @@ -# 🏧 🛠️ - -👑 🔰 👆 ✍ ❔ 🚮 [🛃 🛠️](../tutorial/middleware.md){.internal-link target=_blank} 👆 🈸. - -& ⤴️ 👆 ✍ ❔ 🍵 [⚜ ⏮️ `CORSMiddleware`](../tutorial/cors.md){.internal-link target=_blank}. - -👉 📄 👥 🔜 👀 ❔ ⚙️ 🎏 🛠️. - -## ❎ 🔫 🛠️ - -**FastAPI** ⚓️ 🔛 💃 & 🛠️ 🔫 🔧, 👆 💪 ⚙️ 🙆 🔫 🛠️. - -🛠️ 🚫 ✔️ ⚒ FastAPI ⚖️ 💃 👷, 📏 ⚫️ ⏩ 🔫 🔌. - -🏢, 🔫 🛠️ 🎓 👈 ⌛ 📨 🔫 📱 🥇 ❌. - -, 🧾 🥉-🥳 🔫 🛠️ 👫 🔜 🎲 💬 👆 🕳 💖: - -```Python -from unicorn import UnicornMiddleware - -app = SomeASGIApp() - -new_app = UnicornMiddleware(app, some_config="rainbow") -``` - -✋️ FastAPI (🤙 💃) 🚚 🙅 🌌 ⚫️ 👈 ⚒ 💭 👈 🔗 🛠️ 🍵 💽 ❌ & 🛃 ⚠ 🐕‍🦺 👷 ☑. - -👈, 👆 ⚙️ `app.add_middleware()` (🖼 ⚜). - -```Python -from fastapi import FastAPI -from unicorn import UnicornMiddleware - -app = FastAPI() - -app.add_middleware(UnicornMiddleware, some_config="rainbow") -``` - -`app.add_middleware()` 📨 🛠️ 🎓 🥇 ❌ & 🙆 🌖 ❌ 🚶‍♀️ 🛠️. - -## 🛠️ 🛠️ - -**FastAPI** 🔌 📚 🛠️ ⚠ ⚙️ 💼, 👥 🔜 👀 ⏭ ❔ ⚙️ 👫. - -/// note | 📡 ℹ - -⏭ 🖼, 👆 💪 ⚙️ `from starlette.middleware.something import SomethingMiddleware`. - -**FastAPI** 🚚 📚 🛠️ `fastapi.middleware` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 🛠️ 👟 🔗 ⚪️➡️ 💃. - -/// - -## `HTTPSRedirectMiddleware` - -🛠️ 👈 🌐 📨 📨 🔜 👯‍♂️ `https` ⚖️ `wss`. - -🙆 📨 📨 `http` ⚖️ `ws` 🔜 ❎ 🔐 ⚖ ↩️. - -{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} - -## `TrustedHostMiddleware` - -🛠️ 👈 🌐 📨 📨 ✔️ ☑ ⚒ `Host` 🎚, ✔ 💂‍♂ 🛡 🇺🇸🔍 🦠 🎚 👊. - -{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} - -📄 ❌ 🐕‍🦺: - -* `allowed_hosts` - 📇 🆔 📛 👈 🔜 ✔ 📛. 🃏 🆔 ✅ `*.example.com` 🐕‍🦺 🎀 📁. ✔ 🙆 📛 👯‍♂️ ⚙️ `allowed_hosts=["*"]` ⚖️ 🚫 🛠️. - -🚥 📨 📨 🔨 🚫 ✔ ☑ ⤴️ `400` 📨 🔜 📨. - -## `GZipMiddleware` - -🍵 🗜 📨 🙆 📨 👈 🔌 `"gzip"` `Accept-Encoding` 🎚. - -🛠️ 🔜 🍵 👯‍♂️ 🐩 & 🎥 📨. - -{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} - -📄 ❌ 🐕‍🦺: - -* `minimum_size` - 🚫 🗜 📨 👈 🤪 🌘 👉 💯 📐 🔢. 🔢 `500`. - -## 🎏 🛠️ - -📤 📚 🎏 🔫 🛠️. - -🖼: - -* Uvicorn `ProxyHeadersMiddleware` -* 🇸🇲 - -👀 🎏 💪 🛠️ ✅ 💃 🛠️ 🩺 & 🔫 👌 📇. diff --git a/docs/em/docs/advanced/openapi-callbacks.md b/docs/em/docs/advanced/openapi-callbacks.md deleted file mode 100644 index b0a8216681..0000000000 --- a/docs/em/docs/advanced/openapi-callbacks.md +++ /dev/null @@ -1,186 +0,0 @@ -# 🗄 ⏲ - -👆 💪 ✍ 🛠️ ⏮️ *➡ 🛠️* 👈 💪 ⏲ 📨 *🔢 🛠️* ✍ 👱 🙆 (🎲 🎏 👩‍💻 👈 🔜 *⚙️* 👆 🛠️). - -🛠️ 👈 🔨 🕐❔ 👆 🛠️ 📱 🤙 *🔢 🛠️* 📛 "⏲". ↩️ 🖥 👈 🔢 👩‍💻 ✍ 📨 📨 👆 🛠️ & ⤴️ 👆 🛠️ *🤙 🔙*, 📨 📨 *🔢 🛠️* (👈 🎲 ✍ 🎏 👩‍💻). - -👉 💼, 👆 💪 💚 📄 ❔ 👈 🔢 🛠️ *🔜* 👀 💖. ⚫️❔ *➡ 🛠️* ⚫️ 🔜 ✔️, ⚫️❔ 💪 ⚫️ 🔜 ⌛, ⚫️❔ 📨 ⚫️ 🔜 📨, ♒️. - -## 📱 ⏮️ ⏲ - -➡️ 👀 🌐 👉 ⏮️ 🖼. - -🌈 👆 🛠️ 📱 👈 ✔ 🏗 🧾. - -👉 🧾 🔜 ✔️ `id`, `title` (📦), `customer`, & `total`. - -👩‍💻 👆 🛠️ (🔢 👩‍💻) 🔜 ✍ 🧾 👆 🛠️ ⏮️ 🏤 📨. - -⤴️ 👆 🛠️ 🔜 (➡️ 🌈): - -* 📨 🧾 🕴 🔢 👩‍💻. -* 📈 💸. -* 📨 📨 🔙 🛠️ 👩‍💻 (🔢 👩‍💻). - * 👉 🔜 🔨 📨 🏤 📨 (⚪️➡️ *👆 🛠️*) *🔢 🛠️* 🚚 👈 🔢 👩‍💻 (👉 "⏲"). - -## 😐 **FastAPI** 📱 - -➡️ 🥇 👀 ❔ 😐 🛠️ 📱 🔜 👀 💖 ⏭ ❎ ⏲. - -⚫️ 🔜 ✔️ *➡ 🛠️* 👈 🔜 📨 `Invoice` 💪, & 🔢 🔢 `callback_url` 👈 🔜 🔌 📛 ⏲. - -👉 🍕 📶 😐, 🌅 📟 🎲 ⏪ 😰 👆: - -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[9:13,36:53] *} - -/// tip - -`callback_url` 🔢 🔢 ⚙️ Pydantic 📛 🆎. - -/// - -🕴 🆕 👜 `callbacks=messages_callback_router.routes` ❌ *➡ 🛠️ 👨‍🎨*. 👥 🔜 👀 ⚫️❔ 👈 ⏭. - -## 🔬 ⏲ - -☑ ⏲ 📟 🔜 🪀 🙇 🔛 👆 👍 🛠️ 📱. - -& ⚫️ 🔜 🎲 🪀 📚 ⚪️➡️ 1️⃣ 📱 ⏭. - -⚫️ 💪 1️⃣ ⚖️ 2️⃣ ⏸ 📟, 💖: - -```Python -callback_url = "https://example.com/api/v1/invoices/events/" -httpx.post(callback_url, json={"description": "Invoice paid", "paid": True}) -``` - -✋️ 🎲 🏆 ⚠ 🍕 ⏲ ⚒ 💭 👈 👆 🛠️ 👩‍💻 (🔢 👩‍💻) 🛠️ *🔢 🛠️* ☑, 🛄 💽 👈 *👆 🛠️* 🔜 📨 📨 💪 ⏲, ♒️. - -, ⚫️❔ 👥 🔜 ⏭ 🚮 📟 📄 ❔ 👈 *🔢 🛠️* 🔜 👀 💖 📨 ⏲ ⚪️➡️ *👆 🛠️*. - -👈 🧾 🔜 🎦 🆙 🦁 🎚 `/docs` 👆 🛠️, & ⚫️ 🔜 ➡️ 🔢 👩‍💻 💭 ❔ 🏗 *🔢 🛠️*. - -👉 🖼 🚫 🛠️ ⏲ ⚫️ (👈 💪 ⏸ 📟), 🕴 🧾 🍕. - -/// tip - -☑ ⏲ 🇺🇸🔍 📨. - -🕐❔ 🛠️ ⏲ 👆, 👆 💪 ⚙️ 🕳 💖 🇸🇲 ⚖️ 📨. - -/// - -## ✍ ⏲ 🧾 📟 - -👉 📟 🏆 🚫 🛠️ 👆 📱, 👥 🕴 💪 ⚫️ *📄* ❔ 👈 *🔢 🛠️* 🔜 👀 💖. - -✋️, 👆 ⏪ 💭 ❔ 💪 ✍ 🏧 🧾 🛠️ ⏮️ **FastAPI**. - -👥 🔜 ⚙️ 👈 🎏 💡 📄 ❔ *🔢 🛠️* 🔜 👀 💖... 🏗 *➡ 🛠️(Ⓜ)* 👈 🔢 🛠️ 🔜 🛠️ (🕐 👆 🛠️ 🔜 🤙). - -/// tip - -🕐❔ ✍ 📟 📄 ⏲, ⚫️ 💪 ⚠ 🌈 👈 👆 👈 *🔢 👩‍💻*. & 👈 👆 ⏳ 🛠️ *🔢 🛠️*, 🚫 *👆 🛠️*. - -🍕 🛠️ 👉 ☝ 🎑 ( *🔢 👩‍💻*) 💪 ℹ 👆 💭 💖 ⚫️ 🌅 ⭐ 🌐❔ 🚮 🔢, Pydantic 🏷 💪, 📨, ♒️. 👈 *🔢 🛠️*. - -/// - -### ✍ ⏲ `APIRouter` - -🥇 ✍ 🆕 `APIRouter` 👈 🔜 🔌 1️⃣ ⚖️ 🌅 ⏲. - -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[3,25] *} - -### ✍ ⏲ *➡ 🛠️* - -✍ ⏲ *➡ 🛠️* ⚙️ 🎏 `APIRouter` 👆 ✍ 🔛. - -⚫️ 🔜 👀 💖 😐 FastAPI *➡ 🛠️*: - -* ⚫️ 🔜 🎲 ✔️ 📄 💪 ⚫️ 🔜 📨, ✅ `body: InvoiceEvent`. -* & ⚫️ 💪 ✔️ 📄 📨 ⚫️ 🔜 📨, ✅ `response_model=InvoiceEventReceived`. - -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[16:18,21:22,28:32] *} - -📤 2️⃣ 👑 🔺 ⚪️➡️ 😐 *➡ 🛠️*: - -* ⚫️ 🚫 💪 ✔️ 🙆 ☑ 📟, ↩️ 👆 📱 🔜 🙅 🤙 👉 📟. ⚫️ 🕴 ⚙️ 📄 *🔢 🛠️*. , 🔢 💪 ✔️ `pass`. -* *➡* 💪 🔌 🗄 3️⃣ 🧬 (👀 🌖 🔛) 🌐❔ ⚫️ 💪 ⚙️ 🔢 ⏮️ 🔢 & 🍕 ⏮️ 📨 📨 *👆 🛠️*. - -### ⏲ ➡ 🧬 - -⏲ *➡* 💪 ✔️ 🗄 3️⃣ 🧬 👈 💪 🔌 🍕 ⏮️ 📨 📨 *👆 🛠️*. - -👉 💼, ⚫️ `str`: - -```Python -"{$callback_url}/invoices/{$request.body.id}" -``` - -, 🚥 👆 🛠️ 👩‍💻 (🔢 👩‍💻) 📨 📨 *👆 🛠️* : - -``` -https://yourapi.com/invoices/?callback_url=https://www.external.org/events -``` - -⏮️ 🎻 💪: - -```JSON -{ - "id": "2expen51ve", - "customer": "Mr. Richie Rich", - "total": "9999" -} -``` - -⤴️ *👆 🛠️* 🔜 🛠️ 🧾, & ☝ ⏪, 📨 ⏲ 📨 `callback_url` ( *🔢 🛠️*): - -``` -https://www.external.org/events/invoices/2expen51ve -``` - -⏮️ 🎻 💪 ⚗ 🕳 💖: - -```JSON -{ - "description": "Payment celebration", - "paid": true -} -``` - -& ⚫️ 🔜 ⌛ 📨 ⚪️➡️ 👈 *🔢 🛠️* ⏮️ 🎻 💪 💖: - -```JSON -{ - "ok": true -} -``` - -/// tip - -👀 ❔ ⏲ 📛 ⚙️ 🔌 📛 📨 🔢 🔢 `callback_url` (`https://www.external.org/events`) & 🧾 `id` ⚪️➡️ 🔘 🎻 💪 (`2expen51ve`). - -/// - -### 🚮 ⏲ 📻 - -👉 ☝ 👆 ✔️ *⏲ ➡ 🛠️(Ⓜ)* 💪 (1️⃣(Ⓜ) 👈 *🔢 👩‍💻* 🔜 🛠️ *🔢 🛠️*) ⏲ 📻 👆 ✍ 🔛. - -🔜 ⚙️ 🔢 `callbacks` *👆 🛠️ ➡ 🛠️ 👨‍🎨* 🚶‍♀️ 🔢 `.routes` (👈 🤙 `list` 🛣/*➡ 🛠️*) ⚪️➡️ 👈 ⏲ 📻: - -{* ../../docs_src/openapi_callbacks/tutorial001.py hl[35] *} - -/// tip - -👀 👈 👆 🚫 🚶‍♀️ 📻 ⚫️ (`invoices_callback_router`) `callback=`, ✋️ 🔢 `.routes`, `invoices_callback_router.routes`. - -/// - -### ✅ 🩺 - -🔜 👆 💪 ▶️ 👆 📱 ⏮️ Uvicorn & 🚶 http://127.0.0.1:8000/docs. - -👆 🔜 👀 👆 🩺 ✅ "⏲" 📄 👆 *➡ 🛠️* 👈 🎦 ❔ *🔢 🛠️* 🔜 👀 💖: - - diff --git a/docs/em/docs/advanced/path-operation-advanced-configuration.md b/docs/em/docs/advanced/path-operation-advanced-configuration.md deleted file mode 100644 index 9d9d5fa8da..0000000000 --- a/docs/em/docs/advanced/path-operation-advanced-configuration.md +++ /dev/null @@ -1,172 +0,0 @@ -# ➡ 🛠️ 🏧 📳 - -## 🗄 { - -/// warning - -🚥 👆 🚫 "🕴" 🗄, 👆 🎲 🚫 💪 👉. - -/// - -👆 💪 ⚒ 🗄 `operationId` ⚙️ 👆 *➡ 🛠️* ⏮️ 🔢 `operation_id`. - -👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 😍 🔠 🛠️. - -{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} - -### ⚙️ *➡ 🛠️ 🔢* 📛 { - -🚥 👆 💚 ⚙️ 👆 🔗' 🔢 📛 `operationId`Ⓜ, 👆 💪 🔁 🤭 🌐 👫 & 🔐 🔠 *➡ 🛠️* `operation_id` ⚙️ 👫 `APIRoute.name`. - -👆 🔜 ⚫️ ⏮️ ❎ 🌐 👆 *➡ 🛠️*. - -{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2,12:21,24] *} - -/// tip - -🚥 👆 ❎ 🤙 `app.openapi()`, 👆 🔜 ℹ `operationId`Ⓜ ⏭ 👈. - -/// - -/// warning - -🚥 👆 👉, 👆 ✔️ ⚒ 💭 🔠 1️⃣ 👆 *➡ 🛠️ 🔢* ✔️ 😍 📛. - -🚥 👫 🎏 🕹 (🐍 📁). - -/// - -## 🚫 ⚪️➡️ 🗄 - -🚫 *➡ 🛠️* ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚙️ 🔢 `include_in_schema` & ⚒ ⚫️ `False`: - -{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} - -## 🏧 📛 ⚪️➡️ #️⃣ - -👆 💪 📉 ⏸ ⚙️ ⚪️➡️ #️⃣ *➡ 🛠️ 🔢* 🗄. - -❎ `\f` (😖 "📨 🍼" 🦹) 🤕 **FastAPI** 🔁 🔢 ⚙️ 🗄 👉 ☝. - -⚫️ 🏆 🚫 🎦 🆙 🧾, ✋️ 🎏 🧰 (✅ 🐉) 🔜 💪 ⚙️ 🎂. - -{* ../../docs_src/path_operation_advanced_configuration/tutorial004.py hl[19:29] *} - -## 🌖 📨 - -👆 🎲 ✔️ 👀 ❔ 📣 `response_model` & `status_code` *➡ 🛠️*. - -👈 🔬 🗃 🔃 👑 📨 *➡ 🛠️*. - -👆 💪 📣 🌖 📨 ⏮️ 👫 🏷, 👔 📟, ♒️. - -📤 🎂 📃 📥 🧾 🔃 ⚫️, 👆 💪 ✍ ⚫️ [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}. - -## 🗄 ➕ - -🕐❔ 👆 📣 *➡ 🛠️* 👆 🈸, **FastAPI** 🔁 🏗 🔗 🗃 🔃 👈 *➡ 🛠️* 🔌 🗄 🔗. - -/// note | 📡 ℹ - -🗄 🔧 ⚫️ 🤙 🛠️ 🎚. - -/// - -⚫️ ✔️ 🌐 ℹ 🔃 *➡ 🛠️* & ⚙️ 🏗 🏧 🧾. - -⚫️ 🔌 `tags`, `parameters`, `requestBody`, `responses`, ♒️. - -👉 *➡ 🛠️*-🎯 🗄 🔗 🛎 🏗 🔁 **FastAPI**, ✋️ 👆 💪 ↔ ⚫️. - -/// tip - -👉 🔅 🎚 ↔ ☝. - -🚥 👆 🕴 💪 📣 🌖 📨, 🌅 🏪 🌌 ⚫️ ⏮️ [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}. - -/// - -👆 💪 ↔ 🗄 🔗 *➡ 🛠️* ⚙️ 🔢 `openapi_extra`. - -### 🗄 ↔ - -👉 `openapi_extra` 💪 👍, 🖼, 📣 [🗄 ↔](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): - -{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} - -🚥 👆 📂 🏧 🛠️ 🩺, 👆 ↔ 🔜 🎦 🆙 🔝 🎯 *➡ 🛠️*. - - - -& 🚥 👆 👀 📉 🗄 ( `/openapi.json` 👆 🛠️), 👆 🔜 👀 👆 ↔ 🍕 🎯 *➡ 🛠️* 💁‍♂️: - -```JSON hl_lines="22" -{ - "openapi": "3.0.2", - "info": { - "title": "FastAPI", - "version": "0.1.0" - }, - "paths": { - "/items/": { - "get": { - "summary": "Read Items", - "operationId": "read_items_items__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {} - } - } - } - }, - "x-aperture-labs-portal": "blue" - } - } - } -} -``` - -### 🛃 🗄 *➡ 🛠️* 🔗 - -📖 `openapi_extra` 🔜 🙇 🔗 ⏮️ 🔁 🏗 🗄 🔗 *➡ 🛠️*. - -, 👆 💪 🚮 🌖 💽 🔁 🏗 🔗. - -🖼, 👆 💪 💭 ✍ & ✔ 📨 ⏮️ 👆 👍 📟, 🍵 ⚙️ 🏧 ⚒ FastAPI ⏮️ Pydantic, ✋️ 👆 💪 💚 🔬 📨 🗄 🔗. - -👆 💪 👈 ⏮️ `openapi_extra`: - -{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[20:37,39:40] *} - -👉 🖼, 👥 🚫 📣 🙆 Pydantic 🏷. 👐, 📨 💪 🚫 🎻 🎻, ⚫️ ✍ 🔗 `bytes`, & 🔢 `magic_data_reader()` 🔜 🈚 🎻 ⚫️ 🌌. - -👐, 👥 💪 📣 📈 🔗 📨 💪. - -### 🛃 🗄 🎚 🆎 - -⚙️ 👉 🎏 🎱, 👆 💪 ⚙️ Pydantic 🏷 🔬 🎻 🔗 👈 ⤴️ 🔌 🛃 🗄 🔗 📄 *➡ 🛠️*. - -& 👆 💪 👉 🚥 💽 🆎 📨 🚫 🎻. - -🖼, 👉 🈸 👥 🚫 ⚙️ FastAPI 🛠️ 🛠️ ⚗ 🎻 🔗 ⚪️➡️ Pydantic 🏷 🚫 🏧 🔬 🎻. 👐, 👥 📣 📨 🎚 🆎 📁, 🚫 🎻: - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[17:22,24] *} - -👐, 👐 👥 🚫 ⚙️ 🔢 🛠️ 🛠️, 👥 ⚙️ Pydantic 🏷 ❎ 🏗 🎻 🔗 💽 👈 👥 💚 📨 📁. - -⤴️ 👥 ⚙️ 📨 🔗, & ⚗ 💪 `bytes`. 👉 ⛓ 👈 FastAPI 🏆 🚫 🔄 🎻 📨 🚀 🎻. - -& ⤴️ 👆 📟, 👥 🎻 👈 📁 🎚 🔗, & ⤴️ 👥 🔄 ⚙️ 🎏 Pydantic 🏷 ✔ 📁 🎚: - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007.py hl[26:33] *} - -/// tip - -📥 👥 🏤-⚙️ 🎏 Pydantic 🏷. - -✋️ 🎏 🌌, 👥 💪 ✔️ ✔ ⚫️ 🎏 🌌. - -/// diff --git a/docs/em/docs/advanced/response-change-status-code.md b/docs/em/docs/advanced/response-change-status-code.md deleted file mode 100644 index 4933484dd3..0000000000 --- a/docs/em/docs/advanced/response-change-status-code.md +++ /dev/null @@ -1,31 +0,0 @@ -# 📨 - 🔀 👔 📟 - -👆 🎲 ✍ ⏭ 👈 👆 💪 ⚒ 🔢 [📨 👔 📟](../tutorial/response-status-code.md){.internal-link target=_blank}. - -✋️ 💼 👆 💪 📨 🎏 👔 📟 🌘 🔢. - -## ⚙️ 💼 - -🖼, 🌈 👈 👆 💚 📨 🇺🇸🔍 👔 📟 "👌" `200` 🔢. - -✋️ 🚥 💽 🚫 🔀, 👆 💚 ✍ ⚫️, & 📨 🇺🇸🔍 👔 📟 "✍" `201`. - -✋️ 👆 💚 💪 ⛽ & 🗜 💽 👆 📨 ⏮️ `response_model`. - -📚 💼, 👆 💪 ⚙️ `Response` 🔢. - -## ⚙️ `Response` 🔢 - -👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢* (👆 💪 🍪 & 🎚). - -& ⤴️ 👆 💪 ⚒ `status_code` 👈 *🔀* 📨 🎚. - -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} - -& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). - -& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨. - -**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 👔 📟 (🍪 & 🎚), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`. - -👆 💪 📣 `Response` 🔢 🔗, & ⚒ 👔 📟 👫. ✋️ ✔️ 🤯 👈 🏁 1️⃣ ⚒ 🔜 🏆. diff --git a/docs/em/docs/advanced/response-cookies.md b/docs/em/docs/advanced/response-cookies.md deleted file mode 100644 index a6e37ad74d..0000000000 --- a/docs/em/docs/advanced/response-cookies.md +++ /dev/null @@ -1,51 +0,0 @@ -# 📨 🍪 - -## ⚙️ `Response` 🔢 - -👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢*. - -& ⤴️ 👆 💪 ⚒ 🍪 👈 *🔀* 📨 🎚. - -{* ../../docs_src/response_cookies/tutorial002.py hl[1,8:9] *} - -& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). - -& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨. - -**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 🍪 (🎚 & 👔 📟), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`. - -👆 💪 📣 `Response` 🔢 🔗, & ⚒ 🍪 (& 🎚) 👫. - -## 📨 `Response` 🔗 - -👆 💪 ✍ 🍪 🕐❔ 🛬 `Response` 🔗 👆 📟. - -👈, 👆 💪 ✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank}. - -⤴️ ⚒ 🍪 ⚫️, & ⤴️ 📨 ⚫️: - -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} - -/// tip - -✔️ 🤯 👈 🚥 👆 📨 📨 🔗 ↩️ ⚙️ `Response` 🔢, FastAPI 🔜 📨 ⚫️ 🔗. - -, 👆 🔜 ✔️ ⚒ 💭 👆 💽 ☑ 🆎. 🤶 Ⓜ. ⚫️ 🔗 ⏮️ 🎻, 🚥 👆 🛬 `JSONResponse`. - - & 👈 👆 🚫 📨 🙆 📊 👈 🔜 ✔️ ⛽ `response_model`. - -/// - -### 🌅 ℹ - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. - - & `Response` 💪 ⚙️ 🛎 ⚒ 🎚 & 🍪, **FastAPI** 🚚 ⚫️ `fastapi.Response`. - -/// - -👀 🌐 💪 🔢 & 🎛, ✅ 🧾 💃. diff --git a/docs/em/docs/advanced/response-directly.md b/docs/em/docs/advanced/response-directly.md deleted file mode 100644 index 29819a2055..0000000000 --- a/docs/em/docs/advanced/response-directly.md +++ /dev/null @@ -1,65 +0,0 @@ -# 📨 📨 🔗 - -🕐❔ 👆 ✍ **FastAPI** *➡ 🛠️* 👆 💪 🛎 📨 🙆 📊 ⚪️➡️ ⚫️: `dict`, `list`, Pydantic 🏷, 💽 🏷, ♒️. - -🔢, **FastAPI** 🔜 🔁 🗜 👈 📨 💲 🎻 ⚙️ `jsonable_encoder` 🔬 [🎻 🔗 🔢](../tutorial/encoder.md){.internal-link target=_blank}. - -⤴️, ⛅ 🎑, ⚫️ 🔜 🚮 👈 🎻-🔗 💽 (✅ `dict`) 🔘 `JSONResponse` 👈 🔜 ⚙️ 📨 📨 👩‍💻. - -✋️ 👆 💪 📨 `JSONResponse` 🔗 ⚪️➡️ 👆 *➡ 🛠️*. - -⚫️ 💪 ⚠, 🖼, 📨 🛃 🎚 ⚖️ 🍪. - -## 📨 `Response` - -👐, 👆 💪 📨 🙆 `Response` ⚖️ 🙆 🎧-🎓 ⚫️. - -/// tip - -`JSONResponse` ⚫️ 🎧-🎓 `Response`. - -/// - -& 🕐❔ 👆 📨 `Response`, **FastAPI** 🔜 🚶‍♀️ ⚫️ 🔗. - -⚫️ 🏆 🚫 🙆 💽 🛠️ ⏮️ Pydantic 🏷, ⚫️ 🏆 🚫 🗜 🎚 🙆 🆎, ♒️. - -👉 🤝 👆 📚 💪. 👆 💪 📨 🙆 📊 🆎, 🔐 🙆 💽 📄 ⚖️ 🔬, ♒️. - -## ⚙️ `jsonable_encoder` `Response` - -↩️ **FastAPI** 🚫 🙆 🔀 `Response` 👆 📨, 👆 ✔️ ⚒ 💭 ⚫️ 🎚 🔜 ⚫️. - -🖼, 👆 🚫🔜 🚮 Pydantic 🏷 `JSONResponse` 🍵 🥇 🏭 ⚫️ `dict` ⏮️ 🌐 📊 🆎 (💖 `datetime`, `UUID`, ♒️) 🗜 🎻-🔗 🆎. - -📚 💼, 👆 💪 ⚙️ `jsonable_encoder` 🗜 👆 📊 ⏭ 🚶‍♀️ ⚫️ 📨: - -{* ../../docs_src/response_directly/tutorial001.py hl[6:7,21:22] *} - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import JSONResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. - -/// - -## 🛬 🛃 `Response` - -🖼 🔛 🎦 🌐 🍕 👆 💪, ✋️ ⚫️ 🚫 📶 ⚠, 👆 💪 ✔️ 📨 `item` 🔗, & **FastAPI** 🔜 🚮 ⚫️ `JSONResponse` 👆, 🏭 ⚫️ `dict`, ♒️. 🌐 👈 🔢. - -🔜, ➡️ 👀 ❔ 👆 💪 ⚙️ 👈 📨 🛃 📨. - -➡️ 💬 👈 👆 💚 📨 📂 📨. - -👆 💪 🚮 👆 📂 🎚 🎻, 🚮 ⚫️ `Response`, & 📨 ⚫️: - -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} - -## 🗒 - -🕐❔ 👆 📨 `Response` 🔗 🚮 📊 🚫 ✔, 🗜 (🎻), 🚫 📄 🔁. - -✋️ 👆 💪 📄 ⚫️ 🔬 [🌖 📨 🗄](additional-responses.md){.internal-link target=_blank}. - -👆 💪 👀 ⏪ 📄 ❔ ⚙️/📣 👉 🛃 `Response`Ⓜ ⏪ ✔️ 🏧 💽 🛠️, 🧾, ♒️. diff --git a/docs/em/docs/advanced/response-headers.md b/docs/em/docs/advanced/response-headers.md deleted file mode 100644 index c255380d67..0000000000 --- a/docs/em/docs/advanced/response-headers.md +++ /dev/null @@ -1,41 +0,0 @@ -# 📨 🎚 - -## ⚙️ `Response` 🔢 - -👆 💪 📣 🔢 🆎 `Response` 👆 *➡ 🛠️ 🔢* (👆 💪 🍪). - -& ⤴️ 👆 💪 ⚒ 🎚 👈 *🔀* 📨 🎚. - -{* ../../docs_src/response_headers/tutorial002.py hl[1,7:8] *} - -& ⤴️ 👆 💪 📨 🙆 🎚 👆 💪, 👆 🛎 🔜 ( `dict`, 💽 🏷, ♒️). - -& 🚥 👆 📣 `response_model`, ⚫️ 🔜 ⚙️ ⛽ & 🗜 🎚 👆 📨. - -**FastAPI** 🔜 ⚙️ 👈 *🔀* 📨 ⚗ 🎚 (🍪 & 👔 📟), & 🔜 🚮 👫 🏁 📨 👈 🔌 💲 👆 📨, ⛽ 🙆 `response_model`. - -👆 💪 📣 `Response` 🔢 🔗, & ⚒ 🎚 (& 🍪) 👫. - -## 📨 `Response` 🔗 - -👆 💪 🚮 🎚 🕐❔ 👆 📨 `Response` 🔗. - -✍ 📨 🔬 [📨 📨 🔗](response-directly.md){.internal-link target=_blank} & 🚶‍♀️ 🎚 🌖 🔢: - -{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import Response` ⚖️ `from starlette.responses import JSONResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. - - & `Response` 💪 ⚙️ 🛎 ⚒ 🎚 & 🍪, **FastAPI** 🚚 ⚫️ `fastapi.Response`. - -/// - -## 🛃 🎚 - -✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡. - -✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 (✍ 🌅 [⚜ (✖️-🇨🇳 ℹ 🤝)](../tutorial/cors.md){.internal-link target=_blank}), ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. diff --git a/docs/em/docs/advanced/security/http-basic-auth.md b/docs/em/docs/advanced/security/http-basic-auth.md deleted file mode 100644 index 73736f3b35..0000000000 --- a/docs/em/docs/advanced/security/http-basic-auth.md +++ /dev/null @@ -1,107 +0,0 @@ -# 🇺🇸🔍 🔰 🔐 - -🙅 💼, 👆 💪 ⚙️ 🇺🇸🔍 🔰 🔐. - -🇺🇸🔍 🔰 🔐, 🈸 ⌛ 🎚 👈 🔌 🆔 & 🔐. - -🚥 ⚫️ 🚫 📨 ⚫️, ⚫️ 📨 🇺🇸🔍 4️⃣0️⃣1️⃣ "⛔" ❌. - -& 📨 🎚 `WWW-Authenticate` ⏮️ 💲 `Basic`, & 📦 `realm` 🔢. - -👈 💬 🖥 🎦 🛠️ 📋 🆔 & 🔐. - -⤴️, 🕐❔ 👆 🆎 👈 🆔 & 🔐, 🖥 📨 👫 🎚 🔁. - -## 🙅 🇺🇸🔍 🔰 🔐 - -* 🗄 `HTTPBasic` & `HTTPBasicCredentials`. -* ✍ "`security` ⚖" ⚙️ `HTTPBasic`. -* ⚙️ 👈 `security` ⏮️ 🔗 👆 *➡ 🛠️*. -* ⚫️ 📨 🎚 🆎 `HTTPBasicCredentials`: - * ⚫️ 🔌 `username` & `password` 📨. - -{* ../../docs_src/security/tutorial006.py hl[2,6,10] *} - -🕐❔ 👆 🔄 📂 📛 🥇 🕰 (⚖️ 🖊 "🛠️" 🔼 🩺) 🖥 🔜 💭 👆 👆 🆔 & 🔐: - - - -## ✅ 🆔 - -📥 🌅 🏁 🖼. - -⚙️ 🔗 ✅ 🚥 🆔 & 🔐 ☑. - -👉, ⚙️ 🐍 🐩 🕹 `secrets` ✅ 🆔 & 🔐. - -`secrets.compare_digest()` 💪 ✊ `bytes` ⚖️ `str` 👈 🕴 🔌 🔠 🦹 (🕐 🇪🇸), 👉 ⛓ ⚫️ 🚫🔜 👷 ⏮️ 🦹 💖 `á`, `Sebastián`. - -🍵 👈, 👥 🥇 🗜 `username` & `password` `bytes` 🔢 👫 ⏮️ 🔠-8️⃣. - -⤴️ 👥 💪 ⚙️ `secrets.compare_digest()` 🚚 👈 `credentials.username` `"stanleyjobson"`, & 👈 `credentials.password` `"swordfish"`. - -{* ../../docs_src/security/tutorial007.py hl[1,11:21] *} - -👉 🔜 🎏: - -```Python -if not (credentials.username == "stanleyjobson") or not (credentials.password == "swordfish"): - # Return some error - ... -``` - -✋️ ⚙️ `secrets.compare_digest()` ⚫️ 🔜 🔐 🛡 🆎 👊 🤙 "🕰 👊". - -### ⏲ 👊 - -✋️ ⚫️❔ "⏲ 👊"❓ - -➡️ 🌈 👊 🔄 💭 🆔 & 🔐. - -& 👫 📨 📨 ⏮️ 🆔 `johndoe` & 🔐 `love123`. - -⤴️ 🐍 📟 👆 🈸 🔜 🌓 🕳 💖: - -```Python -if "johndoe" == "stanleyjobson" and "love123" == "swordfish": - ... -``` - -✋️ ▶️️ 🙍 🐍 🔬 🥇 `j` `johndoe` 🥇 `s` `stanleyjobson`, ⚫️ 🔜 📨 `False`, ↩️ ⚫️ ⏪ 💭 👈 📚 2️⃣ 🎻 🚫 🎏, 💭 👈 "📤 🙅‍♂ 💪 🗑 🌅 📊 ⚖ 🎂 🔤". & 👆 🈸 🔜 💬 "❌ 👩‍💻 ⚖️ 🔐". - -✋️ ⤴️ 👊 🔄 ⏮️ 🆔 `stanleyjobsox` & 🔐 `love123`. - -& 👆 🈸 📟 🔨 🕳 💖: - -```Python -if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish": - ... -``` - -🐍 🔜 ✔️ 🔬 🎂 `stanleyjobso` 👯‍♂️ `stanleyjobsox` & `stanleyjobson` ⏭ 🤔 👈 👯‍♂️ 🎻 🚫 🎏. ⚫️ 🔜 ✊ ➕ ⏲ 📨 🔙 "❌ 👩‍💻 ⚖️ 🔐". - -#### 🕰 ❔ ℹ 👊 - -👈 ☝, 👀 👈 💽 ✊ ⏲ 📏 📨 "❌ 👩‍💻 ⚖️ 🔐" 📨, 👊 🔜 💭 👈 👫 🤚 _🕳_ ▶️️, ▶️ 🔤 ▶️️. - -& ⤴️ 👫 💪 🔄 🔄 🤔 👈 ⚫️ 🎲 🕳 🌖 🎏 `stanleyjobsox` 🌘 `johndoe`. - -#### "🕴" 👊 - -↗️, 👊 🔜 🚫 🔄 🌐 👉 ✋, 👫 🔜 ✍ 📋 ⚫️, 🎲 ⏮️ 💯 ⚖️ 💯 💯 📍 🥈. & 🔜 🤚 1️⃣ ➕ ☑ 🔤 🕰. - -✋️ 🔨 👈, ⏲ ⚖️ 📆 👊 🔜 ✔️ 💭 ☑ 🆔 & 🔐, ⏮️ "ℹ" 👆 🈸, ⚙️ 🕰 ✊ ❔. - -#### 🔧 ⚫️ ⏮️ `secrets.compare_digest()` - -✋️ 👆 📟 👥 🤙 ⚙️ `secrets.compare_digest()`. - -📏, ⚫️ 🔜 ✊ 🎏 🕰 🔬 `stanleyjobsox` `stanleyjobson` 🌘 ⚫️ ✊ 🔬 `johndoe` `stanleyjobson`. & 🎏 🔐. - -👈 🌌, ⚙️ `secrets.compare_digest()` 👆 🈸 📟, ⚫️ 🔜 🔒 🛡 👉 🎂 ↔ 💂‍♂ 👊. - -### 📨 ❌ - -⏮️ 🔍 👈 🎓 ❌, 📨 `HTTPException` ⏮️ 👔 📟 4️⃣0️⃣1️⃣ (🎏 📨 🕐❔ 🙅‍♂ 🎓 🚚) & 🚮 🎚 `WWW-Authenticate` ⚒ 🖥 🎦 💳 📋 🔄: - -{* ../../docs_src/security/tutorial007.py hl[23:27] *} diff --git a/docs/em/docs/advanced/security/index.md b/docs/em/docs/advanced/security/index.md deleted file mode 100644 index 5cdc475053..0000000000 --- a/docs/em/docs/advanced/security/index.md +++ /dev/null @@ -1,19 +0,0 @@ -# 🏧 💂‍♂ - -## 🌖 ⚒ - -📤 ➕ ⚒ 🍵 💂‍♂ ↖️ ⚪️➡️ 🕐 📔 [🔰 - 👩‍💻 🦮: 💂‍♂](../../tutorial/security/index.md){.internal-link target=_blank}. - -/// tip - -⏭ 📄 **🚫 🎯 "🏧"**. - - & ⚫️ 💪 👈 👆 ⚙️ 💼, ⚗ 1️⃣ 👫. - -/// - -## ✍ 🔰 🥇 - -⏭ 📄 🤔 👆 ⏪ ✍ 👑 [🔰 - 👩‍💻 🦮: 💂‍♂](../../tutorial/security/index.md){.internal-link target=_blank}. - -👫 🌐 ⚓️ 🔛 🎏 🔧, ✋️ ✔ ➕ 🛠️. diff --git a/docs/em/docs/advanced/security/oauth2-scopes.md b/docs/em/docs/advanced/security/oauth2-scopes.md deleted file mode 100644 index 9e3bc00587..0000000000 --- a/docs/em/docs/advanced/security/oauth2-scopes.md +++ /dev/null @@ -1,274 +0,0 @@ -# Oauth2️⃣ ↔ - -👆 💪 ⚙️ Oauth2️⃣ ↔ 🔗 ⏮️ **FastAPI**, 👫 🛠️ 👷 💎. - -👉 🔜 ✔ 👆 ✔️ 🌖 👌-🧽 ✔ ⚙️, 📄 Oauth2️⃣ 🐩, 🛠️ 🔘 👆 🗄 🈸 (& 🛠️ 🩺). - -Oauth2️⃣ ⏮️ ↔ 🛠️ ⚙️ 📚 🦏 🤝 🐕‍🦺, 💖 👱📔, 🇺🇸🔍, 📂, 🤸‍♂, 👱📔, ♒️. 👫 ⚙️ ⚫️ 🚚 🎯 ✔ 👩‍💻 & 🈸. - -🔠 🕰 👆 "🕹 ⏮️" 👱📔, 🇺🇸🔍, 📂, 🤸‍♂, 👱📔, 👈 🈸 ⚙️ Oauth2️⃣ ⏮️ ↔. - -👉 📄 👆 🔜 👀 ❔ 🛠️ 🤝 & ✔ ⏮️ 🎏 Oauth2️⃣ ⏮️ ↔ 👆 **FastAPI** 🈸. - -/// warning - -👉 🌅 ⚖️ 🌘 🏧 📄. 🚥 👆 ▶️, 👆 💪 🚶 ⚫️. - -👆 🚫 🎯 💪 Oauth2️⃣ ↔, & 👆 💪 🍵 🤝 & ✔ 👐 👆 💚. - -✋️ Oauth2️⃣ ⏮️ ↔ 💪 🎆 🛠️ 🔘 👆 🛠️ (⏮️ 🗄) & 👆 🛠️ 🩺. - -👐, 👆 🛠️ 📚 ↔, ⚖️ 🙆 🎏 💂‍♂/✔ 📄, 👐 👆 💪, 👆 📟. - -📚 💼, Oauth2️⃣ ⏮️ ↔ 💪 👹. - -✋️ 🚥 👆 💭 👆 💪 ⚫️, ⚖️ 👆 😟, 🚧 👂. - -/// - -## Oauth2️⃣ ↔ & 🗄 - -Oauth2️⃣ 🔧 🔬 "↔" 📇 🎻 🎏 🚀. - -🎚 🔠 👉 🎻 💪 ✔️ 🙆 📁, ✋️ 🔜 🚫 🔌 🚀. - -👫 ↔ 🎨 "✔". - -🗄 (✅ 🛠️ 🩺), 👆 💪 🔬 "💂‍♂ ⚖". - -🕐❔ 1️⃣ 👫 💂‍♂ ⚖ ⚙️ Oauth2️⃣, 👆 💪 📣 & ⚙️ ↔. - -🔠 "↔" 🎻 (🍵 🚀). - -👫 🛎 ⚙️ 📣 🎯 💂‍♂ ✔, 🖼: - -* `users:read` ⚖️ `users:write` ⚠ 🖼. -* `instagram_basic` ⚙️ 👱📔 / 👱📔. -* `https://www.googleapis.com/auth/drive` ⚙️ 🇺🇸🔍. - -/// info - -Oauth2️⃣ "↔" 🎻 👈 📣 🎯 ✔ ✔. - -⚫️ 🚫 🤔 🚥 ⚫️ ✔️ 🎏 🦹 💖 `:` ⚖️ 🚥 ⚫️ 📛. - -👈 ℹ 🛠️ 🎯. - -Oauth2️⃣ 👫 🎻. - -/// - -## 🌐 🎑 - -🥇, ➡️ 🔜 👀 🍕 👈 🔀 ⚪️➡️ 🖼 👑 **🔰 - 👩‍💻 🦮** [Oauth2️⃣ ⏮️ 🔐 (& 🔁), 📨 ⏮️ 🥙 🤝](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. 🔜 ⚙️ Oauth2️⃣ ↔: - -{* ../../docs_src/security/tutorial005.py hl[2,4,8,12,46,64,105,107:115,121:125,129:135,140,156] *} - -🔜 ➡️ 📄 👈 🔀 🔁 🔁. - -## Oauth2️⃣ 💂‍♂ ⚖ - -🥇 🔀 👈 🔜 👥 📣 Oauth2️⃣ 💂‍♂ ⚖ ⏮️ 2️⃣ 💪 ↔, `me` & `items`. - -`scopes` 🔢 📨 `dict` ⏮️ 🔠 ↔ 🔑 & 📛 💲: - -{* ../../docs_src/security/tutorial005.py hl[62:65] *} - -↩️ 👥 🔜 📣 📚 ↔, 👫 🔜 🎦 🆙 🛠️ 🩺 🕐❔ 👆 🕹-/✔. - -& 👆 🔜 💪 🖊 ❔ ↔ 👆 💚 🤝 🔐: `me` & `items`. - -👉 🎏 🛠️ ⚙️ 🕐❔ 👆 🤝 ✔ ⏪ 🚨 ⏮️ 👱📔, 🇺🇸🔍, 📂, ♒️: - - - -## 🥙 🤝 ⏮️ ↔ - -🔜, 🔀 🤝 *➡ 🛠️* 📨 ↔ 📨. - -👥 ⚙️ 🎏 `OAuth2PasswordRequestForm`. ⚫️ 🔌 🏠 `scopes` ⏮️ `list` `str`, ⏮️ 🔠 ↔ ⚫️ 📨 📨. - -& 👥 📨 ↔ 🍕 🥙 🤝. - -/// danger - -🦁, 📥 👥 ❎ ↔ 📨 🔗 🤝. - -✋️ 👆 🈸, 💂‍♂, 👆 🔜 ⚒ 💭 👆 🕴 🚮 ↔ 👈 👩‍💻 🤙 💪 ✔️, ⚖️ 🕐 👆 ✔️ 🔁. - -/// - -{* ../../docs_src/security/tutorial005.py hl[156] *} - -## 📣 ↔ *➡ 🛠️* & 🔗 - -🔜 👥 📣 👈 *➡ 🛠️* `/users/me/items/` 🚚 ↔ `items`. - -👉, 👥 🗄 & ⚙️ `Security` ⚪️➡️ `fastapi`. - -👆 💪 ⚙️ `Security` 📣 🔗 (💖 `Depends`), ✋️ `Security` 📨 🔢 `scopes` ⏮️ 📇 ↔ (🎻). - -👉 💼, 👥 🚶‍♀️ 🔗 🔢 `get_current_active_user` `Security` (🎏 🌌 👥 🔜 ⏮️ `Depends`). - -✋️ 👥 🚶‍♀️ `list` ↔, 👉 💼 ⏮️ 1️⃣ ↔: `items` (⚫️ 💪 ✔️ 🌅). - -& 🔗 🔢 `get_current_active_user` 💪 📣 🎧-🔗, 🚫 🕴 ⏮️ `Depends` ✋️ ⏮️ `Security`. 📣 🚮 👍 🎧-🔗 🔢 (`get_current_user`), & 🌖 ↔ 📄. - -👉 💼, ⚫️ 🚚 ↔ `me` (⚫️ 💪 🚚 🌅 🌘 1️⃣ ↔). - -/// note - -👆 🚫 🎯 💪 🚮 🎏 ↔ 🎏 🥉. - -👥 🔨 ⚫️ 📥 🎦 ❔ **FastAPI** 🍵 ↔ 📣 🎏 🎚. - -/// - -{* ../../docs_src/security/tutorial005.py hl[4,140,169] *} - -/// info | 📡 ℹ - -`Security` 🤙 🏿 `Depends`, & ⚫️ ✔️ 1️⃣ ➕ 🔢 👈 👥 🔜 👀 ⏪. - -✋️ ⚙️ `Security` ↩️ `Depends`, **FastAPI** 🔜 💭 👈 ⚫️ 💪 📣 💂‍♂ ↔, ⚙️ 👫 🔘, & 📄 🛠️ ⏮️ 🗄. - -✋️ 🕐❔ 👆 🗄 `Query`, `Path`, `Depends`, `Security` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. - -/// - -## ⚙️ `SecurityScopes` - -🔜 ℹ 🔗 `get_current_user`. - -👉 1️⃣ ⚙️ 🔗 🔛. - -📥 👥 ⚙️ 🎏 Oauth2️⃣ ⚖ 👥 ✍ ⏭, 📣 ⚫️ 🔗: `oauth2_scheme`. - -↩️ 👉 🔗 🔢 🚫 ✔️ 🙆 ↔ 📄 ⚫️, 👥 💪 ⚙️ `Depends` ⏮️ `oauth2_scheme`, 👥 🚫 ✔️ ⚙️ `Security` 🕐❔ 👥 🚫 💪 ✔ 💂‍♂ ↔. - -👥 📣 🎁 🔢 🆎 `SecurityScopes`, 🗄 ⚪️➡️ `fastapi.security`. - -👉 `SecurityScopes` 🎓 🎏 `Request` (`Request` ⚙️ 🤚 📨 🎚 🔗). - -{* ../../docs_src/security/tutorial005.py hl[8,105] *} - -## ⚙️ `scopes` - -🔢 `security_scopes` 🔜 🆎 `SecurityScopes`. - -⚫️ 🔜 ✔️ 🏠 `scopes` ⏮️ 📇 ⚗ 🌐 ↔ ✔ ⚫️ & 🌐 🔗 👈 ⚙️ 👉 🎧-🔗. 👈 ⛓, 🌐 "⚓️"... 👉 💪 🔊 😨, ⚫️ 🔬 🔄 ⏪ 🔛. - -`security_scopes` 🎚 (🎓 `SecurityScopes`) 🚚 `scope_str` 🔢 ⏮️ 👁 🎻, 🔌 👈 ↔ 👽 🚀 (👥 🔜 ⚙️ ⚫️). - -👥 ✍ `HTTPException` 👈 👥 💪 🏤-⚙️ (`raise`) ⏪ 📚 ☝. - -👉 ⚠, 👥 🔌 ↔ 🚚 (🚥 🙆) 🎻 👽 🚀 (⚙️ `scope_str`). 👥 🚮 👈 🎻 ⚗ ↔ `WWW-Authenticate` 🎚 (👉 🍕 🔌). - -{* ../../docs_src/security/tutorial005.py hl[105,107:115] *} - -## ✔ `username` & 💽 💠 - -👥 ✔ 👈 👥 🤚 `username`, & ⚗ ↔. - -& ⤴️ 👥 ✔ 👈 📊 ⏮️ Pydantic 🏷 (✊ `ValidationError` ⚠), & 🚥 👥 🤚 ❌ 👂 🥙 🤝 ⚖️ ⚖ 📊 ⏮️ Pydantic, 👥 🤚 `HTTPException` 👥 ✍ ⏭. - -👈, 👥 ℹ Pydantic 🏷 `TokenData` ⏮️ 🆕 🏠 `scopes`. - -⚖ 📊 ⏮️ Pydantic 👥 💪 ⚒ 💭 👈 👥 ✔️, 🖼, ⚫️❔ `list` `str` ⏮️ ↔ & `str` ⏮️ `username`. - -↩️, 🖼, `dict`, ⚖️ 🕳 🙆, ⚫️ 💪 💔 🈸 ☝ ⏪, ⚒ ⚫️ 💂‍♂ ⚠. - -👥 ✔ 👈 👥 ✔️ 👩‍💻 ⏮️ 👈 🆔, & 🚥 🚫, 👥 🤚 👈 🎏 ⚠ 👥 ✍ ⏭. - -{* ../../docs_src/security/tutorial005.py hl[46,116:128] *} - -## ✔ `scopes` - -👥 🔜 ✔ 👈 🌐 ↔ ✔, 👉 🔗 & 🌐 ⚓️ (🔌 *➡ 🛠️*), 🔌 ↔ 🚚 🤝 📨, ⏪ 🤚 `HTTPException`. - -👉, 👥 ⚙️ `security_scopes.scopes`, 👈 🔌 `list` ⏮️ 🌐 👫 ↔ `str`. - -{* ../../docs_src/security/tutorial005.py hl[129:135] *} - -## 🔗 🌲 & ↔ - -➡️ 📄 🔄 👉 🔗 🌲 & ↔. - -`get_current_active_user` 🔗 ✔️ 🎧-🔗 🔛 `get_current_user`, ↔ `"me"` 📣 `get_current_active_user` 🔜 🔌 📇 ✔ ↔ `security_scopes.scopes` 🚶‍♀️ `get_current_user`. - -*➡ 🛠️* ⚫️ 📣 ↔, `"items"`, 👉 🔜 📇 `security_scopes.scopes` 🚶‍♀️ `get_current_user`. - -📥 ❔ 🔗 🔗 & ↔ 👀 💖: - -* *➡ 🛠️* `read_own_items` ✔️: - * ✔ ↔ `["items"]` ⏮️ 🔗: - * `get_current_active_user`: - * 🔗 🔢 `get_current_active_user` ✔️: - * ✔ ↔ `["me"]` ⏮️ 🔗: - * `get_current_user`: - * 🔗 🔢 `get_current_user` ✔️: - * 🙅‍♂ ↔ ✔ ⚫️. - * 🔗 ⚙️ `oauth2_scheme`. - * `security_scopes` 🔢 🆎 `SecurityScopes`: - * 👉 `security_scopes` 🔢 ✔️ 🏠 `scopes` ⏮️ `list` ⚗ 🌐 👫 ↔ 📣 🔛,: - * `security_scopes.scopes` 🔜 🔌 `["me", "items"]` *➡ 🛠️* `read_own_items`. - * `security_scopes.scopes` 🔜 🔌 `["me"]` *➡ 🛠️* `read_users_me`, ↩️ ⚫️ 📣 🔗 `get_current_active_user`. - * `security_scopes.scopes` 🔜 🔌 `[]` (🕳) *➡ 🛠️* `read_system_status`, ↩️ ⚫️ 🚫 📣 🙆 `Security` ⏮️ `scopes`, & 🚮 🔗, `get_current_user`, 🚫 📣 🙆 `scope` 👯‍♂️. - -/// tip - -⚠ & "🎱" 👜 📥 👈 `get_current_user` 🔜 ✔️ 🎏 📇 `scopes` ✅ 🔠 *➡ 🛠️*. - -🌐 ⚓️ 🔛 `scopes` 📣 🔠 *➡ 🛠️* & 🔠 🔗 🔗 🌲 👈 🎯 *➡ 🛠️*. - -/// - -## 🌖 ℹ 🔃 `SecurityScopes` - -👆 💪 ⚙️ `SecurityScopes` 🙆 ☝, & 💗 🥉, ⚫️ 🚫 ✔️ "🌱" 🔗. - -⚫️ 🔜 🕧 ✔️ 💂‍♂ ↔ 📣 ⏮️ `Security` 🔗 & 🌐 ⚓️ **👈 🎯** *➡ 🛠️* & **👈 🎯** 🔗 🌲. - -↩️ `SecurityScopes` 🔜 ✔️ 🌐 ↔ 📣 ⚓️, 👆 💪 ⚙️ ⚫️ ✔ 👈 🤝 ✔️ 🚚 ↔ 🇨🇫 🔗 🔢, & ⤴️ 📣 🎏 ↔ 📄 🎏 *➡ 🛠️*. - -👫 🔜 ✅ ➡ 🔠 *➡ 🛠️*. - -## ✅ ⚫️ - -🚥 👆 📂 🛠️ 🩺, 👆 💪 🔓 & ✔ ❔ ↔ 👆 💚 ✔. - - - -🚥 👆 🚫 🖊 🙆 ↔, 👆 🔜 "🔓", ✋️ 🕐❔ 👆 🔄 🔐 `/users/me/` ⚖️ `/users/me/items/` 👆 🔜 🤚 ❌ 💬 👈 👆 🚫 ✔️ 🥃 ✔. 👆 🔜 💪 🔐 `/status/`. - -& 🚥 👆 🖊 ↔ `me` ✋️ 🚫 ↔ `items`, 👆 🔜 💪 🔐 `/users/me/` ✋️ 🚫 `/users/me/items/`. - -👈 ⚫️❔ 🔜 🔨 🥉 🥳 🈸 👈 🔄 🔐 1️⃣ 👫 *➡ 🛠️* ⏮️ 🤝 🚚 👩‍💻, ⚓️ 🔛 ❔ 📚 ✔ 👩‍💻 🤝 🈸. - -## 🔃 🥉 🥳 🛠️ - -👉 🖼 👥 ⚙️ Oauth2️⃣ "🔐" 💧. - -👉 ☑ 🕐❔ 👥 🚨 👆 👍 🈸, 🎲 ⏮️ 👆 👍 🕸. - -↩️ 👥 💪 💙 ⚫️ 📨 `username` & `password`, 👥 🎛 ⚫️. - -✋️ 🚥 👆 🏗 Oauth2️⃣ 🈸 👈 🎏 🔜 🔗 (➡, 🚥 👆 🏗 🤝 🐕‍🦺 🌓 👱📔, 🇺🇸🔍, 📂, ♒️.) 👆 🔜 ⚙️ 1️⃣ 🎏 💧. - -🌅 ⚠ 🔑 💧. - -🏆 🔐 📟 💧, ✋️ 🌖 🏗 🛠️ ⚫️ 🚚 🌅 📶. ⚫️ 🌅 🏗, 📚 🐕‍🦺 🔚 🆙 ✔ 🔑 💧. - -/// note - -⚫️ ⚠ 👈 🔠 🤝 🐕‍🦺 📛 👫 💧 🎏 🌌, ⚒ ⚫️ 🍕 👫 🏷. - -✋️ 🔚, 👫 🛠️ 🎏 Oauth2️⃣ 🐩. - -/// - -**FastAPI** 🔌 🚙 🌐 👫 Oauth2️⃣ 🤝 💧 `fastapi.security.oauth2`. - -## `Security` 👨‍🎨 `dependencies` - -🎏 🌌 👆 💪 🔬 `list` `Depends` 👨‍🎨 `dependencies` 🔢 (🔬 [🔗 ➡ 🛠️ 👨‍🎨](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), 👆 💪 ⚙️ `Security` ⏮️ `scopes` 📤. diff --git a/docs/em/docs/advanced/settings.md b/docs/em/docs/advanced/settings.md deleted file mode 100644 index 7fdd0d68a6..0000000000 --- a/docs/em/docs/advanced/settings.md +++ /dev/null @@ -1,396 +0,0 @@ -# ⚒ & 🌐 🔢 - -📚 💼 👆 🈸 💪 💪 🔢 ⚒ ⚖️ 📳, 🖼 ㊙ 🔑, 💽 🎓, 🎓 📧 🐕‍🦺, ♒️. - -🏆 👫 ⚒ 🔢 (💪 🔀), 💖 💽 📛. & 📚 💪 🚿, 💖 ㊙. - -👉 🤔 ⚫️ ⚠ 🚚 👫 🌐 🔢 👈 ✍ 🈸. - -## 🌐 🔢 - -/// tip - -🚥 👆 ⏪ 💭 ⚫️❔ "🌐 🔢" & ❔ ⚙️ 👫, 💭 🆓 🚶 ⏭ 📄 🔛. - -/// - -🌐 🔢 (💭 "🇨🇻 {") 🔢 👈 🖖 🏞 🐍 📟, 🏃‍♂ ⚙️, & 💪 ✍ 👆 🐍 📟 (⚖️ 🎏 📋 👍). - -👆 💪 ✍ & ⚙️ 🌐 🔢 🐚, 🍵 💆‍♂ 🐍: - -//// tab | 💾, 🇸🇻, 🚪 🎉 - -
- -```console -// You could create an env var MY_NAME with -$ export MY_NAME="Wade Wilson" - -// Then you could use it with other programs, like -$ echo "Hello $MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -//// tab | 🚪 📋 - -
- -```console -// Create an env var MY_NAME -$ $Env:MY_NAME = "Wade Wilson" - -// Use it with other programs, like -$ echo "Hello $Env:MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -### ✍ 🇨🇻 {🐍 - -👆 💪 ✍ 🌐 🔢 🏞 🐍, 📶 (⚖️ ⏮️ 🙆 🎏 👩‍🔬), & ⤴️ ✍ 👫 🐍. - -🖼 👆 💪 ✔️ 📁 `main.py` ⏮️: - -```Python hl_lines="3" -import os - -name = os.getenv("MY_NAME", "World") -print(f"Hello {name} from Python") -``` - -/// tip - -🥈 ❌ `os.getenv()` 🔢 💲 📨. - -🚥 🚫 🚚, ⚫️ `None` 🔢, 📥 👥 🚚 `"World"` 🔢 💲 ⚙️. - -/// - -⤴️ 👆 💪 🤙 👈 🐍 📋: - -
- -```console -// Here we don't set the env var yet -$ python main.py - -// As we didn't set the env var, we get the default value - -Hello World from Python - -// But if we create an environment variable first -$ export MY_NAME="Wade Wilson" - -// And then call the program again -$ python main.py - -// Now it can read the environment variable - -Hello Wade Wilson from Python -``` - -
- -🌐 🔢 💪 ⚒ 🏞 📟, ✋️ 💪 ✍ 📟, & 🚫 ✔️ 🏪 (💕 `git`) ⏮️ 🎂 📁, ⚫️ ⚠ ⚙️ 👫 📳 ⚖️ ⚒. - -👆 💪 ✍ 🌐 🔢 🕴 🎯 📋 👼, 👈 🕴 💪 👈 📋, & 🕴 🚮 📐. - -👈, ✍ ⚫️ ▶️️ ⏭ 📋 ⚫️, 🔛 🎏 ⏸: - -
- -```console -// Create an env var MY_NAME in line for this program call -$ MY_NAME="Wade Wilson" python main.py - -// Now it can read the environment variable - -Hello Wade Wilson from Python - -// The env var no longer exists afterwards -$ python main.py - -Hello World from Python -``` - -
- -/// tip - -👆 💪 ✍ 🌅 🔃 ⚫️ 1️⃣2️⃣-⚖ 📱: 📁. - -/// - -### 🆎 & 🔬 - -👫 🌐 🔢 💪 🕴 🍵 ✍ 🎻, 👫 🔢 🐍 & ✔️ 🔗 ⏮️ 🎏 📋 & 🎂 ⚙️ (& ⏮️ 🎏 🏃‍♂ ⚙️, 💾, 🚪, 🇸🇻). - -👈 ⛓ 👈 🙆 💲 ✍ 🐍 ⚪️➡️ 🌐 🔢 🔜 `str`, & 🙆 🛠️ 🎏 🆎 ⚖️ 🔬 ✔️ 🔨 📟. - -## Pydantic `Settings` - -👐, Pydantic 🚚 👑 🚙 🍵 👫 ⚒ 👟 ⚪️➡️ 🌐 🔢 ⏮️ Pydantic: ⚒ 🧾. - -### ✍ `Settings` 🎚 - -🗄 `BaseSettings` ⚪️➡️ Pydantic & ✍ 🎧-🎓, 📶 🌅 💖 ⏮️ Pydantic 🏷. - -🎏 🌌 ⏮️ Pydantic 🏷, 👆 📣 🎓 🔢 ⏮️ 🆎 ✍, & 🎲 🔢 💲. - -👆 💪 ⚙️ 🌐 🎏 🔬 ⚒ & 🧰 👆 ⚙️ Pydantic 🏷, 💖 🎏 📊 🆎 & 🌖 🔬 ⏮️ `Field()`. - -{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} - -/// tip - -🚥 👆 💚 🕳 ⏩ 📁 & 📋, 🚫 ⚙️ 👉 🖼, ⚙️ 🏁 1️⃣ 🔛. - -/// - -⤴️, 🕐❔ 👆 ✍ 👐 👈 `Settings` 🎓 (👉 💼, `settings` 🎚), Pydantic 🔜 ✍ 🌐 🔢 💼-😛 🌌,, ↖-💼 🔢 `APP_NAME` 🔜 ✍ 🔢 `app_name`. - -⏭ ⚫️ 🔜 🗜 & ✔ 💽. , 🕐❔ 👆 ⚙️ 👈 `settings` 🎚, 👆 🔜 ✔️ 📊 🆎 👆 📣 (✅ `items_per_user` 🔜 `int`). - -### ⚙️ `settings` - -⤴️ 👆 💪 ⚙️ 🆕 `settings` 🎚 👆 🈸: - -{* ../../docs_src/settings/tutorial001.py hl[18:20] *} - -### 🏃 💽 - -⏭, 👆 🔜 🏃 💽 🚶‍♀️ 📳 🌐 🔢, 🖼 👆 💪 ⚒ `ADMIN_EMAIL` & `APP_NAME` ⏮️: - -
- -```console -$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uvicorn main:app - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -/// tip - -⚒ 💗 🇨🇻 {👁 📋 🎏 👫 ⏮️ 🚀, & 🚮 👫 🌐 ⏭ 📋. - -/// - -& ⤴️ `admin_email` ⚒ 🔜 ⚒ `"deadpool@example.com"`. - -`app_name` 🔜 `"ChimichangApp"`. - -& `items_per_user` 🔜 🚧 🚮 🔢 💲 `50`. - -## ⚒ ➕1️⃣ 🕹 - -👆 💪 🚮 👈 ⚒ ➕1️⃣ 🕹 📁 👆 👀 [🦏 🈸 - 💗 📁](../tutorial/bigger-applications.md){.internal-link target=_blank}. - -🖼, 👆 💪 ✔️ 📁 `config.py` ⏮️: - -{* ../../docs_src/settings/app01/config.py *} - -& ⤴️ ⚙️ ⚫️ 📁 `main.py`: - -{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} - -/// tip - -👆 🔜 💪 📁 `__init__.py` 👆 👀 🔛 [🦏 🈸 - 💗 📁](../tutorial/bigger-applications.md){.internal-link target=_blank}. - -/// - -## ⚒ 🔗 - -🍾 ⚫️ 5️⃣📆 ⚠ 🚚 ⚒ ⚪️➡️ 🔗, ↩️ ✔️ 🌐 🎚 ⏮️ `settings` 👈 ⚙️ 🌐. - -👉 💪 ✴️ ⚠ ⏮️ 🔬, ⚫️ 📶 ⏩ 🔐 🔗 ⏮️ 👆 👍 🛃 ⚒. - -### 📁 📁 - -👟 ⚪️➡️ ⏮️ 🖼, 👆 `config.py` 📁 💪 👀 💖: - -{* ../../docs_src/settings/app02/config.py hl[10] *} - -👀 👈 🔜 👥 🚫 ✍ 🔢 👐 `settings = Settings()`. - -### 👑 📱 📁 - -🔜 👥 ✍ 🔗 👈 📨 🆕 `config.Settings()`. - -{* ../../docs_src/settings/app02/main.py hl[5,11:12] *} - -/// tip - -👥 🔜 🔬 `@lru_cache` 🍖. - -🔜 👆 💪 🤔 `get_settings()` 😐 🔢. - -/// - -& ⤴️ 👥 💪 🚚 ⚫️ ⚪️➡️ *➡ 🛠️ 🔢* 🔗 & ⚙️ ⚫️ 🙆 👥 💪 ⚫️. - -{* ../../docs_src/settings/app02/main.py hl[16,18:20] *} - -### ⚒ & 🔬 - -⤴️ ⚫️ 🔜 📶 ⏩ 🚚 🎏 ⚒ 🎚 ⏮️ 🔬 🏗 🔗 🔐 `get_settings`: - -{* ../../docs_src/settings/app02/test_main.py hl[9:10,13,21] *} - -🔗 🔐 👥 ⚒ 🆕 💲 `admin_email` 🕐❔ 🏗 🆕 `Settings` 🎚, & ⤴️ 👥 📨 👈 🆕 🎚. - -⤴️ 👥 💪 💯 👈 ⚫️ ⚙️. - -## 👂 `.env` 📁 - -🚥 👆 ✔️ 📚 ⚒ 👈 🎲 🔀 📚, 🎲 🎏 🌐, ⚫️ 5️⃣📆 ⚠ 🚮 👫 🔛 📁 & ⤴️ ✍ 👫 ⚪️➡️ ⚫️ 🚥 👫 🌐 🔢. - -👉 💡 ⚠ 🥃 👈 ⚫️ ✔️ 📛, 👫 🌐 🔢 🛎 🥉 📁 `.env`, & 📁 🤙 "🇨🇻". - -/// tip - -📁 ▶️ ⏮️ ❣ (`.`) 🕵‍♂ 📁 🖥-💖 ⚙️, 💖 💾 & 🇸🇻. - -✋️ 🇨🇻 📁 🚫 🤙 ✔️ ✔️ 👈 ☑ 📁. - -/// - -Pydantic ✔️ 🐕‍🦺 👂 ⚪️➡️ 👉 🆎 📁 ⚙️ 🔢 🗃. 👆 💪 ✍ 🌖 Pydantic ⚒: 🇨🇻 (.🇨🇻) 🐕‍🦺. - -/// tip - -👉 👷, 👆 💪 `pip install python-dotenv`. - -/// - -### `.env` 📁 - -👆 💪 ✔️ `.env` 📁 ⏮️: - -```bash -ADMIN_EMAIL="deadpool@example.com" -APP_NAME="ChimichangApp" -``` - -### ✍ ⚒ ⚪️➡️ `.env` - -& ⤴️ ℹ 👆 `config.py` ⏮️: - -{* ../../docs_src/settings/app03/config.py hl[9:10] *} - -📥 👥 ✍ 🎓 `Config` 🔘 👆 Pydantic `Settings` 🎓, & ⚒ `env_file` 📁 ⏮️ 🇨🇻 📁 👥 💚 ⚙️. - -/// tip - -`Config` 🎓 ⚙️ Pydantic 📳. 👆 💪 ✍ 🌖 Pydantic 🏷 📁 - -/// - -### 🏗 `Settings` 🕴 🕐 ⏮️ `lru_cache` - -👂 📁 ⚪️➡️ 💾 🛎 ⚠ (🐌) 🛠️, 👆 🎲 💚 ⚫️ 🕴 🕐 & ⤴️ 🏤-⚙️ 🎏 ⚒ 🎚, ↩️ 👂 ⚫️ 🔠 📨. - -✋️ 🔠 🕰 👥: - -```Python -Settings() -``` - -🆕 `Settings` 🎚 🔜 ✍, & 🏗 ⚫️ 🔜 ✍ `.env` 📁 🔄. - -🚥 🔗 🔢 💖: - -```Python -def get_settings(): - return Settings() -``` - -👥 🔜 ✍ 👈 🎚 🔠 📨, & 👥 🔜 👂 `.env` 📁 🔠 📨. 👶 👶 - -✋️ 👥 ⚙️ `@lru_cache` 👨‍🎨 🔛 🔝, `Settings` 🎚 🔜 ✍ 🕴 🕐, 🥇 🕰 ⚫️ 🤙. 👶 👶 - -{* ../../docs_src/settings/app03/main.py hl[1,10] *} - -⤴️ 🙆 🏁 🤙 `get_settings()` 🔗 ⏭ 📨, ↩️ 🛠️ 🔗 📟 `get_settings()` & 🏗 🆕 `Settings` 🎚, ⚫️ 🔜 📨 🎏 🎚 👈 📨 🔛 🥇 🤙, 🔄 & 🔄. - -#### `lru_cache` 📡 ℹ - -`@lru_cache` 🔀 🔢 ⚫️ 🎀 📨 🎏 💲 👈 📨 🥇 🕰, ↩️ 💻 ⚫️ 🔄, 🛠️ 📟 🔢 🔠 🕰. - -, 🔢 🔛 ⚫️ 🔜 🛠️ 🕐 🔠 🌀 ❌. & ⤴️ 💲 📨 🔠 👈 🌀 ❌ 🔜 ⚙️ 🔄 & 🔄 🕐❔ 🔢 🤙 ⏮️ ⚫️❔ 🎏 🌀 ❌. - -🖼, 🚥 👆 ✔️ 🔢: - -```Python -@lru_cache -def say_hi(name: str, salutation: str = "Ms."): - return f"Hello {salutation} {name}" -``` - -👆 📋 💪 🛠️ 💖 👉: - -```mermaid -sequenceDiagram - -participant code as Code -participant function as say_hi() -participant execute as Execute function - - rect rgba(0, 255, 0, .1) - code ->> function: say_hi(name="Camila") - function ->> execute: execute function code - execute ->> code: return the result - end - - rect rgba(0, 255, 255, .1) - code ->> function: say_hi(name="Camila") - function ->> code: return stored result - end - - rect rgba(0, 255, 0, .1) - code ->> function: say_hi(name="Rick") - function ->> execute: execute function code - execute ->> code: return the result - end - - rect rgba(0, 255, 0, .1) - code ->> function: say_hi(name="Rick", salutation="Mr.") - function ->> execute: execute function code - execute ->> code: return the result - end - - rect rgba(0, 255, 255, .1) - code ->> function: say_hi(name="Rick") - function ->> code: return stored result - end - - rect rgba(0, 255, 255, .1) - code ->> function: say_hi(name="Camila") - function ->> code: return stored result - end -``` - -💼 👆 🔗 `get_settings()`, 🔢 🚫 ✊ 🙆 ❌, ⚫️ 🕧 📨 🎏 💲. - -👈 🌌, ⚫️ 🎭 🌖 🚥 ⚫️ 🌐 🔢. ✋️ ⚫️ ⚙️ 🔗 🔢, ⤴️ 👥 💪 🔐 ⚫️ 💪 🔬. - -`@lru_cache` 🍕 `functools` ❔ 🍕 🐍 🐩 🗃, 👆 💪 ✍ 🌅 🔃 ⚫️ 🐍 🩺 `@lru_cache`. - -## 🌃 - -👆 💪 ⚙️ Pydantic ⚒ 🍵 ⚒ ⚖️ 📳 👆 🈸, ⏮️ 🌐 🏋️ Pydantic 🏷. - -* ⚙️ 🔗 👆 💪 📉 🔬. -* 👆 💪 ⚙️ `.env` 📁 ⏮️ ⚫️. -* ⚙️ `@lru_cache` ➡️ 👆 ❎ 👂 🇨🇻 📁 🔄 & 🔄 🔠 📨, ⏪ 🤝 👆 🔐 ⚫️ ⏮️ 🔬. diff --git a/docs/em/docs/advanced/sub-applications.md b/docs/em/docs/advanced/sub-applications.md deleted file mode 100644 index 7a802cd77f..0000000000 --- a/docs/em/docs/advanced/sub-applications.md +++ /dev/null @@ -1,67 +0,0 @@ -# 🎧 🈸 - 🗻 - -🚥 👆 💪 ✔️ 2️⃣ 🔬 FastAPI 🈸, ⏮️ 👫 👍 🔬 🗄 & 👫 👍 🩺 ⚜, 👆 💪 ✔️ 👑 📱 & "🗻" 1️⃣ (⚖️ 🌅) 🎧-🈸(Ⓜ). - -## 🗜 **FastAPI** 🈸 - -"🗜" ⛓ ❎ 🍕 "🔬" 🈸 🎯 ➡, 👈 ⤴️ ✊ 💅 🚚 🌐 🔽 👈 ➡, ⏮️ _➡ 🛠️_ 📣 👈 🎧-🈸. - -### 🔝-🎚 🈸 - -🥇, ✍ 👑, 🔝-🎚, **FastAPI** 🈸, & 🚮 *➡ 🛠️*: - -{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *} - -### 🎧-🈸 - -⤴️, ✍ 👆 🎧-🈸, & 🚮 *➡ 🛠️*. - -👉 🎧-🈸 ➕1️⃣ 🐩 FastAPI 🈸, ✋️ 👉 1️⃣ 👈 🔜 "🗻": - -{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *} - -### 🗻 🎧-🈸 - -👆 🔝-🎚 🈸, `app`, 🗻 🎧-🈸, `subapi`. - -👉 💼, ⚫️ 🔜 📌 ➡ `/subapi`: - -{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *} - -### ✅ 🏧 🛠️ 🩺 - -🔜, 🏃 `uvicorn` ⏮️ 👑 📱, 🚥 👆 📁 `main.py`, ⚫️ 🔜: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -& 📂 🩺 http://127.0.0.1:8000/docs. - -👆 🔜 👀 🏧 🛠️ 🩺 👑 📱, 🔌 🕴 🚮 👍 _➡ 🛠️_: - - - -& ⤴️, 📂 🩺 🎧-🈸, http://127.0.0.1:8000/subapi/docs. - -👆 🔜 👀 🏧 🛠️ 🩺 🎧-🈸, ✅ 🕴 🚮 👍 _➡ 🛠️_, 🌐 🔽 ☑ 🎧-➡ 🔡 `/subapi`: - - - -🚥 👆 🔄 🔗 ⏮️ 🙆 2️⃣ 👩‍💻 🔢, 👫 🔜 👷 ☑, ↩️ 🖥 🔜 💪 💬 🔠 🎯 📱 ⚖️ 🎧-📱. - -### 📡 ℹ: `root_path` - -🕐❔ 👆 🗻 🎧-🈸 🔬 🔛, FastAPI 🔜 ✊ 💅 🔗 🗻 ➡ 🎧-🈸 ⚙️ 🛠️ ⚪️➡️ 🔫 🔧 🤙 `root_path`. - -👈 🌌, 🎧-🈸 🔜 💭 ⚙️ 👈 ➡ 🔡 🩺 🎚. - -& 🎧-🈸 💪 ✔️ 🚮 👍 📌 🎧-🈸 & 🌐 🔜 👷 ☑, ↩️ FastAPI 🍵 🌐 👉 `root_path`Ⓜ 🔁. - -👆 🔜 💡 🌅 🔃 `root_path` & ❔ ⚙️ ⚫️ 🎯 📄 🔃 [⛅ 🗳](behind-a-proxy.md){.internal-link target=_blank}. diff --git a/docs/em/docs/advanced/templates.md b/docs/em/docs/advanced/templates.md deleted file mode 100644 index 2e8f562280..0000000000 --- a/docs/em/docs/advanced/templates.md +++ /dev/null @@ -1,84 +0,0 @@ -# 📄 - -👆 💪 ⚙️ 🙆 📄 🚒 👆 💚 ⏮️ **FastAPI**. - -⚠ ⚒ Jinja2️⃣, 🎏 1️⃣ ⚙️ 🏺 & 🎏 🧰. - -📤 🚙 🔗 ⚫️ 💪 👈 👆 💪 ⚙️ 🔗 👆 **FastAPI** 🈸 (🚚 💃). - -## ❎ 🔗 - -❎ `jinja2`: - -
- -```console -$ pip install jinja2 - ----> 100% -``` - -
- -## ⚙️ `Jinja2Templates` - -* 🗄 `Jinja2Templates`. -* ✍ `templates` 🎚 👈 👆 💪 🏤-⚙️ ⏪. -* 📣 `Request` 🔢 *➡ 🛠️* 👈 🔜 📨 📄. -* ⚙️ `templates` 👆 ✍ ✍ & 📨 `TemplateResponse`, 🚶‍♀️ `request` 1️⃣ 🔑-💲 👫 Jinja2️⃣ "🔑". - -{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} - -/// note - -👀 👈 👆 ✔️ 🚶‍♀️ `request` 🍕 🔑-💲 👫 🔑 Jinja2️⃣. , 👆 ✔️ 📣 ⚫️ 👆 *➡ 🛠️*. - -/// - -/// tip - -📣 `response_class=HTMLResponse` 🩺 🎚 🔜 💪 💭 👈 📨 🔜 🕸. - -/// - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.templating import Jinja2Templates`. - -**FastAPI** 🚚 🎏 `starlette.templating` `fastapi.templating` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `Request` & `StaticFiles`. - -/// - -## ✍ 📄 - -⤴️ 👆 💪 ✍ 📄 `templates/item.html` ⏮️: - -```jinja hl_lines="7" -{!../../docs_src/templates/templates/item.html!} -``` - -⚫️ 🔜 🎦 `id` ✊ ⚪️➡️ "🔑" `dict` 👆 🚶‍♀️: - -```Python -{"request": request, "id": id} -``` - -## 📄 & 🎻 📁 - -& 👆 💪 ⚙️ `url_for()` 🔘 📄, & ⚙️ ⚫️, 🖼, ⏮️ `StaticFiles` 👆 📌. - -```jinja hl_lines="4" -{!../../docs_src/templates/templates/item.html!} -``` - -👉 🖼, ⚫️ 🔜 🔗 🎚 📁 `static/styles.css` ⏮️: - -```CSS hl_lines="4" -{!../../docs_src/templates/static/styles.css!} -``` - -& ↩️ 👆 ⚙️ `StaticFiles`, 👈 🎚 📁 🔜 🍦 🔁 👆 **FastAPI** 🈸 📛 `/static/styles.css`. - -## 🌅 ℹ - -🌅 ℹ, 🔌 ❔ 💯 📄, ✅ 💃 🩺 🔛 📄. diff --git a/docs/em/docs/advanced/testing-dependencies.md b/docs/em/docs/advanced/testing-dependencies.md deleted file mode 100644 index b2b4b480d0..0000000000 --- a/docs/em/docs/advanced/testing-dependencies.md +++ /dev/null @@ -1,53 +0,0 @@ -# 🔬 🔗 ⏮️ 🔐 - -## 🔑 🔗 ⏮️ 🔬 - -📤 😐 🌐❔ 👆 💪 💚 🔐 🔗 ⏮️ 🔬. - -👆 🚫 💚 ⏮️ 🔗 🏃 (🚫 🙆 🎧-🔗 ⚫️ 💪 ✔️). - -↩️, 👆 💚 🚚 🎏 🔗 👈 🔜 ⚙️ 🕴 ⏮️ 💯 (🎲 🕴 🎯 💯), & 🔜 🚚 💲 👈 💪 ⚙️ 🌐❔ 💲 ⏮️ 🔗 ⚙️. - -### ⚙️ 💼: 🔢 🐕‍🦺 - -🖼 💪 👈 👆 ✔️ 🔢 🤝 🐕‍🦺 👈 👆 💪 🤙. - -👆 📨 ⚫️ 🤝 & ⚫️ 📨 🔓 👩‍💻. - -👉 🐕‍🦺 5️⃣📆 🔌 👆 📍 📨, & 🤙 ⚫️ 💪 ✊ ➕ 🕰 🌘 🚥 👆 ✔️ 🔧 🎁 👩‍💻 💯. - -👆 🎲 💚 💯 🔢 🐕‍🦺 🕐, ✋️ 🚫 🎯 🤙 ⚫️ 🔠 💯 👈 🏃. - -👉 💼, 👆 💪 🔐 🔗 👈 🤙 👈 🐕‍🦺, & ⚙️ 🛃 🔗 👈 📨 🎁 👩‍💻, 🕴 👆 💯. - -### ⚙️ `app.dependency_overrides` 🔢 - -👫 💼, 👆 **FastAPI** 🈸 ✔️ 🔢 `app.dependency_overrides`, ⚫️ 🙅 `dict`. - -🔐 🔗 🔬, 👆 🚮 🔑 ⏮️ 🔗 (🔢), & 💲, 👆 🔗 🔐 (➕1️⃣ 🔢). - -& ⤴️ **FastAPI** 🔜 🤙 👈 🔐 ↩️ ⏮️ 🔗. - -{* ../../docs_src/dependency_testing/tutorial001.py hl[28:29,32] *} - -/// tip - -👆 💪 ⚒ 🔗 🔐 🔗 ⚙️ 🙆 👆 **FastAPI** 🈸. - -⏮️ 🔗 💪 ⚙️ *➡ 🛠️ 🔢*, *➡ 🛠️ 👨‍🎨* (🕐❔ 👆 🚫 ⚙️ 📨 💲), `.include_router()` 🤙, ♒️. - -FastAPI 🔜 💪 🔐 ⚫️. - -/// - -⤴️ 👆 💪 ⏲ 👆 🔐 (❎ 👫) ⚒ `app.dependency_overrides` 🛁 `dict`: - -```Python -app.dependency_overrides = {} -``` - -/// tip - -🚥 👆 💚 🔐 🔗 🕴 ⏮️ 💯, 👆 💪 ⚒ 🔐 ▶️ 💯 (🔘 💯 🔢) & ⏲ ⚫️ 🔚 (🔚 💯 🔢). - -/// diff --git a/docs/em/docs/advanced/testing-events.md b/docs/em/docs/advanced/testing-events.md deleted file mode 100644 index f62e9e0694..0000000000 --- a/docs/em/docs/advanced/testing-events.md +++ /dev/null @@ -1,5 +0,0 @@ -# 🔬 🎉: 🕴 - 🤫 - -🕐❔ 👆 💪 👆 🎉 🐕‍🦺 (`startup` & `shutdown`) 🏃 👆 💯, 👆 💪 ⚙️ `TestClient` ⏮️ `with` 📄: - -{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} diff --git a/docs/em/docs/advanced/testing-websockets.md b/docs/em/docs/advanced/testing-websockets.md deleted file mode 100644 index 96aa8b765d..0000000000 --- a/docs/em/docs/advanced/testing-websockets.md +++ /dev/null @@ -1,13 +0,0 @@ -# 🔬 *️⃣ - -👆 💪 ⚙️ 🎏 `TestClient` 💯*️⃣. - -👉, 👆 ⚙️ `TestClient` `with` 📄, 🔗*️⃣: - -{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} - -/// note - -🌅 ℹ, ✅ 💃 🧾 🔬 *️⃣ . - -/// diff --git a/docs/em/docs/advanced/using-request-directly.md b/docs/em/docs/advanced/using-request-directly.md deleted file mode 100644 index 238557e5e0..0000000000 --- a/docs/em/docs/advanced/using-request-directly.md +++ /dev/null @@ -1,56 +0,0 @@ -# ⚙️ 📨 🔗 - -🆙 🔜, 👆 ✔️ 📣 🍕 📨 👈 👆 💪 ⏮️ 👫 🆎. - -✊ 📊 ⚪️➡️: - -* ➡ 🔢. -* 🎚. -* 🍪. -* ♒️. - -& 🔨, **FastAPI** ⚖ 👈 💽, 🏭 ⚫️ & 🏭 🧾 👆 🛠️ 🔁. - -✋️ 📤 ⚠ 🌐❔ 👆 💪 💪 🔐 `Request` 🎚 🔗. - -## ℹ 🔃 `Request` 🎚 - -**FastAPI** 🤙 **💃** 🔘, ⏮️ 🧽 📚 🧰 🔛 🔝, 👆 💪 ⚙️ 💃 `Request` 🎚 🔗 🕐❔ 👆 💪. - -⚫️ 🔜 ⛓ 👈 🚥 👆 🤚 📊 ⚪️➡️ `Request` 🎚 🔗 (🖼, ✍ 💪) ⚫️ 🏆 🚫 ✔, 🗜 ⚖️ 📄 (⏮️ 🗄, 🏧 🛠️ 👩‍💻 🔢) FastAPI. - -👐 🙆 🎏 🔢 📣 🛎 (🖼, 💪 ⏮️ Pydantic 🏷) 🔜 ✔, 🗜, ✍, ♒️. - -✋️ 📤 🎯 💼 🌐❔ ⚫️ ⚠ 🤚 `Request` 🎚. - -## ⚙️ `Request` 🎚 🔗 - -➡️ 🌈 👆 💚 🤚 👩‍💻 📢 📢/🦠 🔘 👆 *➡ 🛠️ 🔢*. - -👈 👆 💪 🔐 📨 🔗. - -{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} - -📣 *➡ 🛠️ 🔢* 🔢 ⏮️ 🆎 ➖ `Request` **FastAPI** 🔜 💭 🚶‍♀️ `Request` 👈 🔢. - -/// tip - -🗒 👈 👉 💼, 👥 📣 ➡ 🔢 ⤴️ 📨 🔢. - -, ➡ 🔢 🔜 ⚗, ✔, 🗜 ✔ 🆎 & ✍ ⏮️ 🗄. - -🎏 🌌, 👆 💪 📣 🙆 🎏 🔢 🛎, & ➡, 🤚 `Request` 💁‍♂️. - -/// - -## `Request` 🧾 - -👆 💪 ✍ 🌅 ℹ 🔃 `Request` 🎚 🛂 💃 🧾 🕸. - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.requests import Request`. - -**FastAPI** 🚚 ⚫️ 🔗 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. - -/// diff --git a/docs/em/docs/advanced/websockets.md b/docs/em/docs/advanced/websockets.md deleted file mode 100644 index a097778c7a..0000000000 --- a/docs/em/docs/advanced/websockets.md +++ /dev/null @@ -1,186 +0,0 @@ -# *️⃣ - -👆 💪 ⚙️ *️⃣ ⏮️ **FastAPI**. - -## ❎ `WebSockets` - -🥇 👆 💪 ❎ `WebSockets`: - -
- -```console -$ pip install websockets - ----> 100% -``` - -
- -## *️⃣ 👩‍💻 - -### 🏭 - -👆 🏭 ⚙️, 👆 🎲 ✔️ 🕸 ✍ ⏮️ 🏛 🛠️ 💖 😥, Vue.js ⚖️ 📐. - -& 🔗 ⚙️ *️⃣ ⏮️ 👆 👩‍💻 👆 🔜 🎲 ⚙️ 👆 🕸 🚙. - -⚖️ 👆 💪 ✔️ 🇦🇸 📱 🈸 👈 🔗 ⏮️ 👆 *️⃣ 👩‍💻 🔗, 🇦🇸 📟. - -⚖️ 👆 5️⃣📆 ✔️ 🙆 🎏 🌌 🔗 ⏮️ *️⃣ 🔗. - ---- - -✋️ 👉 🖼, 👥 🔜 ⚙️ 📶 🙅 🕸 📄 ⏮️ 🕸, 🌐 🔘 📏 🎻. - -👉, ↗️, 🚫 ⚖ & 👆 🚫🔜 ⚙️ ⚫️ 🏭. - -🏭 👆 🔜 ✔️ 1️⃣ 🎛 🔛. - -✋️ ⚫️ 🙅 🌌 🎯 🔛 💽-🚄 *️⃣ & ✔️ 👷 🖼: - -{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} - -## ✍ `websocket` - -👆 **FastAPI** 🈸, ✍ `websocket`: - -{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.websockets import WebSocket`. - -**FastAPI** 🚚 🎏 `WebSocket` 🔗 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. - -/// - -## ⌛ 📧 & 📨 📧 - -👆 *️⃣ 🛣 👆 💪 `await` 📧 & 📨 📧. - -{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} - -👆 💪 📨 & 📨 💱, ✍, & 🎻 💽. - -## 🔄 ⚫️ - -🚥 👆 📁 📛 `main.py`, 🏃 👆 🈸 ⏮️: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -📂 👆 🖥 http://127.0.0.1:8000. - -👆 🔜 👀 🙅 📃 💖: - - - -👆 💪 🆎 📧 🔢 📦, & 📨 👫: - - - -& 👆 **FastAPI** 🈸 ⏮️ *️⃣ 🔜 📨 🔙: - - - -👆 💪 📨 (& 📨) 📚 📧: - - - -& 🌐 👫 🔜 ⚙️ 🎏 *️⃣ 🔗. - -## ⚙️ `Depends` & 🎏 - -*️⃣ 🔗 👆 💪 🗄 ⚪️➡️ `fastapi` & ⚙️: - -* `Depends` -* `Security` -* `Cookie` -* `Header` -* `Path` -* `Query` - -👫 👷 🎏 🌌 🎏 FastAPI 🔗/*➡ 🛠️*: - -{* ../../docs_src/websockets/tutorial002.py hl[66:77,76:91] *} - -/// info - -👉 *️⃣ ⚫️ 🚫 🤙 ⚒ 🔑 🤚 `HTTPException`, ↩️ 👥 🤚 `WebSocketException`. - -👆 💪 ⚙️ 📪 📟 ⚪️➡️ ☑ 📟 🔬 🔧. - -/// - -### 🔄 *️⃣ ⏮️ 🔗 - -🚥 👆 📁 📛 `main.py`, 🏃 👆 🈸 ⏮️: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -📂 👆 🖥 http://127.0.0.1:8000. - -📤 👆 💪 ⚒: - -* "🏬 🆔", ⚙️ ➡. -* "🤝" ⚙️ 🔢 🔢. - -/// tip - -👀 👈 🔢 `token` 🔜 🍵 🔗. - -/// - -⏮️ 👈 👆 💪 🔗 *️⃣ & ⤴️ 📨 & 📨 📧: - - - -## 🚚 🔀 & 💗 👩‍💻 - -🕐❔ *️⃣ 🔗 📪, `await websocket.receive_text()` 🔜 🤚 `WebSocketDisconnect` ⚠, ❔ 👆 💪 ⤴️ ✊ & 🍵 💖 👉 🖼. - -{* ../../docs_src/websockets/tutorial003.py hl[81:83] *} - -🔄 ⚫️ 👅: - -* 📂 📱 ⏮️ 📚 🖥 📑. -* ✍ 📧 ⚪️➡️ 👫. -* ⤴️ 🔐 1️⃣ 📑. - -👈 🔜 🤚 `WebSocketDisconnect` ⚠, & 🌐 🎏 👩‍💻 🔜 📨 📧 💖: - -``` -Client #1596980209979 left the chat -``` - -/// tip - -📱 🔛 ⭐ & 🙅 🖼 🎦 ❔ 🍵 & 📻 📧 📚 *️⃣ 🔗. - -✋️ ✔️ 🤯 👈, 🌐 🍵 💾, 👁 📇, ⚫️ 🔜 🕴 👷 ⏪ 🛠️ 🏃, & 🔜 🕴 👷 ⏮️ 👁 🛠️. - -🚥 👆 💪 🕳 ⏩ 🛠️ ⏮️ FastAPI ✋️ 👈 🌖 🏋️, 🐕‍🦺 ✳, ✳ ⚖️ 🎏, ✅ 🗜/📻. - -/// - -## 🌅 ℹ - -💡 🌅 🔃 🎛, ✅ 💃 🧾: - -* `WebSocket` 🎓. -* 🎓-⚓️ *️⃣ 🚚. diff --git a/docs/em/docs/advanced/wsgi.md b/docs/em/docs/advanced/wsgi.md deleted file mode 100644 index d923347d5a..0000000000 --- a/docs/em/docs/advanced/wsgi.md +++ /dev/null @@ -1,35 +0,0 @@ -# ✅ 🇨🇻 - 🏺, ✳, 🎏 - -👆 💪 🗻 🇨🇻 🈸 👆 👀 ⏮️ [🎧 🈸 - 🗻](sub-applications.md){.internal-link target=_blank}, [⛅ 🗳](behind-a-proxy.md){.internal-link target=_blank}. - -👈, 👆 💪 ⚙️ `WSGIMiddleware` & ⚙️ ⚫️ 🎁 👆 🇨🇻 🈸, 🖼, 🏺, ✳, ♒️. - -## ⚙️ `WSGIMiddleware` - -👆 💪 🗄 `WSGIMiddleware`. - -⤴️ 🎁 🇨🇻 (✅ 🏺) 📱 ⏮️ 🛠️. - -& ⤴️ 🗻 👈 🔽 ➡. - -{* ../../docs_src/wsgi/tutorial001.py hl[2:3,22] *} - -## ✅ ⚫️ - -🔜, 🔠 📨 🔽 ➡ `/v1/` 🔜 🍵 🏺 🈸. - -& 🎂 🔜 🍵 **FastAPI**. - -🚥 👆 🏃 ⚫️ ⏮️ Uvicorn & 🚶 http://localhost:8000/v1/ 👆 🔜 👀 📨 ⚪️➡️ 🏺: - -```txt -Hello, World from Flask! -``` - -& 🚥 👆 🚶 http://localhost:8000/v2 👆 🔜 👀 📨 ⚪️➡️ FastAPI: - -```JSON -{ - "message": "Hello World" -} -``` diff --git a/docs/em/docs/alternatives.md b/docs/em/docs/alternatives.md deleted file mode 100644 index 4cbac7539f..0000000000 --- a/docs/em/docs/alternatives.md +++ /dev/null @@ -1,485 +0,0 @@ -# 🎛, 🌈 & 🔺 - -⚫️❔ 😮 **FastAPI**, ❔ ⚫️ 🔬 🎏 🎛 & ⚫️❔ ⚫️ 🇭🇲 ⚪️➡️ 👫. - -## 🎶 - -**FastAPI** 🚫🔜 🔀 🚥 🚫 ⏮️ 👷 🎏. - -📤 ✔️ 📚 🧰 ✍ ⏭ 👈 ✔️ ℹ 😮 🚮 🏗. - -👤 ✔️ ❎ 🏗 🆕 🛠️ 📚 1️⃣2️⃣🗓️. 🥇 👤 🔄 ❎ 🌐 ⚒ 📔 **FastAPI** ⚙️ 📚 🎏 🛠️, 🔌-🔌, & 🧰. - -✋️ ☝, 📤 🙅‍♂ 🎏 🎛 🌘 🏗 🕳 👈 🚚 🌐 👫 ⚒, ✊ 🏆 💭 ⚪️➡️ ⏮️ 🧰, & 🌀 👫 🏆 🌌 💪, ⚙️ 🇪🇸 ⚒ 👈 ➖🚫 💪 ⏭ (🐍 3️⃣.6️⃣ ➕ 🆎 🔑). - -## ⏮️ 🧰 - -### - -⚫️ 🌅 🌟 🐍 🛠️ & 🛎 🕴. ⚫️ ⚙️ 🏗 ⚙️ 💖 👱📔. - -⚫️ 📶 😆 🔗 ⏮️ 🔗 💽 (💖 ✳ ⚖️ ✳),, ✔️ ☁ 💽 (💖 🗄, ✳, 👸, ♒️) 👑 🏪 🚒 🚫 📶 ⏩. - -⚫️ ✍ 🏗 🕸 👩‍💻, 🚫 ✍ 🔗 ⚙️ 🏛 🕸 (💖 😥, Vue.js & 📐) ⚖️ 🎏 ⚙️ (💖 📳) 🔗 ⏮️ ⚫️. - -### ✳ 🎂 🛠️ - -✳ 🎂 🛠️ ✍ 🗜 🧰 🏗 🕸 🔗 ⚙️ ✳ 🔘, 📉 🚮 🛠️ 🛠️. - -⚫️ ⚙️ 📚 🏢 ✅ 🦎, 🟥 👒 & 🎟. - -⚫️ 🕐 🥇 🖼 **🏧 🛠️ 🧾**, & 👉 🎯 🕐 🥇 💭 👈 😮 "🔎" **FastAPI**. - -/// note - -✳ 🎂 🛠️ ✍ ✡ 🇺🇸🏛. 🎏 👼 💃 & Uvicorn, 🔛 ❔ **FastAPI** ⚓️. - -/// - -/// check | 😮 **FastAPI** - -✔️ 🏧 🛠️ 🧾 🕸 👩‍💻 🔢. - -/// - -### 🏺 - -🏺 "🕸", ⚫️ 🚫 🔌 💽 🛠️ 🚫 📚 👜 👈 👟 🔢 ✳. - -👉 🦁 & 💪 ✔ 🔨 👜 💖 ⚙️ ☁ 💽 👑 💽 💾 ⚙️. - -⚫️ 📶 🙅, ⚫️ 📶 🏋️ 💡, 👐 🧾 🤚 🙁 📡 ☝. - -⚫️ 🛎 ⚙️ 🎏 🈸 👈 🚫 🎯 💪 💽, 👩‍💻 🧾, ⚖️ 🙆 📚 ⚒ 👈 👟 🏤-🏗 ✳. 👐 📚 👫 ⚒ 💪 🚮 ⏮️ 🔌-🔌. - -👉 ⚖ 🍕, & ➖ "🕸" 👈 💪 ↔ 📔 ⚫️❔ ⚫️❔ 💪 🔑 ⚒ 👈 👤 💚 🚧. - -👐 🦁 🏺, ⚫️ 😑 💖 👍 🏏 🏗 🔗. ⏭ 👜 🔎 "✳ 🎂 🛠️" 🏺. - -/// check | 😮 **FastAPI** - -◾-🛠️. ⚒ ⚫️ ⏩ 🌀 & 🏏 🧰 & 🍕 💪. - -✔️ 🙅 & ⏩ ⚙️ 🕹 ⚙️. - -/// - -### 📨 - -**FastAPI** 🚫 🤙 🎛 **📨**. 👫 ↔ 📶 🎏. - -⚫️ 🔜 🤙 ⚠ ⚙️ 📨 *🔘* FastAPI 🈸. - -✋️, FastAPI 🤚 🌈 ⚪️➡️ 📨. - -**📨** 🗃 *🔗* ⏮️ 🔗 (👩‍💻), ⏪ **FastAPI** 🗃 *🏗* 🔗 (💽). - -👫, 🌖 ⚖️ 🌘, 🔄 🔚, 🔗 🔠 🎏. - -📨 ✔️ 📶 🙅 & 🏋️ 🔧, ⚫️ 📶 ⏩ ⚙️, ⏮️ 🤔 🔢. ✋️ 🎏 🕰, ⚫️ 📶 🏋️ & 🛃. - -👈 ⚫️❔, 💬 🛂 🕸: - -> 📨 1️⃣ 🏆 ⏬ 🐍 📦 🌐 🕰 - -🌌 👆 ⚙️ ⚫️ 📶 🙅. 🖼, `GET` 📨, 👆 🔜 ✍: - -```Python -response = requests.get("http://example.com/some/url") -``` - -FastAPI 😑 🛠️ *➡ 🛠️* 💪 👀 💖: - -```Python hl_lines="1" -@app.get("/some/url") -def read_url(): - return {"message": "Hello World"} -``` - -👀 🔀 `requests.get(...)` & `@app.get(...)`. - -/// check | 😮 **FastAPI** - -* ✔️ 🙅 & 🏋️ 🛠️. -* ⚙️ 🇺🇸🔍 👩‍🔬 📛 (🛠️) 🔗, 🎯 & 🏋️ 🌌. -* ✔️ 🤔 🔢, ✋️ 🏋️ 🛃. - -/// - -### 🦁 / 🗄 - -👑 ⚒ 👤 💚 ⚪️➡️ ✳ 🎂 🛠️ 🏧 🛠️ 🧾. - -⤴️ 👤 🔎 👈 📤 🐩 📄 🔗, ⚙️ 🎻 (⚖️ 📁, ↔ 🎻) 🤙 🦁. - -& 📤 🕸 👩‍💻 🔢 🦁 🛠️ ⏪ ✍. , 💆‍♂ 💪 🏗 🦁 🧾 🛠️ 🔜 ✔ ⚙️ 👉 🕸 👩‍💻 🔢 🔁. - -☝, 🦁 👐 💾 🏛, 📁 🗄. - -👈 ⚫️❔ 🕐❔ 💬 🔃 ⏬ 2️⃣.0️⃣ ⚫️ ⚠ 💬 "🦁", & ⏬ 3️⃣ ➕ "🗄". - -/// check | 😮 **FastAPI** - -🛠️ & ⚙️ 📂 🐩 🛠️ 🔧, ↩️ 🛃 🔗. - - & 🛠️ 🐩-⚓️ 👩‍💻 🔢 🧰: - -* 🦁 🎚 -* 📄 - -👫 2️⃣ 👐 ➖ 📶 🌟 & ⚖, ✋️ 🔨 ⏩ 🔎, 👆 💪 🔎 💯 🌖 🎛 👩‍💻 🔢 🗄 (👈 👆 💪 ⚙️ ⏮️ **FastAPI**). - -/// - -### 🏺 🎂 🛠️ - -📤 📚 🏺 🎂 🛠️, ✋️ ⏮️ 💰 🕰 & 👷 🔘 🔬 👫, 👤 🔎 👈 📚 😞 ⚖️ 🚫, ⏮️ 📚 🧍 ❔ 👈 ⚒ 👫 🙃. - -### 🍭 - -1️⃣ 👑 ⚒ 💪 🛠️ ⚙️ 📊 "🛠️" ❔ ✊ 📊 ⚪️➡️ 📟 (🐍) & 🏭 ⚫️ 🔘 🕳 👈 💪 📨 🔘 🕸. 🖼, 🏭 🎚 ⚗ 📊 ⚪️➡️ 💽 🔘 🎻 🎚. 🏭 `datetime` 🎚 🔘 🎻, ♒️. - -➕1️⃣ 🦏 ⚒ 💚 🔗 💽 🔬, ⚒ 💭 👈 💽 ☑, 🤝 🎯 🔢. 🖼, 👈 🏑 `int`, & 🚫 🎲 🎻. 👉 ✴️ ⚠ 📨 💽. - -🍵 💽 🔬 ⚙️, 👆 🔜 ✔️ 🌐 ✅ ✋, 📟. - -👫 ⚒ ⚫️❔ 🍭 🏗 🚚. ⚫️ 👑 🗃, & 👤 ✔️ ⚙️ ⚫️ 📚 ⏭. - -✋️ ⚫️ ✍ ⏭ 📤 🔀 🐍 🆎 🔑. , 🔬 🔠 🔗 👆 💪 ⚙️ 🎯 🇨🇻 & 🎓 🚚 🍭. - -/// check | 😮 **FastAPI** - -⚙️ 📟 🔬 "🔗" 👈 🚚 💽 🆎 & 🔬, 🔁. - -/// - -### Webarg - -➕1️⃣ 🦏 ⚒ ✔ 🔗 📊 ⚪️➡️ 📨 📨. - -Webarg 🧰 👈 ⚒ 🚚 👈 🔛 🔝 📚 🛠️, 🔌 🏺. - -⚫️ ⚙️ 🍭 🔘 💽 🔬. & ⚫️ ✍ 🎏 👩‍💻. - -⚫️ 👑 🧰 & 👤 ✔️ ⚙️ ⚫️ 📚 💁‍♂️, ⏭ ✔️ **FastAPI**. - -/// info - -Webarg ✍ 🎏 🍭 👩‍💻. - -/// - -/// check | 😮 **FastAPI** - -✔️ 🏧 🔬 📨 📨 💽. - -/// - -### APISpec - -🍭 & Webarg 🚚 🔬, ✍ & 🛠️ 🔌-🔌. - -✋️ 🧾 ❌. ⤴️ APISpec ✍. - -⚫️ 🔌-📚 🛠️ (& 📤 🔌-💃 💁‍♂️). - -🌌 ⚫️ 👷 👈 👆 ✍ 🔑 🔗 ⚙️ 📁 📁 🔘 #️⃣ 🔠 🔢 🚚 🛣. - -& ⚫️ 🏗 🗄 🔗. - -👈 ❔ ⚫️ 👷 🏺, 💃, 🆘, ♒️. - -✋️ ⤴️, 👥 ✔️ 🔄 ⚠ ✔️ ◾-❕, 🔘 🐍 🎻 (🦏 📁). - -👨‍🎨 💪 🚫 ℹ 🌅 ⏮️ 👈. & 🚥 👥 🔀 🔢 ⚖️ 🍭 🔗 & 💭 🔀 👈 📁#️⃣, 🏗 🔗 🔜 ❌. - -/// info - -APISpec ✍ 🎏 🍭 👩‍💻. - -/// - -/// check | 😮 **FastAPI** - -🐕‍🦺 📂 🐩 🛠️, 🗄. - -/// - -### 🏺-Apispec - -⚫️ 🏺 🔌 -, 👈 👔 👯‍♂️ Webarg, 🍭 & APISpec. - -⚫️ ⚙️ ℹ ⚪️➡️ Webarg & 🍭 🔁 🏗 🗄 🔗, ⚙️ APISpec. - -⚫️ 👑 🧰, 📶 🔽-📈. ⚫️ 🔜 🌌 🌖 🌟 🌘 📚 🏺 🔌-🔌 👅 📤. ⚫️ 💪 ↩️ 🚮 🧾 ➖ 💁‍♂️ 🩲 & 📝. - -👉 ❎ ✔️ ✍ 📁 (➕1️⃣ ❕) 🔘 🐍 ✍. - -👉 🌀 🏺, 🏺-Apispec ⏮️ 🍭 & Webarg 👇 💕 👩‍💻 📚 ⏭ 🏗 **FastAPI**. - -⚙️ ⚫️ ↘️ 🏗 📚 🏺 🌕-📚 🚂. 👫 👑 📚 👤 (& 📚 🔢 🏉) ✔️ ⚙️ 🆙 🔜: - -* https://github.com/tiangolo/full-stack -* https://github.com/tiangolo/full-stack-flask-couchbase -* https://github.com/tiangolo/full-stack-flask-couchdb - -& 👫 🎏 🌕-📚 🚂 🧢 [**FastAPI** 🏗 🚂](project-generation.md){.internal-link target=_blank}. - -/// info - -🏺-Apispec ✍ 🎏 🍭 👩‍💻. - -/// - -/// check | 😮 **FastAPI** - -🏗 🗄 🔗 🔁, ⚪️➡️ 🎏 📟 👈 🔬 🛠️ & 🔬. - -/// - -### NestJS (& 📐) - -👉 ➖🚫 🚫 🐍, NestJS 🕸 (📕) ✳ 🛠️ 😮 📐. - -⚫️ 🏆 🕳 🙁 🎏 ⚫️❔ 💪 🔨 ⏮️ 🏺-Apispec. - -⚫️ ✔️ 🛠️ 🔗 💉 ⚙️, 😮 📐 2️⃣. ⚫️ 🚚 🏤-® "💉" (💖 🌐 🎏 🔗 💉 ⚙️ 👤 💭),, ⚫️ 🚮 🎭 & 📟 🔁. - -🔢 🔬 ⏮️ 📕 🆎 (🎏 🐍 🆎 🔑), 👨‍🎨 🐕‍🦺 👍. - -✋️ 📕 📊 🚫 🛡 ⏮️ 📹 🕸, ⚫️ 🚫🔜 ⚓️ 🔛 🆎 🔬 🔬, 🛠️ & 🧾 🎏 🕰. ↩️ 👉 & 🔧 🚫, 🤚 🔬, 🛠️ & 🏧 🔗 ⚡, ⚫️ 💪 🚮 👨‍🎨 📚 🥉. , ⚫️ ▶️️ 🔁. - -⚫️ 💪 🚫 🍵 🔁 🏷 📶 👍. , 🚥 🎻 💪 📨 🎻 🎚 👈 ✔️ 🔘 🏑 👈 🔄 🐦 🎻 🎚, ⚫️ 🚫🔜 ☑ 📄 & ✔. - -/// check | 😮 **FastAPI** - -⚙️ 🐍 🆎 ✔️ 👑 👨‍🎨 🐕‍🦺. - -✔️ 🏋️ 🔗 💉 ⚙️. 🔎 🌌 📉 📟 🔁. - -/// - -### 🤣 - -⚫️ 🕐 🥇 📶 ⏩ 🐍 🛠️ ⚓️ 🔛 `asyncio`. ⚫️ ⚒ 📶 🎏 🏺. - -/// note | 📡 ℹ - -⚫️ ⚙️ `uvloop` ↩️ 🔢 🐍 `asyncio` ➰. 👈 ⚫️❔ ⚒ ⚫️ ⏩. - -⚫️ 🎯 😮 Uvicorn & 💃, 👈 ⏳ ⏩ 🌘 🤣 📂 📇. - -/// - -/// check | 😮 **FastAPI** - -🔎 🌌 ✔️ 😜 🎭. - -👈 ⚫️❔ **FastAPI** ⚓️ 🔛 💃, ⚫️ ⏩ 🛠️ 💪 (💯 🥉-🥳 📇). - -/// - -### 🦅 - -🦅 ➕1️⃣ ↕ 🎭 🐍 🛠️, ⚫️ 🔧 ⭐, & 👷 🏛 🎏 🛠️ 💖 🤗. - -⚫️ 🏗 ✔️ 🔢 👈 📨 2️⃣ 🔢, 1️⃣ "📨" & 1️⃣ "📨". ⤴️ 👆 "✍" 🍕 ⚪️➡️ 📨, & "✍" 🍕 📨. ↩️ 👉 🔧, ⚫️ 🚫 💪 📣 📨 🔢 & 💪 ⏮️ 🐩 🐍 🆎 🔑 🔢 🔢. - -, 💽 🔬, 🛠️, & 🧾, ✔️ ⌛ 📟, 🚫 🔁. ⚖️ 👫 ✔️ 🛠️ 🛠️ 🔛 🔝 🦅, 💖 🤗. 👉 🎏 🔺 🔨 🎏 🛠️ 👈 😮 🦅 🔧, ✔️ 1️⃣ 📨 🎚 & 1️⃣ 📨 🎚 🔢. - -/// check | 😮 **FastAPI** - -🔎 🌌 🤚 👑 🎭. - -⤴️ ⏮️ 🤗 (🤗 ⚓️ 🔛 🦅) 😮 **FastAPI** 📣 `response` 🔢 🔢. - -👐 FastAPI ⚫️ 📦, & ⚙️ ✴️ ⚒ 🎚, 🍪, & 🎛 👔 📟. - -/// - -### - -👤 🔎 ♨ 🥇 ▶️ 🏗 **FastAPI**. & ⚫️ ✔️ 🎏 💭: - -* ⚓️ 🔛 🐍 🆎 🔑. -* 🔬 & 🧾 ⚪️➡️ 👫 🆎. -* 🔗 💉 ⚙️. - -⚫️ 🚫 ⚙️ 💽 🔬, 🛠️ & 🧾 🥉-🥳 🗃 💖 Pydantic, ⚫️ ✔️ 🚮 👍. , 👫 💽 🆎 🔑 🔜 🚫 ♻ 💪. - -⚫️ 🚚 🐥 🍖 🌅 🔁 📳. & ⚫️ ⚓️ 🔛 🇨🇻 (↩️ 🔫), ⚫️ 🚫 🔧 ✊ 📈 ↕-🎭 🚚 🧰 💖 Uvicorn, 💃 & 🤣. - -🔗 💉 ⚙️ 🚚 🏤-® 🔗 & 🔗 ❎ 🧢 🔛 📣 🆎. , ⚫️ 🚫 💪 📣 🌅 🌘 1️⃣ "🦲" 👈 🚚 🎯 🆎. - -🛣 📣 👁 🥉, ⚙️ 🔢 📣 🎏 🥉 (↩️ ⚙️ 👨‍🎨 👈 💪 🥉 ▶️️ 🔛 🔝 🔢 👈 🍵 🔗). 👉 🔐 ❔ ✳ 🔨 ⚫️ 🌘 ❔ 🏺 (& 💃) 🔨 ⚫️. ⚫️ 🎏 📟 👜 👈 📶 😆 🔗. - -/// check | 😮 **FastAPI** - -🔬 ➕ 🔬 💽 🆎 ⚙️ "🔢" 💲 🏷 🔢. 👉 📉 👨‍🎨 🐕‍🦺, & ⚫️ 🚫 💪 Pydantic ⏭. - -👉 🤙 😮 🛠️ 🍕 Pydantic, 🐕‍🦺 🎏 🔬 📄 👗 (🌐 👉 🛠️ 🔜 ⏪ 💪 Pydantic). - -/// - -### 🤗 - -🤗 🕐 🥇 🛠️ 🛠️ 📄 🛠️ 🔢 🆎 ⚙️ 🐍 🆎 🔑. 👉 👑 💭 👈 😮 🎏 🧰 🎏. - -⚫️ ⚙️ 🛃 🆎 🚮 📄 ↩️ 🐩 🐍 🆎, ✋️ ⚫️ 🦏 🔁 ⏩. - -⚫️ 🕐 🥇 🛠️ 🏗 🛃 🔗 📣 🎂 🛠️ 🎻. - -⚫️ 🚫 ⚓️ 🔛 🐩 💖 🗄 & 🎻 🔗. ⚫️ 🚫🔜 🎯 🛠️ ⚫️ ⏮️ 🎏 🧰, 💖 🦁 🎚. ✋️ 🔄, ⚫️ 📶 💡 💭. - -⚫️ ✔️ 😌, ⭐ ⚒: ⚙️ 🎏 🛠️, ⚫️ 💪 ✍ 🔗 & 🇳🇨. - -⚫️ ⚓️ 🔛 ⏮️ 🐩 🔁 🐍 🕸 🛠️ (🇨🇻), ⚫️ 💪 🚫 🍵 *️⃣ & 🎏 👜, 👐 ⚫️ ✔️ ↕ 🎭 💁‍♂️. - -/// info - -🤗 ✍ ✡ 🗄, 🎏 👼 `isort`, 👑 🧰 🔁 😇 🗄 🐍 📁. - -/// - -/// check | 💭 😮 **FastAPI** - -🤗 😮 🍕 APIStar, & 1️⃣ 🧰 👤 🔎 🏆 👍, 🌟 APIStar. - -🤗 ℹ 😍 **FastAPI** ⚙️ 🐍 🆎 🔑 📣 🔢, & 🏗 🔗 ⚖ 🛠️ 🔁. - -🤗 😮 **FastAPI** 📣 `response` 🔢 🔢 ⚒ 🎚 & 🍪. - -/// - -### APIStar (<= 0️⃣.5️⃣) - -▶️️ ⏭ 🤔 🏗 **FastAPI** 👤 🔎 **APIStar** 💽. ⚫️ ✔️ 🌖 🌐 👤 👀 & ✔️ 👑 🔧. - -⚫️ 🕐 🥇 🛠️ 🛠️ ⚙️ 🐍 🆎 🔑 📣 🔢 & 📨 👈 👤 ⏱ 👀 (⏭ NestJS & ♨). 👤 🔎 ⚫️ 🌅 ⚖️ 🌘 🎏 🕰 🤗. ✋️ APIStar ⚙️ 🗄 🐩. - -⚫️ ✔️ 🏧 💽 🔬, 💽 🛠️ & 🗄 🔗 ⚡ ⚓️ 🔛 🎏 🆎 🔑 📚 🥉. - -💪 🔗 🔑 🚫 ⚙️ 🎏 🐍 🆎 🔑 💖 Pydantic, ⚫️ 🍖 🌅 🎏 🍭,, 👨‍🎨 🐕‍🦺 🚫🔜 👍, ✋️, APIStar 🏆 💪 🎛. - -⚫️ ✔️ 🏆 🎭 📇 🕰 (🕴 💥 💃). - -🥇, ⚫️ 🚫 ✔️ 🏧 🛠️ 🧾 🕸 🎚, ✋️ 👤 💭 👤 💪 🚮 🦁 🎚 ⚫️. - -⚫️ ✔️ 🔗 💉 ⚙️. ⚫️ ✔ 🏤-® 🦲, 🎏 🧰 🔬 🔛. ✋️, ⚫️ 👑 ⚒. - -👤 🙅 💪 ⚙️ ⚫️ 🌕 🏗, ⚫️ 🚫 ✔️ 💂‍♂ 🛠️,, 👤 🚫 🚫 ❎ 🌐 ⚒ 👤 ✔️ ⏮️ 🌕-📚 🚂 ⚓️ 🔛 🏺-Apispec. 👤 ✔️ 👇 📈 🏗 ✍ 🚲 📨 ❎ 👈 🛠️. - -✋️ ⤴️, 🏗 🎯 🔀. - -⚫️ 🙅‍♂ 📏 🛠️ 🕸 🛠️, 👼 💪 🎯 🔛 💃. - -🔜 APIStar ⚒ 🧰 ✔ 🗄 🔧, 🚫 🕸 🛠️. - -/// info - -APIStar ✍ ✡ 🇺🇸🏛. 🎏 👨 👈 ✍: - -* ✳ 🎂 🛠️ -* 💃 (❔ **FastAPI** ⚓️) -* Uvicorn (⚙️ 💃 & **FastAPI**) - -/// - -/// check | 😮 **FastAPI** - -🔀. - -💭 📣 💗 👜 (💽 🔬, 🛠️ & 🧾) ⏮️ 🎏 🐍 🆎, 👈 🎏 🕰 🚚 👑 👨‍🎨 🐕‍🦺, 🕳 👤 🤔 💎 💭. - - & ⏮️ 🔎 📏 🕰 🎏 🛠️ & 🔬 📚 🎏 🎛, APIStar 🏆 🎛 💪. - -⤴️ APIStar ⛔️ 🔀 💽 & 💃 ✍, & 🆕 👻 🏛 ✅ ⚙️. 👈 🏁 🌈 🏗 **FastAPI**. - -👤 🤔 **FastAPI** "🛐 👨‍💼" APIStar, ⏪ 📉 & 📈 ⚒, ⌨ ⚙️, & 🎏 🍕, ⚓️ 🔛 🏫 ⚪️➡️ 🌐 👉 ⏮️ 🧰. - -/// - -## ⚙️ **FastAPI** - -### Pydantic - -Pydantic 🗃 🔬 💽 🔬, 🛠️ & 🧾 (⚙️ 🎻 🔗) ⚓️ 🔛 🐍 🆎 🔑. - -👈 ⚒ ⚫️ 📶 🏋️. - -⚫️ ⭐ 🍭. 👐 ⚫️ ⏩ 🌘 🍭 📇. & ⚫️ ⚓️ 🔛 🎏 🐍 🆎 🔑, 👨‍🎨 🐕‍🦺 👑. - -/// check | **FastAPI** ⚙️ ⚫️ - -🍵 🌐 💽 🔬, 💽 🛠️ & 🏧 🏷 🧾 (⚓️ 🔛 🎻 🔗). - -**FastAPI** ⤴️ ✊ 👈 🎻 🔗 💽 & 🚮 ⚫️ 🗄, ↖️ ⚪️➡️ 🌐 🎏 👜 ⚫️ 🔨. - -/// - -### 💃 - -💃 💿 🔫 🛠️/🧰, ❔ 💯 🏗 ↕-🎭 ✳ 🐕‍🦺. - -⚫️ 📶 🙅 & 🏋️. ⚫️ 🔧 💪 🏧, & ✔️ 🔧 🦲. - -⚫️ ✔️: - -* 🤙 🎆 🎭. -* *️⃣ 🐕‍🦺. -* -🛠️ 🖥 📋. -* 🕴 & 🤫 🎉. -* 💯 👩‍💻 🏗 🔛 🇸🇲. -* ⚜, 🗜, 🎻 📁, 🎏 📨. -* 🎉 & 🍪 🐕‍🦺. -* 1️⃣0️⃣0️⃣ 💯 💯 💰. -* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ ✍. -* 👩‍❤‍👨 🏋️ 🔗. - -💃 ⏳ ⏩ 🐍 🛠️ 💯. 🕴 💥 Uvicorn, ❔ 🚫 🛠️, ✋️ 💽. - -💃 🚚 🌐 🔰 🕸 🕸 🛠️. - -✋️ ⚫️ 🚫 🚚 🏧 💽 🔬, 🛠️ ⚖️ 🧾. - -👈 1️⃣ 👑 👜 👈 **FastAPI** 🚮 🔛 🔝, 🌐 ⚓️ 🔛 🐍 🆎 🔑 (⚙️ Pydantic). 👈, ➕ 🔗 💉 ⚙️, 💂‍♂ 🚙, 🗄 🔗 ⚡, ♒️. - -/// note | 📡 ℹ - -🔫 🆕 "🐩" ➖ 🛠️ ✳ 🐚 🏉 👨‍🎓. ⚫️ 🚫 "🐍 🐩" (🇩🇬), 👐 👫 🛠️ 🔨 👈. - -👐, ⚫️ ⏪ ➖ ⚙️ "🐩" 📚 🧰. 👉 📉 📉 🛠️, 👆 💪 🎛 Uvicorn 🙆 🎏 🔫 💽 (💖 👸 ⚖️ Hypercorn), ⚖️ 👆 💪 🚮 🔫 🔗 🧰, 💖 `python-socketio`. - -/// - -/// check | **FastAPI** ⚙️ ⚫️ - -🍵 🌐 🐚 🕸 🍕. ❎ ⚒ 🔛 🔝. - -🎓 `FastAPI` ⚫️ 😖 🔗 ⚪️➡️ 🎓 `Starlette`. - -, 🕳 👈 👆 💪 ⏮️ 💃, 👆 💪 ⚫️ 🔗 ⏮️ **FastAPI**, ⚫️ 🌖 💃 🔛 💊. - -/// - -### Uvicorn - -Uvicorn 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. - -⚫️ 🚫 🕸 🛠️, ✋️ 💽. 🖼, ⚫️ 🚫 🚚 🧰 🕹 ➡. 👈 🕳 👈 🛠️ 💖 💃 (⚖️ **FastAPI**) 🔜 🚚 🔛 🔝. - -⚫️ 👍 💽 💃 & **FastAPI**. - -/// check | **FastAPI** 👍 ⚫️ - -👑 🕸 💽 🏃 **FastAPI** 🈸. - -👆 💪 🌀 ⚫️ ⏮️ 🐁, ✔️ 🔁 👁-🛠️ 💽. - -✅ 🌅 ℹ [🛠️](deployment/index.md){.internal-link target=_blank} 📄. - -/// - -## 📇 & 🚅 - -🤔, 🔬, & 👀 🔺 🖖 Uvicorn, 💃 & FastAPI, ✅ 📄 🔃 [📇](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/em/docs/async.md b/docs/em/docs/async.md deleted file mode 100644 index cde778b0ff..0000000000 --- a/docs/em/docs/async.md +++ /dev/null @@ -1,442 +0,0 @@ -# 🛠️ & 🔁 / ⌛ - -ℹ 🔃 `async def` ❕ *➡ 🛠️ 🔢* & 🖥 🔃 🔁 📟, 🛠️, & 🔁. - -## 🏃 ❓ - -🆑;👩‍⚕️: - -🚥 👆 ⚙️ 🥉 🥳 🗃 👈 💬 👆 🤙 👫 ⏮️ `await`, 💖: - -```Python -results = await some_library() -``` - -⤴️, 📣 👆 *➡ 🛠️ 🔢* ⏮️ `async def` 💖: - -```Python hl_lines="2" -@app.get('/') -async def read_results(): - results = await some_library() - return results -``` - -/// note - -👆 💪 🕴 ⚙️ `await` 🔘 🔢 ✍ ⏮️ `async def`. - -/// - ---- - -🚥 👆 ⚙️ 🥉 🥳 🗃 👈 🔗 ⏮️ 🕳 (💽, 🛠️, 📁 ⚙️, ♒️.) & 🚫 ✔️ 🐕‍🦺 ⚙️ `await`, (👉 ⏳ 💼 🌅 💽 🗃), ⤴️ 📣 👆 *➡ 🛠️ 🔢* 🛎, ⏮️ `def`, 💖: - -```Python hl_lines="2" -@app.get('/') -def results(): - results = some_library() - return results -``` - ---- - -🚥 👆 🈸 (😫) 🚫 ✔️ 🔗 ⏮️ 🕳 🙆 & ⌛ ⚫️ 📨, ⚙️ `async def`. - ---- - -🚥 👆 🚫 💭, ⚙️ 😐 `def`. - ---- - -**🗒**: 👆 💪 🌀 `def` & `async def` 👆 *➡ 🛠️ 🔢* 🌅 👆 💪 & 🔬 🔠 1️⃣ ⚙️ 🏆 🎛 👆. FastAPI 🔜 ▶️️ 👜 ⏮️ 👫. - -😆, 🙆 💼 🔛, FastAPI 🔜 👷 🔁 & 📶 ⏩. - -✋️ 📄 📶 🔛, ⚫️ 🔜 💪 🎭 🛠️. - -## 📡 ℹ - -🏛 ⏬ 🐍 ✔️ 🐕‍🦺 **"🔁 📟"** ⚙️ 🕳 🤙 **"🔁"**, ⏮️ **`async` & `await`** ❕. - -➡️ 👀 👈 🔤 🍕 📄 🔛: - -* **🔁 📟** -* **`async` & `await`** -* **🔁** - -## 🔁 📟 - -🔁 📟 ⛓ 👈 🇪🇸 👶 ✔️ 🌌 💬 💻 / 📋 👶 👈 ☝ 📟, ⚫️ 👶 🔜 ✔️ ⌛ *🕳 🙆* 🏁 👱 🙆. ➡️ 💬 👈 *🕳 🙆* 🤙 "🐌-📁" 👶. - -, ⏮️ 👈 🕰, 💻 💪 🚶 & 🎏 👷, ⏪ "🐌-📁" 👶 🏁. - -⤴️ 💻 / 📋 👶 🔜 👟 🔙 🔠 🕰 ⚫️ ✔️ 🤞 ↩️ ⚫️ ⌛ 🔄, ⚖️ 🕐❔ ⚫️ 👶 🏁 🌐 👷 ⚫️ ✔️ 👈 ☝. & ⚫️ 👶 🔜 👀 🚥 🙆 📋 ⚫️ ⌛ ✔️ ⏪ 🏁, 🤸 ⚫️❔ ⚫️ ✔️. - -⏭, ⚫️ 👶 ✊ 🥇 📋 🏁 (➡️ 💬, 👆 "🐌-📁" 👶) & 😣 ⚫️❔ ⚫️ ✔️ ⏮️ ⚫️. - -👈 "⌛ 🕳 🙆" 🛎 🔗 👤/🅾 🛠️ 👈 📶 "🐌" (🔬 🚅 🕹 & 💾 💾), 💖 ⌛: - -* 📊 ⚪️➡️ 👩‍💻 📨 🔘 🕸 -* 📊 📨 👆 📋 📨 👩‍💻 🔘 🕸 -* 🎚 📁 💾 ✍ ⚙️ & 🤝 👆 📋 -* 🎚 👆 📋 🤝 ⚙️ ✍ 💾 -* 🛰 🛠️ 🛠️ -* 💽 🛠️ 🏁 -* 💽 🔢 📨 🏁 -* ♒️. - -🛠️ 🕰 🍴 ✴️ ⌛ 👤/🅾 🛠️, 👫 🤙 👫 "👤/🅾 🔗" 🛠️. - -⚫️ 🤙 "🔁" ↩️ 💻 / 📋 🚫 ✔️ "🔁" ⏮️ 🐌 📋, ⌛ ☑ 🙍 👈 📋 🏁, ⏪ 🔨 🕳, 💪 ✊ 📋 🏁 & 😣 👷. - -↩️ 👈, 💆‍♂ "🔁" ⚙️, 🕐 🏁, 📋 💪 ⌛ ⏸ 🐥 👄 (⏲) 💻 / 📋 🏁 ⚫️❔ ⚫️ 🚶, & ⤴️ 👟 🔙 ✊ 🏁 & 😣 👷 ⏮️ 👫. - -"🔁" (👽 "🔁") 👫 🛎 ⚙️ ⚖ "🔁", ↩️ 💻 / 📋 ⏩ 🌐 📶 🔁 ⏭ 🔀 🎏 📋, 🚥 👈 🔁 🔌 ⌛. - -### 🛠️ & 🍔 - -👉 💭 **🔁** 📟 🔬 🔛 🕣 🤙 **"🛠️"**. ⚫️ 🎏 ⚪️➡️ **"🔁"**. - -**🛠️** & **🔁** 👯‍♂️ 🔗 "🎏 👜 😥 🌅 ⚖️ 🌘 🎏 🕰". - -✋️ ℹ 🖖 *🛠️* & *🔁* 🎏. - -👀 🔺, 🌈 📄 📖 🔃 🍔: - -### 🛠️ 🍔 - -👆 🚶 ⏮️ 👆 🥰 🤚 ⏩ 🥕, 👆 🧍 ⏸ ⏪ 🏧 ✊ ✔ ⚪️➡️ 👫👫 🚪 👆. 👶 - - - -⤴️ ⚫️ 👆 🔄, 👆 🥉 👆 ✔ 2️⃣ 📶 🎀 🍔 👆 🥰 & 👆. 👶 👶 - - - -🏧 💬 🕳 🍳 👨‍🍳 👫 💭 👫 ✔️ 🏗 👆 🍔 (✋️ 👫 ⏳ 🏗 🕐 ⏮️ 👩‍💻). - - - -👆 💸. 👶 - -🏧 🤝 👆 🔢 👆 🔄. - - - -⏪ 👆 ⌛, 👆 🚶 ⏮️ 👆 🥰 & ⚒ 🏓, 👆 🧎 & 💬 ⏮️ 👆 🥰 📏 🕰 (👆 🍔 📶 🎀 & ✊ 🕰 🏗). - -👆 🏖 🏓 ⏮️ 👆 🥰, ⏪ 👆 ⌛ 🍔, 👆 💪 💸 👈 🕰 😮 ❔ 👌, 🐨 & 🙃 👆 🥰 👶 👶 👶. - - - -⏪ ⌛ & 💬 👆 🥰, ⚪️➡️ 🕰 🕰, 👆 ✅ 🔢 🖥 🔛 ⏲ 👀 🚥 ⚫️ 👆 🔄 ⏪. - -⤴️ ☝, ⚫️ 😒 👆 🔄. 👆 🚶 ⏲, 🤚 👆 🍔 & 👟 🔙 🏓. - - - -👆 & 👆 🥰 🍴 🍔 & ✔️ 👌 🕰. 👶 - - - -/// info - -🌹 🖼 👯 🍏. 👶 - -/// - ---- - -🌈 👆 💻 / 📋 👶 👈 📖. - -⏪ 👆 ⏸, 👆 ⛽ 👶, ⌛ 👆 🔄, 🚫 🔨 🕳 📶 "😌". ✋️ ⏸ ⏩ ↩️ 🏧 🕴 ✊ ✔ (🚫 🏗 👫), 👈 👌. - -⤴️, 🕐❔ ⚫️ 👆 🔄, 👆 ☑ "😌" 👷, 👆 🛠️ 🍣, 💭 ⚫️❔ 👆 💚, 🤚 👆 🥰 ⚒, 💸, ✅ 👈 👆 🤝 ☑ 💵 ⚖️ 💳, ✅ 👈 👆 🈚 ☑, ✅ 👈 ✔ ✔️ ☑ 🏬, ♒️. - -✋️ ⤴️, ✋️ 👆 🚫 ✔️ 👆 🍔, 👆 👷 ⏮️ 🏧 "🔛 ⏸" ⏸, ↩️ 👆 ✔️ ⌛ 👶 👆 🍔 🔜. - -✋️ 👆 🚶 ↖️ ⚪️➡️ ⏲ & 🧎 🏓 ⏮️ 🔢 👆 🔄, 👆 💪 🎛 👶 👆 🙋 👆 🥰, & "👷" 👶 👶 🔛 👈. ⤴️ 👆 🔄 🔨 🕳 📶 "😌" 😏 ⏮️ 👆 🥰 👶. - -⤴️ 🏧 👶 💬 "👤 🏁 ⏮️ 🔨 🍔" 🚮 👆 🔢 🔛 ⏲ 🖥, ✋️ 👆 🚫 🦘 💖 😜 ⏪ 🕐❔ 🖥 🔢 🔀 👆 🔄 🔢. 👆 💭 🙅‍♂ 1️⃣ 🔜 📎 👆 🍔 ↩️ 👆 ✔️ 🔢 👆 🔄, & 👫 ✔️ 👫. - -👆 ⌛ 👆 🥰 🏁 📖 (🏁 ⏮️ 👷 👶 / 📋 ➖ 🛠️ 👶), 😀 🖐 & 💬 👈 👆 🔜 🍔 ⏸. - -⤴️ 👆 🚶 ⏲ 👶, ▶️ 📋 👈 🔜 🏁 👶, ⚒ 🍔, 💬 👏 & ✊ 👫 🏓. 👈 🏁 👈 🔁 / 📋 🔗 ⏮️ ⏲ ⏹. 👈 🔄, ✍ 🆕 📋, "🍴 🍔" 👶 👶, ✋️ ⏮️ 1️⃣ "🤚 🍔" 🏁 ⏹. - -### 🔗 🍔 - -🔜 ➡️ 🌈 👫 ➖🚫 🚫 "🛠️ 🍔", ✋️ "🔗 🍔". - -👆 🚶 ⏮️ 👆 🥰 🤚 🔗 ⏩ 🥕. - -👆 🧍 ⏸ ⏪ 📚 (➡️ 💬 8️⃣) 🏧 👈 🎏 🕰 🍳 ✊ ✔ ⚪️➡️ 👫👫 🚪 👆. - -👱 ⏭ 👆 ⌛ 👫 🍔 🔜 ⏭ 🍂 ⏲ ↩️ 🔠 8️⃣ 🏧 🚶 & 🏗 🍔 ▶️️ ↖️ ⏭ 💆‍♂ ⏭ ✔. - - - -⤴️ ⚫️ 😒 👆 🔄, 👆 🥉 👆 ✔ 2️⃣ 📶 🎀 🍔 👆 🥰 & 👆. - -👆 💸 👶. - - - -🏧 🚶 👨‍🍳. - -👆 ⌛, 🧍 🚪 ⏲ 👶, 👈 🙅‍♂ 1️⃣ 🙆 ✊ 👆 🍔 ⏭ 👆, 📤 🙅‍♂ 🔢 🔄. - - - -👆 & 👆 🥰 😩 🚫 ➡️ 🙆 🤚 🚪 👆 & ✊ 👆 🍔 🕐❔ 👫 🛬, 👆 🚫🔜 💸 🙋 👆 🥰. 👶 - -👉 "🔁" 👷, 👆 "🔁" ⏮️ 🏧/🍳 👶 👶. 👆 ✔️ ⌛ 👶 & 📤 ☑ 🙍 👈 🏧/🍳 👶 👶 🏁 🍔 & 🤝 👫 👆, ⚖️ ⏪, 👱 🙆 💪 ✊ 👫. - - - -⤴️ 👆 🏧/🍳 👶 👶 😒 👟 🔙 ⏮️ 👆 🍔, ⏮️ 📏 🕰 ⌛ 👶 📤 🚪 ⏲. - - - -👆 ✊ 👆 🍔 & 🚶 🏓 ⏮️ 👆 🥰. - -👆 🍴 👫, & 👆 🔨. ⏹ - - - -📤 🚫 🌅 💬 ⚖️ 😏 🌅 🕰 💸 ⌛ 👶 🚪 ⏲. 👶 - -/// info - -🌹 🖼 👯 🍏. 👶 - -/// - ---- - -👉 😐 🔗 🍔, 👆 💻 / 📋 👶 ⏮️ 2️⃣ 🕹 (👆 & 👆 🥰), 👯‍♂️ ⌛ 👶 & 💡 👫 🙋 👶 "⌛ 🔛 ⏲" 👶 📏 🕰. - -⏩ 🥕 🏪 ✔️ 8️⃣ 🕹 (🏧/🍳). ⏪ 🛠️ 🍔 🏪 💪 ✔️ ✔️ 🕴 2️⃣ (1️⃣ 🏧 & 1️⃣ 🍳). - -✋️, 🏁 💡 🚫 🏆. 👶 - ---- - -👉 🔜 🔗 🌓 📖 🍔. 👶 - -🌅 "🎰 👨‍❤‍👨" 🖼 👉, 🌈 🏦. - -🆙 ⏳, 🏆 🏦 ✔️ 💗 🏧 👶 👶 👶 👶 👶 👶 👶 👶 & 🦏 ⏸ 👶 👶 👶 👶 👶 👶 👶 👶. - -🌐 🏧 🔨 🌐 👷 ⏮️ 1️⃣ 👩‍💻 ⏮️ 🎏 👶 👶 👶. - -& 👆 ✔️ ⌛ 👶 ⏸ 📏 🕰 ⚖️ 👆 💸 👆 🔄. - -👆 🎲 🚫🔜 💚 ✊ 👆 🥰 👶 ⏮️ 👆 👷 🏦 👶. - -### 🍔 🏁 - -👉 😐 "⏩ 🥕 🍔 ⏮️ 👆 🥰", 📤 📚 ⌛ 👶, ⚫️ ⚒ 📚 🌅 🔑 ✔️ 🛠️ ⚙️ ⏸ 👶 👶. - -👉 💼 🌅 🕸 🈸. - -📚, 📚 👩‍💻, ✋️ 👆 💽 ⌛ 👶 👫 🚫--👍 🔗 📨 👫 📨. - -& ⤴️ ⌛ 👶 🔄 📨 👟 🔙. - -👉 "⌛" 👶 ⚖ ⏲, ✋️, ⚖ ⚫️ 🌐, ⚫️ 📚 ⌛ 🔚. - -👈 ⚫️❔ ⚫️ ⚒ 📚 🔑 ⚙️ 🔁 ⏸ 👶 👶 📟 🕸 🔗. - -👉 😇 🔀 ⚫️❔ ⚒ ✳ 🌟 (✋️ ✳ 🚫 🔗) & 👈 💪 🚶 🛠️ 🇪🇸. - -& 👈 🎏 🎚 🎭 👆 🤚 ⏮️ **FastAPI**. - -& 👆 💪 ✔️ 🔁 & 🔀 🎏 🕰, 👆 🤚 ↕ 🎭 🌘 🌅 💯 ✳ 🛠️ & 🔛 🇷🇪 ⏮️ 🚶, ❔ ✍ 🇪🇸 🔐 🅱 (🌐 👏 💃). - -### 🛠️ 👍 🌘 🔁 ❓ - -😆 ❗ 👈 🚫 🛐 📖. - -🛠️ 🎏 🌘 🔁. & ⚫️ 👻 🔛 **🎯** 😐 👈 🔌 📚 ⌛. ↩️ 👈, ⚫️ 🛎 📚 👍 🌘 🔁 🕸 🈸 🛠️. ✋️ 🚫 🌐. - -, ⚖ 👈 👅, 🌈 📄 📏 📖: - -> 👆 ✔️ 🧹 🦏, 💩 🏠. - -*😆, 👈 🎂 📖*. - ---- - -📤 🙅‍♂ ⌛ 👶 🙆, 📚 👷 🔨, 🔛 💗 🥉 🏠. - -👆 💪 ✔️ 🔄 🍔 🖼, 🥇 🏠 🧖‍♂, ⤴️ 👨‍🍳, ✋️ 👆 🚫 ⌛ 👶 🕳, 🧹 & 🧹, 🔄 🚫🔜 📉 🕳. - -⚫️ 🔜 ✊ 🎏 💸 🕰 🏁 ⏮️ ⚖️ 🍵 🔄 (🛠️) & 👆 🔜 ✔️ ⌛ 🎏 💸 👷. - -✋️ 👉 💼, 🚥 👆 💪 ✊️ 8️⃣ 👰-🏧/🍳/🔜-🧹, & 🔠 1️⃣ 👫 (➕ 👆) 💪 ✊ 🏒 🏠 🧹 ⚫️, 👆 💪 🌐 👷 **🔗**, ⏮️ ➕ ℹ, & 🏁 🌅 🔜. - -👉 😐, 🔠 1️⃣ 🧹 (🔌 👆) 🔜 🕹, 🤸 👫 🍕 👨‍🏭. - -& 🏆 🛠️ 🕰 ✊ ☑ 👷 (↩️ ⌛), & 👷 💻 ⌛ 💽, 👫 🤙 👫 ⚠ "💽 🎁". - ---- - -⚠ 🖼 💽 🔗 🛠️ 👜 👈 🚚 🏗 🧪 🏭. - -🖼: - -* **🎧** ⚖️ **🖼 🏭**. -* **💻 👓**: 🖼 ✍ 💯 🔅, 🔠 🔅 ✔️ 3️⃣ 💲 / 🎨, 🏭 👈 🛎 🚚 💻 🕳 🔛 📚 🔅, 🌐 🎏 🕰. -* **🎰 🏫**: ⚫️ 🛎 🚚 📚 "✖" & "🖼" ✖. 💭 🦏 📋 ⏮️ 🔢 & ✖ 🌐 👫 👯‍♂️ 🎏 🕰. -* **⏬ 🏫**: 👉 🎧-🏑 🎰 🏫,, 🎏 ✔. ⚫️ 👈 📤 🚫 👁 📋 🔢 ✖, ✋️ 🦏 ⚒ 👫, & 📚 💼, 👆 ⚙️ 🎁 🕹 🏗 & / ⚖️ ⚙️ 👈 🏷. - -### 🛠️ ➕ 🔁: 🕸 ➕ 🎰 🏫 - -⏮️ **FastAPI** 👆 💪 ✊ 📈 🛠️ 👈 📶 ⚠ 🕸 🛠️ (🎏 👑 🧲 ✳). - -✋️ 👆 💪 🐄 💰 🔁 & 💾 (✔️ 💗 🛠️ 🏃‍♂ 🔗) **💽 🎁** ⚖ 💖 👈 🎰 🏫 ⚙️. - -👈, ➕ 🙅 👐 👈 🐍 👑 🇪🇸 **💽 🧪**, 🎰 🏫 & ✴️ ⏬ 🏫, ⚒ FastAPI 📶 👍 🏏 💽 🧪 / 🎰 🏫 🕸 🔗 & 🈸 (👪 📚 🎏). - -👀 ❔ 🏆 👉 🔁 🏭 👀 📄 🔃 [🛠️](deployment/index.md){.internal-link target=_blank}. - -## `async` & `await` - -🏛 ⏬ 🐍 ✔️ 📶 🏋️ 🌌 🔬 🔁 📟. 👉 ⚒ ⚫️ 👀 💖 😐 "🔁" 📟 & "⌛" 👆 ▶️️ 🙍. - -🕐❔ 📤 🛠️ 👈 🔜 🚚 ⌛ ⏭ 🤝 🏁 & ✔️ 🐕‍🦺 👉 🆕 🐍 ⚒, 👆 💪 📟 ⚫️ 💖: - -```Python -burgers = await get_burgers(2) -``` - -🔑 📥 `await`. ⚫️ 💬 🐍 👈 ⚫️ ✔️ ⌛ ⏸ `get_burgers(2)` 🏁 🔨 🚮 👜 👶 ⏭ ♻ 🏁 `burgers`. ⏮️ 👈, 🐍 🔜 💭 👈 ⚫️ 💪 🚶 & 🕳 🙆 👶 👶 👐 (💖 📨 ➕1️⃣ 📨). - -`await` 👷, ⚫️ ✔️ 🔘 🔢 👈 🐕‍🦺 👉 🔀. 👈, 👆 📣 ⚫️ ⏮️ `async def`: - -```Python hl_lines="1" -async def get_burgers(number: int): - # Do some asynchronous stuff to create the burgers - return burgers -``` - -...↩️ `def`: - -```Python hl_lines="2" -# This is not asynchronous -def get_sequential_burgers(number: int): - # Do some sequential stuff to create the burgers - return burgers -``` - -⏮️ `async def`, 🐍 💭 👈, 🔘 👈 🔢, ⚫️ ✔️ 🤔 `await` 🧬, & 👈 ⚫️ 💪 "⏸" ⏸ 🛠️ 👈 🔢 & 🚶 🕳 🙆 👶 ⏭ 👟 🔙. - -🕐❔ 👆 💚 🤙 `async def` 🔢, 👆 ✔️ "⌛" ⚫️. , 👉 🏆 🚫 👷: - -```Python -# This won't work, because get_burgers was defined with: async def -burgers = get_burgers(2) -``` - ---- - -, 🚥 👆 ⚙️ 🗃 👈 💬 👆 👈 👆 💪 🤙 ⚫️ ⏮️ `await`, 👆 💪 ✍ *➡ 🛠️ 🔢* 👈 ⚙️ ⚫️ ⏮️ `async def`, 💖: - -```Python hl_lines="2-3" -@app.get('/burgers') -async def read_burgers(): - burgers = await get_burgers(2) - return burgers -``` - -### 🌅 📡 ℹ - -👆 💪 ✔️ 👀 👈 `await` 💪 🕴 ⚙️ 🔘 🔢 🔬 ⏮️ `async def`. - -✋️ 🎏 🕰, 🔢 🔬 ⏮️ `async def` ✔️ "⌛". , 🔢 ⏮️ `async def` 💪 🕴 🤙 🔘 🔢 🔬 ⏮️ `async def` 💁‍♂️. - -, 🔃 🥚 & 🐔, ❔ 👆 🤙 🥇 `async` 🔢 ❓ - -🚥 👆 👷 ⏮️ **FastAPI** 👆 🚫 ✔️ 😟 🔃 👈, ↩️ 👈 "🥇" 🔢 🔜 👆 *➡ 🛠️ 🔢*, & FastAPI 🔜 💭 ❔ ▶️️ 👜. - -✋️ 🚥 👆 💚 ⚙️ `async` / `await` 🍵 FastAPI, 👆 💪 ⚫️ 👍. - -### ✍ 👆 👍 🔁 📟 - -💃 (& **FastAPI**) ⚓️ 🔛 AnyIO, ❔ ⚒ ⚫️ 🔗 ⏮️ 👯‍♂️ 🐍 🐩 🗃 & 🎻. - -🎯, 👆 💪 🔗 ⚙️ AnyIO 👆 🏧 🛠️ ⚙️ 💼 👈 🚚 🌅 🏧 ⚓ 👆 👍 📟. - -& 🚥 👆 🚫 ⚙️ FastAPI, 👆 💪 ✍ 👆 👍 🔁 🈸 ⏮️ AnyIO 🏆 🔗 & 🤚 🚮 💰 (✅ *📊 🛠️*). - -### 🎏 📨 🔁 📟 - -👉 👗 ⚙️ `async` & `await` 📶 🆕 🇪🇸. - -✋️ ⚫️ ⚒ 👷 ⏮️ 🔁 📟 📚 ⏩. - -👉 🎏 ❕ (⚖️ 🌖 🌓) 🔌 ⏳ 🏛 ⏬ 🕸 (🖥 & ✳). - -✋️ ⏭ 👈, 🚚 🔁 📟 🌖 🏗 & ⚠. - -⏮️ ⏬ 🐍, 👆 💪 ✔️ ⚙️ 🧵 ⚖️ 🐁. ✋️ 📟 🌌 🌖 🏗 🤔, ℹ, & 💭 🔃. - -⏮️ ⏬ ✳ / 🖥 🕸, 👆 🔜 ✔️ ⚙️ "⏲". ❔ ↘️ "⏲ 🔥😈". - -## 🔁 - -**🔁** 📶 🎀 ⚖ 👜 📨 `async def` 🔢. 🐍 💭 👈 ⚫️ 🕳 💖 🔢 👈 ⚫️ 💪 ▶️ & 👈 ⚫️ 🔜 🔚 ☝, ✋️ 👈 ⚫️ 5️⃣📆 ⏸ ⏸ 🔘 💁‍♂️, 🕐❔ 📤 `await` 🔘 ⚫️. - -✋️ 🌐 👉 🛠️ ⚙️ 🔁 📟 ⏮️ `async` & `await` 📚 🕰 🔬 ⚙️ "🔁". ⚫️ ⭐ 👑 🔑 ⚒ 🚶, "🔁". - -## 🏁 - -➡️ 👀 🎏 🔤 ⚪️➡️ 🔛: - -> 🏛 ⏬ 🐍 ✔️ 🐕‍🦺 **"🔁 📟"** ⚙️ 🕳 🤙 **"🔁"**, ⏮️ **`async` & `await`** ❕. - -👈 🔜 ⚒ 🌅 🔑 🔜. 👶 - -🌐 👈 ⚫️❔ 🏋️ FastAPI (🔘 💃) & ⚫️❔ ⚒ ⚫️ ✔️ ✅ 🎆 🎭. - -## 📶 📡 ℹ - -/// warning - -👆 💪 🎲 🚶 👉. - -👉 📶 📡 ℹ ❔ **FastAPI** 👷 🔘. - -🚥 👆 ✔️ 📡 💡 (🈶-🏋, 🧵, 🍫, ♒️.) & 😟 🔃 ❔ FastAPI 🍵 `async def` 🆚 😐 `def`, 🚶 ⤴️. - -/// - -### ➡ 🛠️ 🔢 - -🕐❔ 👆 📣 *➡ 🛠️ 🔢* ⏮️ 😐 `def` ↩️ `async def`, ⚫️ 🏃 🔢 🧵 👈 ⤴️ ⌛, ↩️ ➖ 🤙 🔗 (⚫️ 🔜 🍫 💽). - -🚥 👆 👟 ⚪️➡️ ➕1️⃣ 🔁 🛠️ 👈 🔨 🚫 👷 🌌 🔬 🔛 & 👆 ⚙️ ⚖ 🙃 📊-🕴 *➡ 🛠️ 🔢* ⏮️ ✅ `def` 🤪 🎭 📈 (🔃 1️⃣0️⃣0️⃣ 💓), 🙏 🗒 👈 **FastAPI** ⭐ 🔜 🔄. 👫 💼, ⚫️ 👻 ⚙️ `async def` 🚥 👆 *➡ 🛠️ 🔢* ⚙️ 📟 👈 🎭 🚧 👤/🅾. - -, 👯‍♂️ ⚠, 🤞 👈 **FastAPI** 🔜 [⏩](index.md#_15){.internal-link target=_blank} 🌘 (⚖️ 🌘 ⭐) 👆 ⏮️ 🛠️. - -### 🔗 - -🎏 ✔ [🔗](tutorial/dependencies/index.md){.internal-link target=_blank}. 🚥 🔗 🐩 `def` 🔢 ↩️ `async def`, ⚫️ 🏃 🔢 🧵. - -### 🎧-🔗 - -👆 💪 ✔️ 💗 🔗 & [🎧-🔗](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} 🚫 🔠 🎏 (🔢 🔢 🔑), 👫 💪 ✍ ⏮️ `async def` & ⏮️ 😐 `def`. ⚫️ 🔜 👷, & 🕐 ✍ ⏮️ 😐 `def` 🔜 🤙 🔛 🔢 🧵 (⚪️➡️ 🧵) ↩️ ➖ "⌛". - -### 🎏 🚙 🔢 - -🙆 🎏 🚙 🔢 👈 👆 🤙 🔗 💪 ✍ ⏮️ 😐 `def` ⚖️ `async def` & FastAPI 🏆 🚫 📉 🌌 👆 🤙 ⚫️. - -👉 🔅 🔢 👈 FastAPI 🤙 👆: *➡ 🛠️ 🔢* & 🔗. - -🚥 👆 🚙 🔢 😐 🔢 ⏮️ `def`, ⚫️ 🔜 🤙 🔗 (👆 ✍ ⚫️ 👆 📟), 🚫 🧵, 🚥 🔢 ✍ ⏮️ `async def` ⤴️ 👆 🔜 `await` 👈 🔢 🕐❔ 👆 🤙 ⚫️ 👆 📟. - ---- - -🔄, 👉 📶 📡 ℹ 👈 🔜 🎲 ⚠ 🚥 👆 👟 🔎 👫. - -⏪, 👆 🔜 👍 ⏮️ 📄 ⚪️➡️ 📄 🔛: 🏃 ❓. diff --git a/docs/em/docs/benchmarks.md b/docs/em/docs/benchmarks.md deleted file mode 100644 index 003c3f62de..0000000000 --- a/docs/em/docs/benchmarks.md +++ /dev/null @@ -1,34 +0,0 @@ -# 📇 - -🔬 🇸🇲 📇 🎦 **FastAPI** 🈸 🏃‍♂ 🔽 Uvicorn 1️⃣ ⏩ 🐍 🛠️ 💪, 🕴 🔛 💃 & Uvicorn 👫 (⚙️ 🔘 FastAPI). (*) - -✋️ 🕐❔ ✅ 📇 & 🔺 👆 🔜 ✔️ 📄 🤯. - -## 📇 & 🚅 - -🕐❔ 👆 ✅ 📇, ⚫️ ⚠ 👀 📚 🧰 🎏 🆎 🔬 🌓. - -🎯, 👀 Uvicorn, 💃 & FastAPI 🔬 👯‍♂️ (👪 📚 🎏 🧰). - -🙅 ⚠ ❎ 🧰, 👍 🎭 ⚫️ 🔜 🤚. & 🏆 📇 🚫 💯 🌖 ⚒ 🚚 🧰. - -🔗 💖: - -* **Uvicorn**: 🔫 💽 - * **💃**: (⚙️ Uvicorn) 🕸 🕸 - * **FastAPI**: (⚙️ 💃) 🛠️ 🕸 ⏮️ 📚 🌖 ⚒ 🏗 🔗, ⏮️ 💽 🔬, ♒️. - -* **Uvicorn**: - * 🔜 ✔️ 🏆 🎭, ⚫️ 🚫 ✔️ 🌅 ➕ 📟 ↖️ ⚪️➡️ 💽 ⚫️. - * 👆 🚫🔜 ✍ 🈸 Uvicorn 🔗. 👈 🔜 ⛓ 👈 👆 📟 🔜 ✔️ 🔌 🌖 ⚖️ 🌘, 🌘, 🌐 📟 🚚 💃 (⚖️ **FastAPI**). & 🚥 👆 👈, 👆 🏁 🈸 🔜 ✔️ 🎏 🌥 ✔️ ⚙️ 🛠️ & 📉 👆 📱 📟 & 🐛. - * 🚥 👆 ⚖ Uvicorn, 🔬 ⚫️ 🛡 👸, Hypercorn, ✳, ♒️. 🈸 💽. -* **💃**: - * 🔜 ✔️ ⏭ 🏆 🎭, ⏮️ Uvicorn. 👐, 💃 ⚙️ Uvicorn 🏃. , ⚫️ 🎲 💪 🕴 🤚 "🐌" 🌘 Uvicorn ✔️ 🛠️ 🌅 📟. - * ✋️ ⚫️ 🚚 👆 🧰 🏗 🙅 🕸 🈸, ⏮️ 🕹 ⚓️ 🔛 ➡, ♒️. - * 🚥 👆 ⚖ 💃, 🔬 ⚫️ 🛡 🤣, 🏺, ✳, ♒️. 🕸 🛠️ (⚖️ 🕸). -* **FastAPI**: - * 🎏 🌌 👈 💃 ⚙️ Uvicorn & 🚫🔜 ⏩ 🌘 ⚫️, **FastAPI** ⚙️ 💃, ⚫️ 🚫🔜 ⏩ 🌘 ⚫️. - * FastAPI 🚚 🌅 ⚒ 🔛 🔝 💃. ⚒ 👈 👆 🌖 🕧 💪 🕐❔ 🏗 🔗, 💖 💽 🔬 & 🛠️. & ⚙️ ⚫️, 👆 🤚 🏧 🧾 🆓 (🏧 🧾 🚫 🚮 🌥 🏃‍♂ 🈸, ⚫️ 🏗 🔛 🕴). - * 🚥 👆 🚫 ⚙️ FastAPI & ⚙️ 💃 🔗 (⚖️ ➕1️⃣ 🧰, 💖 🤣, 🏺, 🆘, ♒️) 👆 🔜 ✔️ 🛠️ 🌐 💽 🔬 & 🛠️ 👆. , 👆 🏁 🈸 🔜 ✔️ 🎏 🌥 🚥 ⚫️ 🏗 ⚙️ FastAPI. & 📚 💼, 👉 💽 🔬 & 🛠️ 🦏 💸 📟 ✍ 🈸. - * , ⚙️ FastAPI 👆 ♻ 🛠️ 🕰, 🐛, ⏸ 📟, & 👆 🔜 🎲 🤚 🎏 🎭 (⚖️ 👍) 👆 🔜 🚥 👆 🚫 ⚙️ ⚫️ (👆 🔜 ✔️ 🛠️ ⚫️ 🌐 👆 📟). - * 🚥 👆 ⚖ FastAPI, 🔬 ⚫️ 🛡 🕸 🈸 🛠️ (⚖️ ⚒ 🧰) 👈 🚚 💽 🔬, 🛠️ & 🧾, 💖 🏺-apispec, NestJS, ♨, ♒️. 🛠️ ⏮️ 🛠️ 🏧 💽 🔬, 🛠️ & 🧾. diff --git a/docs/em/docs/deployment/concepts.md b/docs/em/docs/deployment/concepts.md deleted file mode 100644 index bbb017277c..0000000000 --- a/docs/em/docs/deployment/concepts.md +++ /dev/null @@ -1,323 +0,0 @@ -# 🛠️ 🔧 - -🕐❔ 🛠️ **FastAPI** 🈸, ⚖️ 🤙, 🙆 🆎 🕸 🛠️, 📤 📚 🔧 👈 👆 🎲 💅 🔃, & ⚙️ 👫 👆 💪 🔎 **🏆 ☑** 🌌 **🛠️ 👆 🈸**. - -⚠ 🔧: - -* 💂‍♂ - 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* 🧬 (🔢 🛠️ 🏃) -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -👥 🔜 👀 ❔ 👫 🔜 📉 **🛠️**. - -🔚, 🏆 🎯 💪 **🍦 👆 🛠️ 👩‍💻** 🌌 👈 **🔐**, **❎ 📉**, & ⚙️ **📊 ℹ** (🖼 🛰 💽/🕹 🎰) ♻ 💪. 👶 - -👤 🔜 💬 👆 🍖 🌖 🔃 👫 **🔧** 📥, & 👈 🔜 🤞 🤝 👆 **🤔** 👆 🔜 💪 💭 ❔ 🛠️ 👆 🛠️ 📶 🎏 🌐, 🎲 **🔮** 🕐 👈 🚫 🔀. - -🤔 👫 🔧, 👆 🔜 💪 **🔬 & 🔧** 🏆 🌌 🛠️ **👆 👍 🔗**. - -⏭ 📃, 👤 🔜 🤝 👆 🌅 **🧱 🍮** 🛠️ FastAPI 🈸. - -✋️ 🔜, ➡️ ✅ 👉 ⚠ **⚛ 💭**. 👫 🔧 ✔ 🙆 🎏 🆎 🕸 🛠️. 👶 - -## 💂‍♂ - 🇺🇸🔍 - -[⏮️ 📃 🔃 🇺🇸🔍](https.md){.internal-link target=_blank} 👥 🇭🇲 🔃 ❔ 🇺🇸🔍 🚚 🔐 👆 🛠️. - -👥 👀 👈 🇺🇸🔍 🛎 🚚 🦲 **🔢** 👆 🈸 💽, **🤝 ❎ 🗳**. - -& 📤 ✔️ 🕳 🈚 **♻ 🇺🇸🔍 📄**, ⚫️ 💪 🎏 🦲 ⚖️ ⚫️ 💪 🕳 🎏. - -### 🖼 🧰 🇺🇸🔍 - -🧰 👆 💪 ⚙️ 🤝 ❎ 🗳: - -* Traefik - * 🔁 🍵 📄 🔕 👶 -* 📥 - * 🔁 🍵 📄 🔕 👶 -* 👌 - * ⏮️ 🔢 🦲 💖 Certbot 📄 🔕 -* ✳ - * ⏮️ 🔢 🦲 💖 Certbot 📄 🔕 -* Kubernetes ⏮️ 🚧 🕹 💖 👌 - * ⏮️ 🔢 🦲 💖 🛂-👨‍💼 📄 🔕 -* 🍵 🔘 ☁ 🐕‍🦺 🍕 👫 🐕‍🦺 (✍ 🔛 👶) - -➕1️⃣ 🎛 👈 👆 💪 ⚙️ **☁ 🐕‍🦺** 👈 🔨 🌖 👷 ✅ ⚒ 🆙 🇺🇸🔍. ⚫️ 💪 ✔️ 🚫 ⚖️ 🈚 👆 🌅, ♒️. ✋️ 👈 💼, 👆 🚫🔜 ✔️ ⚒ 🆙 🤝 ❎ 🗳 👆. - -👤 🔜 🎦 👆 🧱 🖼 ⏭ 📃. - ---- - -⤴️ ⏭ 🔧 🤔 🌐 🔃 📋 🏃 👆 ☑ 🛠️ (✅ Uvicorn). - -## 📋 & 🛠️ - -👥 🔜 💬 📚 🔃 🏃 "**🛠️**", ⚫️ ⚠ ✔️ ☯ 🔃 ⚫️❔ ⚫️ ⛓, & ⚫️❔ 🔺 ⏮️ 🔤 "**📋**". - -### ⚫️❔ 📋 - -🔤 **📋** 🛎 ⚙️ 🔬 📚 👜: - -* **📟** 👈 👆 ✍, **🐍 📁**. -* **📁** 👈 💪 **🛠️** 🏃‍♂ ⚙️, 🖼: `python`, `python.exe` ⚖️ `uvicorn`. -* 🎯 📋 ⏪ ⚫️ **🏃‍♂** 🔛 🏗 ⚙️, ⚙️ 💽, & ♻ 👜 🔛 💾. 👉 🤙 **🛠️**. - -### ⚫️❔ 🛠️ - -🔤 **🛠️** 🛎 ⚙️ 🌖 🎯 🌌, 🕴 🔗 👜 👈 🏃 🏃‍♂ ⚙️ (💖 🏁 ☝ 🔛): - -* 🎯 📋 ⏪ ⚫️ **🏃‍♂** 🔛 🏃‍♂ ⚙️. - * 👉 🚫 🔗 📁, 🚫 📟, ⚫️ 🔗 **🎯** 👜 👈 ➖ **🛠️** & 🔄 🏃‍♂ ⚙️. -* 🙆 📋, 🙆 📟, **💪 🕴 👜** 🕐❔ ⚫️ ➖ **🛠️**. , 🕐❔ 📤 **🛠️ 🏃**. -* 🛠️ 💪 **❎** (⚖️ "💥") 👆, ⚖️ 🏃‍♂ ⚙️. 👈 ☝, ⚫️ ⛔️ 🏃/➖ 🛠️, & ⚫️ 💪 **🙅‍♂ 📏 👜**. -* 🔠 🈸 👈 👆 ✔️ 🏃 🔛 👆 💻 ✔️ 🛠️ ⛅ ⚫️, 🔠 🏃‍♂ 📋, 🔠 🚪, ♒️. & 📤 🛎 📚 🛠️ 🏃 **🎏 🕰** ⏪ 💻 🔛. -* 📤 💪 **💗 🛠️** **🎏 📋** 🏃 🎏 🕰. - -🚥 👆 ✅ 👅 "📋 👨‍💼" ⚖️ "⚙️ 🖥" (⚖️ 🎏 🧰) 👆 🏃‍♂ ⚙️, 👆 🔜 💪 👀 📚 👈 🛠️ 🏃‍♂. - -& , 🖼, 👆 🔜 🎲 👀 👈 📤 💗 🛠️ 🏃 🎏 🖥 📋 (🦎, 💄, 📐, ♒️). 👫 🛎 🏃 1️⃣ 🛠️ 📍 📑, ➕ 🎏 ➕ 🛠️. - - - ---- - -🔜 👈 👥 💭 🔺 🖖 ⚖ **🛠️** & **📋**, ➡️ 😣 💬 🔃 🛠️. - -## 🏃‍♂ 🔛 🕴 - -🌅 💼, 🕐❔ 👆 ✍ 🕸 🛠️, 👆 💚 ⚫️ **🕧 🏃‍♂**, ➡, 👈 👆 👩‍💻 💪 🕧 🔐 ⚫️. 👉 ↗️, 🚥 👆 ✔️ 🎯 🤔 ⚫️❔ 👆 💚 ⚫️ 🏃 🕴 🎯 ⚠, ✋️ 🌅 🕰 👆 💚 ⚫️ 🕧 🏃‍♂ & **💪**. - -### 🛰 💽 - -🕐❔ 👆 ⚒ 🆙 🛰 💽 (☁ 💽, 🕹 🎰, ♒️.) 🙅 👜 👆 💪 🏃 Uvicorn (⚖️ 🎏) ❎, 🎏 🌌 👆 🕐❔ 🛠️ 🌐. - -& ⚫️ 🔜 👷 & 🔜 ⚠ **⏮️ 🛠️**. - -✋️ 🚥 👆 🔗 💽 💸, **🏃‍♂ 🛠️** 🔜 🎲 ☠️. - -& 🚥 💽 ⏏ (🖼 ⏮️ ℹ, ⚖️ 🛠️ ⚪️➡️ ☁ 🐕‍🦺) 👆 🎲 **🏆 🚫 👀 ⚫️**. & ↩️ 👈, 👆 🏆 🚫 💭 👈 👆 ✔️ ⏏ 🛠️ ❎. , 👆 🛠️ 🔜 🚧 ☠️. 👶 - -### 🏃 🔁 🔛 🕴 - -🏢, 👆 🔜 🎲 💚 💽 📋 (✅ Uvicorn) ▶️ 🔁 🔛 💽 🕴, & 🍵 💪 🙆 **🗿 🏥**, ✔️ 🛠️ 🕧 🏃 ⏮️ 👆 🛠️ (✅ Uvicorn 🏃‍♂ 👆 FastAPI 📱). - -### 🎏 📋 - -🏆 👉, 👆 🔜 🛎 ✔️ **🎏 📋** 👈 🔜 ⚒ 💭 👆 🈸 🏃 🔛 🕴. & 📚 💼, ⚫️ 🔜 ⚒ 💭 🎏 🦲 ⚖️ 🈸 🏃, 🖼, 💽. - -### 🖼 🧰 🏃 🕴 - -🖼 🧰 👈 💪 👉 👨‍🏭: - -* ☁ -* Kubernetes -* ☁ ✍ -* ☁ 🐝 📳 -* ✳ -* 👨‍💻 -* 🍵 🔘 ☁ 🐕‍🦺 🍕 👫 🐕‍🦺 -* 🎏... - -👤 🔜 🤝 👆 🌅 🧱 🖼 ⏭ 📃. - -## ⏏ - -🎏 ⚒ 💭 👆 🈸 🏃 🔛 🕴, 👆 🎲 💚 ⚒ 💭 ⚫️ **⏏** ⏮️ ❌. - -### 👥 ⚒ ❌ - -👥, 🗿, ⚒ **❌**, 🌐 🕰. 🖥 🌖 *🕧* ✔️ **🐛** 🕵‍♂ 🎏 🥉. 👶 - -& 👥 👩‍💻 🚧 📉 📟 👥 🔎 👈 🐛 & 👥 🛠️ 🆕 ⚒ (🎲 ❎ 🆕 🐛 💁‍♂️ 👶). - -### 🤪 ❌ 🔁 🍵 - -🕐❔ 🏗 🕸 🔗 ⏮️ FastAPI, 🚥 📤 ❌ 👆 📟, FastAPI 🔜 🛎 🔌 ⚫️ 👁 📨 👈 ⏲ ❌. 🛡 - -👩‍💻 🔜 🤚 **5️⃣0️⃣0️⃣ 🔗 💽 ❌** 👈 📨, ✋️ 🈸 🔜 😣 👷 ⏭ 📨 ↩️ 💥 🍕. - -### 🦏 ❌ - 💥 - -👐, 📤 5️⃣📆 💼 🌐❔ 👥 ✍ 📟 👈 **💥 🎂 🈸** ⚒ Uvicorn & 🐍 💥. 👶 - -& , 👆 🔜 🎲 🚫 💚 🈸 🚧 ☠️ ↩️ 📤 ❌ 1️⃣ 🥉, 👆 🎲 💚 ⚫️ **😣 🏃** 🌘 *➡ 🛠️* 👈 🚫 💔. - -### ⏏ ⏮️ 💥 - -✋️ 👈 💼 ⏮️ 🤙 👎 ❌ 👈 💥 🏃‍♂ **🛠️**, 👆 🔜 💚 🔢 🦲 👈 🈚 **🔁** 🛠️, 🌘 👩‍❤‍👨 🕰... - -/// tip - -...👐 🚥 🎂 🈸 **💥 ⏪** ⚫️ 🎲 🚫 ⚒ 🔑 🚧 🔁 ⚫️ ♾. ✋️ 📚 💼, 👆 🔜 🎲 👀 ⚫️ ⏮️ 🛠️, ⚖️ 🌘 ▶️️ ⏮️ 🛠️. - -➡️ 🎯 🔛 👑 💼, 🌐❔ ⚫️ 💪 💥 🍕 🎯 💼 **🔮**, & ⚫️ ⚒ 🔑 ⏏ ⚫️. - -/// - -👆 🔜 🎲 💚 ✔️ 👜 🈚 🔁 👆 🈸 **🔢 🦲**, ↩️ 👈 ☝, 🎏 🈸 ⏮️ Uvicorn & 🐍 ⏪ 💥, 📤 🕳 🎏 📟 🎏 📱 👈 💪 🕳 🔃 ⚫️. - -### 🖼 🧰 ⏏ 🔁 - -🏆 💼, 🎏 🧰 👈 ⚙️ **🏃 📋 🔛 🕴** ⚙️ 🍵 🏧 **⏏**. - -🖼, 👉 💪 🍵: - -* ☁ -* Kubernetes -* ☁ ✍ -* ☁ 🐝 📳 -* ✳ -* 👨‍💻 -* 🍵 🔘 ☁ 🐕‍🦺 🍕 👫 🐕‍🦺 -* 🎏... - -## 🧬 - 🛠️ & 💾 - -⏮️ FastAPI 🈸, ⚙️ 💽 📋 💖 Uvicorn, 🏃‍♂ ⚫️ 🕐 **1️⃣ 🛠️** 💪 🍦 💗 👩‍💻 🔁. - -✋️ 📚 💼, 👆 🔜 💚 🏃 📚 👨‍🏭 🛠️ 🎏 🕰. - -### 💗 🛠️ - 👨‍🏭 - -🚥 👆 ✔️ 🌅 👩‍💻 🌘 ⚫️❔ 👁 🛠️ 💪 🍵 (🖼 🚥 🕹 🎰 🚫 💁‍♂️ 🦏) & 👆 ✔️ **💗 🐚** 💽 💽, ⤴️ 👆 💪 ✔️ **💗 🛠️** 🏃‍♂ ⏮️ 🎏 🈸 🎏 🕰, & 📎 🌐 📨 👪 👫. - -🕐❔ 👆 🏃 **💗 🛠️** 🎏 🛠️ 📋, 👫 🛎 🤙 **👨‍🏭**. - -### 👨‍🏭 🛠️ & ⛴ - -💭 ⚪️➡️ 🩺 [🔃 🇺🇸🔍](https.md){.internal-link target=_blank} 👈 🕴 1️⃣ 🛠️ 💪 👂 🔛 1️⃣ 🌀 ⛴ & 📢 📢 💽 ❓ - -👉 ☑. - -, 💪 ✔️ **💗 🛠️** 🎏 🕰, 📤 ✔️ **👁 🛠️ 👂 🔛 ⛴** 👈 ⤴️ 📶 📻 🔠 👨‍🏭 🛠️ 🌌. - -### 💾 📍 🛠️ - -🔜, 🕐❔ 📋 📐 👜 💾, 🖼, 🎰 🏫 🏷 🔢, ⚖️ 🎚 ⭕ 📁 🔢, 🌐 👈 **🍴 👄 💾 (💾)** 💽. - -& 💗 🛠️ 🛎 **🚫 💰 🙆 💾**. 👉 ⛓ 👈 🔠 🏃 🛠️ ✔️ 🚮 👍 👜, 🔢, & 💾. & 🚥 👆 😩 ⭕ 💸 💾 👆 📟, **🔠 🛠️** 🔜 🍴 🌓 💸 💾. - -### 💽 💾 - -🖼, 🚥 👆 📟 📐 🎰 🏫 🏷 ⏮️ **1️⃣ 💾 📐**, 🕐❔ 👆 🏃 1️⃣ 🛠️ ⏮️ 👆 🛠️, ⚫️ 🔜 🍴 🌘 1️⃣ 💾 💾. & 🚥 👆 ▶️ **4️⃣ 🛠️** (4️⃣ 👨‍🏭), 🔠 🔜 🍴 1️⃣ 💾 💾. 🌐, 👆 🛠️ 🔜 🍴 **4️⃣ 💾 💾**. - -& 🚥 👆 🛰 💽 ⚖️ 🕹 🎰 🕴 ✔️ 3️⃣ 💾 💾, 🔄 📐 🌅 🌘 4️⃣ 💾 💾 🔜 🤕 ⚠. 👶 - -### 💗 🛠️ - 🖼 - -👉 🖼, 📤 **👨‍💼 🛠️** 👈 ▶️ & 🎛 2️⃣ **👨‍🏭 🛠️**. - -👉 👨‍💼 🛠️ 🔜 🎲 1️⃣ 👂 🔛 **⛴** 📢. & ⚫️ 🔜 📶 🌐 📻 👨‍🏭 🛠️. - -👈 👨‍🏭 🛠️ 🔜 🕐 🏃‍♂ 👆 🈸, 👫 🔜 🎭 👑 📊 📨 **📨** & 📨 **📨**, & 👫 🔜 📐 🕳 👆 🚮 🔢 💾. - - - -& ↗️, 🎏 🎰 🔜 🎲 ✔️ **🎏 🛠️** 🏃 👍, ↖️ ⚪️➡️ 👆 🈸. - -😌 ℹ 👈 🌐 **💽 ⚙️** 🔠 🛠️ 💪 **🪀** 📚 🤭 🕰, ✋️ **💾 (💾)** 🛎 🚧 🌖 ⚖️ 🌘 **⚖**. - -🚥 👆 ✔️ 🛠️ 👈 🔨 ⭐ 💸 📊 🔠 🕰 & 👆 ✔️ 📚 👩‍💻, ⤴️ **💽 🛠️** 🔜 🎲 *⚖* (↩️ 🕧 🔜 🆙 & 🔽 🔜). - -### 🖼 🧬 🧰 & 🎛 - -📤 💪 📚 🎯 🏆 👉, & 👤 🔜 💬 👆 🌅 🔃 🎯 🎛 ⏭ 📃, 🖼 🕐❔ 💬 🔃 ☁ & 📦. - -👑 ⚛ 🤔 👈 📤 ✔️ **👁** 🦲 🚚 **⛴** **📢 📢**. & ⤴️ ⚫️ ✔️ ✔️ 🌌 **📶** 📻 🔁 **🛠️/👨‍🏭**. - -📥 💪 🌀 & 🎛: - -* **🐁** 🛠️ **Uvicorn 👨‍🏭** - * 🐁 🔜 **🛠️ 👨‍💼** 👂 🔛 **📢** & **⛴**, 🧬 🔜 ✔️ **💗 Uvicorn 👨‍🏭 🛠️** -* **Uvicorn** 🛠️ **Uvicorn 👨‍🏭** - * 1️⃣ Uvicorn **🛠️ 👨‍💼** 🔜 👂 🔛 **📢** & **⛴**, & ⚫️ 🔜 ▶️ **💗 Uvicorn 👨‍🏭 🛠️** -* **Kubernetes** & 🎏 📎 **📦 ⚙️** - * 🕳 **☁** 🧽 🔜 👂 🔛 **📢** & **⛴**. 🧬 🔜 ✔️ **💗 📦**, 🔠 ⏮️ **1️⃣ Uvicorn 🛠️** 🏃‍♂ -* **☁ 🐕‍🦺** 👈 🍵 👉 👆 - * ☁ 🐕‍🦺 🔜 🎲 **🍵 🧬 👆**. ⚫️ 🔜 🎲 ➡️ 👆 🔬 **🛠️ 🏃**, ⚖️ **📦 🖼** ⚙️, 🙆 💼, ⚫️ 🔜 🌅 🎲 **👁 Uvicorn 🛠️**, & ☁ 🐕‍🦺 🔜 🈚 🔁 ⚫️. - -/// tip - -🚫 😟 🚥 👫 🏬 🔃 **📦**, ☁, ⚖️ Kubernetes 🚫 ⚒ 📚 🔑. - -👤 🔜 💬 👆 🌅 🔃 📦 🖼, ☁, Kubernetes, ♒️. 🔮 📃: [FastAPI 📦 - ☁](docker.md){.internal-link target=_blank}. - -/// - -## ⏮️ 🔁 ⏭ ▶️ - -📤 📚 💼 🌐❔ 👆 💚 🎭 📶 **⏭ ▶️** 👆 🈸. - -🖼, 👆 💪 💚 🏃 **💽 🛠️**. - -✋️ 🌅 💼, 👆 🔜 💚 🎭 👉 🔁 🕴 **🕐**. - -, 👆 🔜 💚 ✔️ **👁 🛠️** 🎭 👈 **⏮️ 🔁**, ⏭ ▶️ 🈸. - -& 👆 🔜 ✔️ ⚒ 💭 👈 ⚫️ 👁 🛠️ 🏃 👈 ⏮️ 🔁 ** 🚥 ⏮️, 👆 ▶️ **💗 🛠️** (💗 👨‍🏭) 🈸 ⚫️. 🚥 👈 🔁 🏃 **💗 🛠️**, 👫 🔜 **❎** 👷 🏃‍♂ ⚫️ 🔛 **🔗**, & 🚥 📶 🕳 💎 💖 💽 🛠️, 👫 💪 🤕 ⚔ ⏮️ 🔠 🎏. - -↗️, 📤 💼 🌐❔ 📤 🙅‍♂ ⚠ 🏃 ⏮️ 🔁 💗 🕰, 👈 💼, ⚫️ 📚 ⏩ 🍵. - -/// tip - -, ✔️ 🤯 👈 ⚓️ 🔛 👆 🖥, 💼 👆 **5️⃣📆 🚫 💪 🙆 ⏮️ 🔁** ⏭ ▶️ 👆 🈸. - -👈 💼, 👆 🚫🔜 ✔️ 😟 🔃 🙆 👉. 🤷 - -/// - -### 🖼 ⏮️ 🔁 🎛 - -👉 🔜 **🪀 🙇** 🔛 🌌 👆 **🛠️ 👆 ⚙️**, & ⚫️ 🔜 🎲 🔗 🌌 👆 ▶️ 📋, 🚚 ⏏, ♒️. - -📥 💪 💭: - -* "🕑 📦" Kubernetes 👈 🏃 ⏭ 👆 📱 📦 -* 🎉 ✍ 👈 🏃 ⏮️ 🔁 & ⤴️ ▶️ 👆 🈸 - * 👆 🔜 💪 🌌 ▶️/⏏ *👈* 🎉 ✍, 🔍 ❌, ♒️. - -/// tip - -👤 🔜 🤝 👆 🌅 🧱 🖼 🔨 👉 ⏮️ 📦 🔮 📃: [FastAPI 📦 - ☁](docker.md){.internal-link target=_blank}. - -/// - -## ℹ 🛠️ - -👆 💽(Ⓜ) () **ℹ**, 👆 💪 🍴 ⚖️ **⚙️**, ⏮️ 👆 📋, 📊 🕰 🔛 💽, & 💾 💾 💪. - -❔ 🌅 ⚙️ ℹ 👆 💚 😩/♻ ❓ ⚫️ 💪 ⏩ 💭 "🚫 🌅", ✋️ 🌌, 👆 🔜 🎲 💚 🍴 **🌅 💪 🍵 💥**. - -🚥 👆 💸 3️⃣ 💽 ✋️ 👆 ⚙️ 🕴 🐥 🍖 👫 💾 & 💽, 👆 🎲 **🗑 💸** 👶, & 🎲 **🗑 💽 🔦 🏋️** 👶, ♒️. - -👈 💼, ⚫️ 💪 👻 ✔️ 🕴 2️⃣ 💽 & ⚙️ ↕ 🌐 👫 ℹ (💽, 💾, 💾, 🕸 💿, ♒️). - -🔛 🎏 ✋, 🚥 👆 ✔️ 2️⃣ 💽 & 👆 ⚙️ **1️⃣0️⃣0️⃣ 💯 👫 💽 & 💾**, ☝ 1️⃣ 🛠️ 🔜 💭 🌅 💾, & 💽 🔜 ✔️ ⚙️ 💾 "💾" (❔ 💪 💯 🕰 🐌), ⚖️ **💥**. ⚖️ 1️⃣ 🛠️ 💪 💪 📊 & 🔜 ✔️ ⌛ ⏭ 💽 🆓 🔄. - -👉 💼, ⚫️ 🔜 👍 🤚 **1️⃣ ➕ 💽** & 🏃 🛠️ 🔛 ⚫️ 👈 👫 🌐 ✔️ **🥃 💾 & 💽 🕰**. - -📤 🤞 👈 🤔 👆 ✔️ **🌵** ⚙️ 👆 🛠️. 🎲 ⚫️ 🚶 🦠, ⚖️ 🎲 🎏 🐕‍🦺 ⚖️ 🤖 ▶️ ⚙️ ⚫️. & 👆 💪 💚 ✔️ ➕ ℹ 🔒 👈 💼. - -👆 💪 🚮 **❌ 🔢** 🎯, 🖼, 🕳 **🖖 5️⃣0️⃣ 💯 9️⃣0️⃣ 💯** ℹ 🛠️. ☝ 👈 📚 🎲 👑 👜 👆 🔜 💚 ⚖ & ⚙️ ⚒ 👆 🛠️. - -👆 💪 ⚙️ 🙅 🧰 💖 `htop` 👀 💽 & 💾 ⚙️ 👆 💽 ⚖️ 💸 ⚙️ 🔠 🛠️. ⚖️ 👆 💪 ⚙️ 🌖 🏗 ⚖ 🧰, ❔ 5️⃣📆 📎 🤭 💽, ♒️. - -## 🌃 - -👆 ✔️ 👂 📥 👑 🔧 👈 👆 🔜 🎲 💪 ✔️ 🤯 🕐❔ 🤔 ❔ 🛠️ 👆 🈸: - -* 💂‍♂ - 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* 🧬 (🔢 🛠️ 🏃) -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -🤔 👉 💭 & ❔ ✔ 👫 🔜 🤝 👆 🤔 💪 ✊ 🙆 🚫 🕐❔ 🛠️ & 🛠️ 👆 🛠️. 👶 - -⏭ 📄, 👤 🔜 🤝 👆 🌅 🧱 🖼 💪 🎛 👆 💪 ⏩. 👶 diff --git a/docs/em/docs/deployment/docker.md b/docs/em/docs/deployment/docker.md deleted file mode 100644 index 2152f1a0ee..0000000000 --- a/docs/em/docs/deployment/docker.md +++ /dev/null @@ -1,731 +0,0 @@ -# FastAPI 📦 - ☁ - -🕐❔ 🛠️ FastAPI 🈸 ⚠ 🎯 🏗 **💾 📦 🖼**. ⚫️ 🛎 🔨 ⚙️ **☁**. 👆 💪 ⤴️ 🛠️ 👈 📦 🖼 1️⃣ 👩‍❤‍👨 💪 🌌. - -⚙️ 💾 📦 ✔️ 📚 📈 ✅ **💂‍♂**, **🔬**, **🦁**, & 🎏. - -/// tip - -🏃 & ⏪ 💭 👉 💩 ❓ 🦘 [`Dockerfile` 🔛 👶](#fastapi). - -/// - -
-📁 🎮 👶 - -```Dockerfile -FROM python:3.9 - -WORKDIR /code - -COPY ./requirements.txt /code/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -COPY ./app /code/app - -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] - -# If running behind a proxy like Nginx or Traefik add --proxy-headers -# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--proxy-headers"] -``` - -
- -## ⚫️❔ 📦 - -📦 (✴️ 💾 📦) 📶 **💿** 🌌 📦 🈸 ✅ 🌐 👫 🔗 & 💪 📁 ⏪ 🚧 👫 ❎ ⚪️➡️ 🎏 📦 (🎏 🈸 ⚖️ 🦲) 🎏 ⚙️. - -💾 📦 🏃 ⚙️ 🎏 💾 💾 🦠 (🎰, 🕹 🎰, ☁ 💽, ♒️). 👉 ⛓ 👈 👫 📶 💿 (🔬 🌕 🕹 🎰 👍 🎂 🏃‍♂ ⚙️). - -👉 🌌, 📦 🍴 **🐥 ℹ**, 💸 ⭐ 🏃‍♂ 🛠️ 🔗 (🕹 🎰 🔜 🍴 🌅 🌅). - -📦 ✔️ 👫 👍 **❎** 🏃‍♂ 🛠️ (🛎 1️⃣ 🛠️), 📁 ⚙️, & 🕸, 🔬 🛠️, 💂‍♂, 🛠️, ♒️. - -## ⚫️❔ 📦 🖼 - -**📦** 🏃 ⚪️➡️ **📦 🖼**. - -📦 🖼 **🎻** ⏬ 🌐 📁, 🌐 🔢, & 🔢 📋/📋 👈 🔜 🎁 📦. **🎻** 📥 ⛓ 👈 📦 **🖼** 🚫 🏃, ⚫️ 🚫 ➖ 🛠️, ⚫️ 🕴 📦 📁 & 🗃. - -🔅 "**📦 🖼**" 👈 🏪 🎻 🎚,"**📦**" 🛎 🔗 🏃‍♂ 👐, 👜 👈 ➖ **🛠️**. - -🕐❔ **📦** ▶️ & 🏃‍♂ (▶️ ⚪️➡️ **📦 🖼**) ⚫️ 💪 ✍ ⚖️ 🔀 📁, 🌐 🔢, ♒️. 👈 🔀 🔜 🔀 🕴 👈 📦, ✋️ 🔜 🚫 😣 👽 📦 🖼 (🔜 🚫 🖊 💾). - -📦 🖼 ⭐ **📋** 📁 & 🎚, ✅ `python` & 📁 `main.py`. - -& **📦** ⚫️ (🔅 **📦 🖼**) ☑ 🏃 👐 🖼, ⭐ **🛠️**. 👐, 📦 🏃 🕴 🕐❔ ⚫️ ✔️ **🛠️ 🏃** (& 🛎 ⚫️ 🕴 👁 🛠️). 📦 ⛔️ 🕐❔ 📤 🙅‍♂ 🛠️ 🏃 ⚫️. - -## 📦 🖼 - -☁ ✔️ 1️⃣ 👑 🧰 ✍ & 🛠️ **📦 🖼** & **📦**. - -& 📤 📢 ☁ 🎡 ⏮️ 🏤-⚒ **🛂 📦 🖼** 📚 🧰, 🌐, 💽, & 🈸. - -🖼, 📤 🛂 🐍 🖼. - -& 📤 📚 🎏 🖼 🎏 👜 💖 💽, 🖼: - -* -* -* -* , ♒️. - -⚙️ 🏤-⚒ 📦 🖼 ⚫️ 📶 ⏩ **🌀** & ⚙️ 🎏 🧰. 🖼, 🔄 👅 🆕 💽. 🌅 💼, 👆 💪 ⚙️ **🛂 🖼**, & 🔗 👫 ⏮️ 🌐 🔢. - -👈 🌌, 📚 💼 👆 💪 💡 🔃 📦 & ☁ & 🏤-⚙️ 👈 💡 ⏮️ 📚 🎏 🧰 & 🦲. - -, 👆 🔜 🏃 **💗 📦** ⏮️ 🎏 👜, 💖 💽, 🐍 🈸, 🕸 💽 ⏮️ 😥 🕸 🈸, & 🔗 👫 👯‍♂️ 📨 👫 🔗 🕸. - -🌐 📦 🧾 ⚙️ (💖 ☁ ⚖️ Kubernetes) ✔️ 👫 🕸 ⚒ 🛠️ 🔘 👫. - -## 📦 & 🛠️ - -**📦 🖼** 🛎 🔌 🚮 🗃 🔢 📋 ⚖️ 📋 👈 🔜 🏃 🕐❔ **📦** ▶️ & 🔢 🚶‍♀️ 👈 📋. 📶 🎏 ⚫️❔ 🔜 🚥 ⚫️ 📋 ⏸. - -🕐❔ **📦** ▶️, ⚫️ 🔜 🏃 👈 📋/📋 (👐 👆 💪 🔐 ⚫️ & ⚒ ⚫️ 🏃 🎏 📋/📋). - -📦 🏃 📏 **👑 🛠️** (📋 ⚖️ 📋) 🏃. - -📦 🛎 ✔️ **👁 🛠️**, ✋️ ⚫️ 💪 ▶️ ✳ ⚪️➡️ 👑 🛠️, & 👈 🌌 👆 🔜 ✔️ **💗 🛠️** 🎏 📦. - -✋️ ⚫️ 🚫 💪 ✔️ 🏃‍♂ 📦 🍵 **🌘 1️⃣ 🏃‍♂ 🛠️**. 🚥 👑 🛠️ ⛔️, 📦 ⛔️. - -## 🏗 ☁ 🖼 FastAPI - -🆗, ➡️ 🏗 🕳 🔜 ❗ 👶 - -👤 🔜 🎦 👆 ❔ 🏗 **☁ 🖼** FastAPI **⚪️➡️ 🖌**, ⚓️ 🔛 **🛂 🐍** 🖼. - -👉 ⚫️❔ 👆 🔜 💚 **🏆 💼**, 🖼: - -* ⚙️ **Kubernetes** ⚖️ 🎏 🧰 -* 🕐❔ 🏃‍♂ 🔛 **🍓 👲** -* ⚙️ ☁ 🐕‍🦺 👈 🔜 🏃 📦 🖼 👆, ♒️. - -### 📦 📄 - -👆 🔜 🛎 ✔️ **📦 📄** 👆 🈸 📁. - -⚫️ 🔜 🪀 ✴️ 🔛 🧰 👆 ⚙️ **❎** 👈 📄. - -🌅 ⚠ 🌌 ⚫️ ✔️ 📁 `requirements.txt` ⏮️ 📦 📛 & 👫 ⏬, 1️⃣ 📍 ⏸. - -👆 🔜 ↗️ ⚙️ 🎏 💭 👆 ✍ [🔃 FastAPI ⏬](versions.md){.internal-link target=_blank} ⚒ ↔ ⏬. - -🖼, 👆 `requirements.txt` 💪 👀 💖: - -``` -fastapi>=0.68.0,<0.69.0 -pydantic>=1.8.0,<2.0.0 -uvicorn>=0.15.0,<0.16.0 -``` - -& 👆 🔜 🛎 ❎ 👈 📦 🔗 ⏮️ `pip`, 🖼: - -
- -```console -$ pip install -r requirements.txt ----> 100% -Successfully installed fastapi pydantic uvicorn -``` - -
- -/// info - -📤 🎏 📁 & 🧰 🔬 & ❎ 📦 🔗. - -👤 🔜 🎦 👆 🖼 ⚙️ 🎶 ⏪ 📄 🔛. 👶 - -/// - -### ✍ **FastAPI** 📟 - -* ✍ `app` 📁 & ⛔ ⚫️. -* ✍ 🛁 📁 `__init__.py`. -* ✍ `main.py` 📁 ⏮️: - -```Python -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} -``` - -### 📁 - -🔜 🎏 🏗 📁 ✍ 📁 `Dockerfile` ⏮️: - -```{ .dockerfile .annotate } -# (1) -FROM python:3.9 - -# (2) -WORKDIR /code - -# (3) -COPY ./requirements.txt /code/requirements.txt - -# (4) -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -# (5) -COPY ./app /code/app - -# (6) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] -``` - -1️⃣. ▶️ ⚪️➡️ 🛂 🐍 🧢 🖼. - -2️⃣. ⚒ ⏮️ 👷 📁 `/code`. - - 👉 🌐❔ 👥 🔜 🚮 `requirements.txt` 📁 & `app` 📁. - -3️⃣. 📁 📁 ⏮️ 📄 `/code` 📁. - - 📁 **🕴** 📁 ⏮️ 📄 🥇, 🚫 🎂 📟. - - 👉 📁 **🚫 🔀 🛎**, ☁ 🔜 🔍 ⚫️ & ⚙️ **💾** 👉 🔁, 🛠️ 💾 ⏭ 🔁 💁‍♂️. - -4️⃣. ❎ 📦 🔗 📄 📁. - - `--no-cache-dir` 🎛 💬 `pip` 🚫 🖊 ⏬ 📦 🌐, 👈 🕴 🚥 `pip` 🔜 🏃 🔄 ❎ 🎏 📦, ✋️ 👈 🚫 💼 🕐❔ 👷 ⏮️ 📦. - - /// note - - `--no-cache-dir` 🕴 🔗 `pip`, ⚫️ ✔️ 🕳 ⏮️ ☁ ⚖️ 📦. - - /// - - `--upgrade` 🎛 💬 `pip` ♻ 📦 🚥 👫 ⏪ ❎. - - ↩️ ⏮️ 🔁 🖨 📁 💪 🔍 **☁ 💾**, 👉 🔁 🔜 **⚙️ ☁ 💾** 🕐❔ 💪. - - ⚙️ 💾 👉 🔁 🔜 **🖊** 👆 📚 **🕰** 🕐❔ 🏗 🖼 🔄 & 🔄 ⏮️ 🛠️, ↩️ **⏬ & ❎** 🌐 🔗 **🔠 🕰**. - -5️⃣. 📁 `./app` 📁 🔘 `/code` 📁. - - 👉 ✔️ 🌐 📟 ❔ ⚫️❔ **🔀 🌅 🛎** ☁ **💾** 🏆 🚫 ⚙️ 👉 ⚖️ 🙆 **📄 🔁** 💪. - - , ⚫️ ⚠ 🚮 👉 **🏘 🔚** `Dockerfile`, 🔬 📦 🖼 🏗 🕰. - -6️⃣. ⚒ **📋** 🏃 `uvicorn` 💽. - - `CMD` ✊ 📇 🎻, 🔠 👫 🎻 ⚫️❔ 👆 🔜 🆎 📋 ⏸ 👽 🚀. - - 👉 📋 🔜 🏃 ⚪️➡️ **⏮️ 👷 📁**, 🎏 `/code` 📁 👆 ⚒ 🔛 ⏮️ `WORKDIR /code`. - - ↩️ 📋 🔜 ▶️ `/code` & 🔘 ⚫️ 📁 `./app` ⏮️ 👆 📟, **Uvicorn** 🔜 💪 👀 & **🗄** `app` ⚪️➡️ `app.main`. - -/// tip - -📄 ⚫️❔ 🔠 ⏸ 🔨 🖊 🔠 🔢 💭 📟. 👶 - -/// - -👆 🔜 🔜 ✔️ 📁 📊 💖: - -``` -. -├── app -│   ├── __init__.py -│ └── main.py -├── Dockerfile -└── requirements.txt -``` - -#### ⛅ 🤝 ❎ 🗳 - -🚥 👆 🏃‍♂ 👆 📦 ⛅ 🤝 ❎ 🗳 (📐 ⚙) 💖 👌 ⚖️ Traefik, 🚮 🎛 `--proxy-headers`, 👉 🔜 💬 Uvicorn 💙 🎚 📨 👈 🗳 💬 ⚫️ 👈 🈸 🏃 ⛅ 🇺🇸🔍, ♒️. - -```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] -``` - -#### ☁ 💾 - -📤 ⚠ 🎱 👉 `Dockerfile`, 👥 🥇 📁 **📁 ⏮️ 🔗 😞**, 🚫 🎂 📟. ➡️ 👤 💬 👆 ⚫️❔ 👈. - -```Dockerfile -COPY ./requirements.txt /code/requirements.txt -``` - -☁ & 🎏 🧰 **🏗** 👉 📦 🖼 **🔁**, 🚮 **1️⃣ 🧽 🔛 🔝 🎏**, ▶️ ⚪️➡️ 🔝 `Dockerfile` & ❎ 🙆 📁 ✍ 🔠 👩‍🌾 `Dockerfile`. - -☁ & 🎏 🧰 ⚙️ **🔗 💾** 🕐❔ 🏗 🖼, 🚥 📁 🚫 🔀 ↩️ 🏁 🕰 🏗 📦 🖼, ⤴️ ⚫️ 🔜 **🏤-⚙️ 🎏 🧽** ✍ 🏁 🕰, ↩️ 🖨 📁 🔄 & 🏗 🆕 🧽 ⚪️➡️ 🖌. - -❎ 📁 📁 🚫 🎯 📉 👜 💁‍♂️ 🌅, ✋️ ↩️ ⚫️ ⚙️ 💾 👈 🔁, ⚫️ 💪 **⚙️ 💾 ⏭ 🔁**. 🖼, ⚫️ 💪 ⚙️ 💾 👩‍🌾 👈 ❎ 🔗 ⏮️: - -```Dockerfile -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt -``` - -📁 ⏮️ 📦 📄 **🏆 🚫 🔀 🛎**. , 🖨 🕴 👈 📁, ☁ 🔜 💪 **⚙️ 💾** 👈 🔁. - -& ⤴️, ☁ 🔜 💪 **⚙️ 💾 ⏭ 🔁** 👈 ⏬ & ❎ 👈 🔗. & 📥 🌐❔ 👥 **🖊 📚 🕰**. 👶 ...& ❎ 😩 ⌛. 👶 👶 - -⏬ & ❎ 📦 🔗 **💪 ✊ ⏲**, ✋️ ⚙️ **💾** 🔜 **✊ 🥈** 🌅. - -& 👆 🔜 🏗 📦 🖼 🔄 & 🔄 ⏮️ 🛠️ ✅ 👈 👆 📟 🔀 👷, 📤 📚 📈 🕰 👉 🔜 🖊. - -⤴️, 🏘 🔚 `Dockerfile`, 👥 📁 🌐 📟. 👉 ⚫️❔ **🔀 🏆 🛎**, 👥 🚮 ⚫️ 🏘 🔚, ↩️ 🌖 🕧, 🕳 ⏮️ 👉 🔁 🔜 🚫 💪 ⚙️ 💾. - -```Dockerfile -COPY ./app /code/app -``` - -### 🏗 ☁ 🖼 - -🔜 👈 🌐 📁 🥉, ➡️ 🏗 📦 🖼. - -* 🚶 🏗 📁 (🌐❔ 👆 `Dockerfile` , ⚗ 👆 `app` 📁). -* 🏗 👆 FastAPI 🖼: - -
- -```console -$ docker build -t myimage . - ----> 100% -``` - -
- -/// tip - -👀 `.` 🔚, ⚫️ 🌓 `./`, ⚫️ 💬 ☁ 📁 ⚙️ 🏗 📦 🖼. - -👉 💼, ⚫️ 🎏 ⏮️ 📁 (`.`). - -/// - -### ▶️ ☁ 📦 - -* 🏃 📦 ⚓️ 🔛 👆 🖼: - -
- -```console -$ docker run -d --name mycontainer -p 80:80 myimage -``` - -
- -## ✅ ⚫️ - -👆 🔜 💪 ✅ ⚫️ 👆 ☁ 📦 📛, 🖼: http://192.168.99.100/items/5?q=somequery ⚖️ http://127.0.0.1/items/5?q=somequery (⚖️ 🌓, ⚙️ 👆 ☁ 🦠). - -👆 🔜 👀 🕳 💖: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -## 🎓 🛠️ 🩺 - -🔜 👆 💪 🚶 http://192.168.99.100/docs ⚖️ http://127.0.0.1/docs (⚖️ 🌓, ⚙️ 👆 ☁ 🦠). - -👆 🔜 👀 🏧 🎓 🛠️ 🧾 (🚚 🦁 🎚): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -## 🎛 🛠️ 🩺 - -& 👆 💪 🚶 http://192.168.99.100/redoc ⚖️ http://127.0.0.1/redoc (⚖️ 🌓, ⚙️ 👆 ☁ 🦠). - -👆 🔜 👀 🎛 🏧 🧾 (🚚 📄): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## 🏗 ☁ 🖼 ⏮️ 👁-📁 FastAPI - -🚥 👆 FastAPI 👁 📁, 🖼, `main.py` 🍵 `./app` 📁, 👆 📁 📊 💪 👀 💖 👉: - -``` -. -├── Dockerfile -├── main.py -└── requirements.txt -``` - -⤴️ 👆 🔜 ✔️ 🔀 🔗 ➡ 📁 📁 🔘 `Dockerfile`: - -```{ .dockerfile .annotate hl_lines="10 13" } -FROM python:3.9 - -WORKDIR /code - -COPY ./requirements.txt /code/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -# (1) -COPY ./main.py /code/ - -# (2) -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"] -``` - -1️⃣. 📁 `main.py` 📁 `/code` 📁 🔗 (🍵 🙆 `./app` 📁). - -2️⃣. 🏃 Uvicorn & 💬 ⚫️ 🗄 `app` 🎚 ⚪️➡️ `main` (↩️ 🏭 ⚪️➡️ `app.main`). - -⤴️ 🔆 Uvicorn 📋 ⚙️ 🆕 🕹 `main` ↩️ `app.main` 🗄 FastAPI 🎚 `app`. - -## 🛠️ 🔧 - -➡️ 💬 🔄 🔃 🎏 [🛠️ 🔧](concepts.md){.internal-link target=_blank} ⚖ 📦. - -📦 ✴️ 🧰 📉 🛠️ **🏗 & 🛠️** 🈸, ✋️ 👫 🚫 🛠️ 🎯 🎯 🍵 👉 **🛠️ 🔧**, & 📤 📚 💪 🎛. - -**👍 📰** 👈 ⏮️ 🔠 🎏 🎛 📤 🌌 📔 🌐 🛠️ 🔧. 👶 - -➡️ 📄 👉 **🛠️ 🔧** ⚖ 📦: - -* 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* 🧬 (🔢 🛠️ 🏃) -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -## 🇺🇸🔍 - -🚥 👥 🎯 🔛 **📦 🖼** FastAPI 🈸 (& ⏪ 🏃‍♂ **📦**), 🇺🇸🔍 🛎 🔜 🍵 **🗜** ➕1️⃣ 🧰. - -⚫️ 💪 ➕1️⃣ 📦, 🖼 ⏮️ Traefik, 🚚 **🇺🇸🔍** & **🏧** 🛠️ **📄**. - -/// tip - -Traefik ✔️ 🛠️ ⏮️ ☁, Kubernetes, & 🎏, ⚫️ 📶 ⏩ ⚒ 🆙 & 🔗 🇺🇸🔍 👆 📦 ⏮️ ⚫️. - -/// - -👐, 🇺🇸🔍 💪 🍵 ☁ 🐕‍🦺 1️⃣ 👫 🐕‍🦺 (⏪ 🏃 🈸 📦). - -## 🏃‍♂ 🔛 🕴 & ⏏ - -📤 🛎 ➕1️⃣ 🧰 🈚 **▶️ & 🏃‍♂** 👆 📦. - -⚫️ 💪 **☁** 🔗, **☁ ✍**, **Kubernetes**, **☁ 🐕‍🦺**, ♒️. - -🌅 (⚖️ 🌐) 💼, 📤 🙅 🎛 🛠️ 🏃 📦 🔛 🕴 & 🛠️ ⏏ 🔛 ❌. 🖼, ☁, ⚫️ 📋 ⏸ 🎛 `--restart`. - -🍵 ⚙️ 📦, ⚒ 🈸 🏃 🔛 🕴 & ⏮️ ⏏ 💪 ⚠ & ⚠. ✋️ 🕐❔ **👷 ⏮️ 📦** 🌅 💼 👈 🛠️ 🔌 🔢. 👶 - -## 🧬 - 🔢 🛠️ - -🚥 👆 ✔️ 🌑 🎰 ⏮️ **☁**, ☁ 🐝 📳, 🖖, ⚖️ ➕1️⃣ 🎏 🏗 ⚙️ 🛠️ 📎 📦 🔛 💗 🎰, ⤴️ 👆 🔜 🎲 💚 **🍵 🧬** **🌑 🎚** ↩️ ⚙️ **🛠️ 👨‍💼** (💖 🐁 ⏮️ 👨‍🏭) 🔠 📦. - -1️⃣ 📚 📎 📦 🧾 ⚙️ 💖 Kubernetes 🛎 ✔️ 🛠️ 🌌 🚚 **🧬 📦** ⏪ 🔗 **📐 ⚖** 📨 📨. 🌐 **🌑 🎚**. - -📚 💼, 👆 🔜 🎲 💚 🏗 **☁ 🖼 ⚪️➡️ 🖌** [🔬 🔛](#_6), ❎ 👆 🔗, & 🏃‍♂ **👁 Uvicorn 🛠️** ↩️ 🏃‍♂ 🕳 💖 🐁 ⏮️ Uvicorn 👨‍🏭. - -### 📐 ⚙ - -🕐❔ ⚙️ 📦, 👆 🔜 🛎 ✔️ 🦲 **👂 🔛 👑 ⛴**. ⚫️ 💪 🎲 ➕1️⃣ 📦 👈 **🤝 ❎ 🗳** 🍵 **🇺🇸🔍** ⚖️ 🎏 🧰. - -👉 🦲 🔜 ✊ **📐** 📨 & 📎 👈 👪 👨‍🏭 (🤞) **⚖** 🌌, ⚫️ 🛎 🤙 **📐 ⚙**. - -/// tip - -🎏 **🤝 ❎ 🗳** 🦲 ⚙️ 🇺🇸🔍 🔜 🎲 **📐 ⚙**. - -/// - -& 🕐❔ 👷 ⏮️ 📦, 🎏 ⚙️ 👆 ⚙️ ▶️ & 🛠️ 👫 🔜 ⏪ ✔️ 🔗 🧰 📶 **🕸 📻** (✅ 🇺🇸🔍 📨) ⚪️➡️ 👈 **📐 ⚙** (👈 💪 **🤝 ❎ 🗳**) 📦(Ⓜ) ⏮️ 👆 📱. - -### 1️⃣ 📐 ⚙ - 💗 👨‍🏭 📦 - -🕐❔ 👷 ⏮️ **Kubernetes** ⚖️ 🎏 📎 📦 🧾 ⚙️, ⚙️ 👫 🔗 🕸 🛠️ 🔜 ✔ 👁 **📐 ⚙** 👈 👂 🔛 👑 **⛴** 📶 📻 (📨) 🎲 **💗 📦** 🏃 👆 📱. - -🔠 👫 📦 🏃‍♂ 👆 📱 🔜 🛎 ✔️ **1️⃣ 🛠️** (✅ Uvicorn 🛠️ 🏃 👆 FastAPI 🈸). 👫 🔜 🌐 **🌓 📦**, 🏃‍♂ 🎏 👜, ✋️ 🔠 ⏮️ 🚮 👍 🛠️, 💾, ♒️. 👈 🌌 👆 🔜 ✊ 📈 **🛠️** **🎏 🐚** 💽, ⚖️ **🎏 🎰**. - -& 📎 📦 ⚙️ ⏮️ **📐 ⚙** 🔜 **📎 📨** 🔠 1️⃣ 📦 ⏮️ 👆 📱 **🔄**. , 🔠 📨 💪 🍵 1️⃣ 💗 **🔁 📦** 🏃 👆 📱. - -& 🛎 👉 **📐 ⚙** 🔜 💪 🍵 📨 👈 🚶 *🎏* 📱 👆 🌑 (✅ 🎏 🆔, ⚖️ 🔽 🎏 📛 ➡ 🔡), & 🔜 📶 👈 📻 ▶️️ 📦 *👈 🎏* 🈸 🏃‍♂ 👆 🌑. - -### 1️⃣ 🛠️ 📍 📦 - -👉 🆎 😐, 👆 🎲 🔜 💚 ✔️ **👁 (Uvicorn) 🛠️ 📍 📦**, 👆 🔜 ⏪ 🚚 🧬 🌑 🎚. - -, 👉 💼, 👆 **🔜 🚫** 💚 ✔️ 🛠️ 👨‍💼 💖 🐁 ⏮️ Uvicorn 👨‍🏭, ⚖️ Uvicorn ⚙️ 🚮 👍 Uvicorn 👨‍🏭. 👆 🔜 💚 ✔️ **👁 Uvicorn 🛠️** 📍 📦 (✋️ 🎲 💗 📦). - -✔️ ➕1️⃣ 🛠️ 👨‍💼 🔘 📦 (🔜 ⏮️ 🐁 ⚖️ Uvicorn 🛠️ Uvicorn 👨‍🏭) 🔜 🕴 🚮 **🙃 🔀** 👈 👆 🌅 🎲 ⏪ ✊ 💅 ⏮️ 👆 🌑 ⚙️. - -### 📦 ⏮️ 💗 🛠️ & 🎁 💼 - -↗️, 📤 **🎁 💼** 🌐❔ 👆 💪 💚 ✔️ **📦** ⏮️ **🐁 🛠️ 👨‍💼** ▶️ 📚 **Uvicorn 👨‍🏭 🛠️** 🔘. - -📚 💼, 👆 💪 ⚙️ **🛂 ☁ 🖼** 👈 🔌 **🐁** 🛠️ 👨‍💼 🏃‍♂ 💗 **Uvicorn 👨‍🏭 🛠️**, & 🔢 ⚒ 🔆 🔢 👨‍🏭 ⚓️ 🔛 ⏮️ 💽 🐚 🔁. 👤 🔜 💬 👆 🌅 🔃 ⚫️ 🔛 [🛂 ☁ 🖼 ⏮️ 🐁 - Uvicorn](#-uvicorn). - -📥 🖼 🕐❔ 👈 💪 ⚒ 🔑: - -#### 🙅 📱 - -👆 💪 💚 🛠️ 👨‍💼 📦 🚥 👆 🈸 **🙅 🥃** 👈 👆 🚫 💪 (🐥 🚫) 👌-🎶 🔢 🛠️ 💁‍♂️ 🌅, & 👆 💪 ⚙️ 🏧 🔢 (⏮️ 🛂 ☁ 🖼), & 👆 🏃‍♂ ⚫️ 🔛 **👁 💽**, 🚫 🌑. - -#### ☁ ✍ - -👆 💪 🛠️ **👁 💽** (🚫 🌑) ⏮️ **☁ ✍**, 👆 🚫🔜 ✔️ ⏩ 🌌 🛠️ 🧬 📦 (⏮️ ☁ ✍) ⏪ 🛡 🔗 🕸 & **📐 ⚖**. - -⤴️ 👆 💪 💚 ✔️ **👁 📦** ⏮️ **🛠️ 👨‍💼** ▶️ **📚 👨‍🏭 🛠️** 🔘. - -#### 🤴 & 🎏 🤔 - -👆 💪 ✔️ **🎏 🤔** 👈 🔜 ⚒ ⚫️ ⏩ ✔️ **👁 📦** ⏮️ **💗 🛠️** ↩️ ✔️ **💗 📦** ⏮️ **👁 🛠️** 🔠 👫. - -🖼 (🪀 🔛 👆 🖥) 👆 💪 ✔️ 🧰 💖 🤴 🏭 🎏 📦 👈 🔜 ✔️ 🔐 **🔠 📨** 👈 👟. - -👉 💼, 🚥 👆 ✔️ **💗 📦**, 🔢, 🕐❔ 🤴 👟 **✍ ⚖**, ⚫️ 🔜 🤚 🕐 **👁 📦 🔠 🕰** (📦 👈 🍵 👈 🎯 📨), ↩️ 🤚 **📈 ⚖** 🌐 🔁 📦. - -⤴️, 👈 💼, ⚫️ 💪 🙅 ✔️ **1️⃣ 📦** ⏮️ **💗 🛠️**, & 🇧🇿 🧰 (✅ 🤴 🏭) 🔛 🎏 📦 📈 🤴 ⚖ 🌐 🔗 🛠️ & 🎦 👈 ⚖ 🔛 👈 👁 📦. - ---- - -👑 ☝, **👌** 👉 **🚫 ✍ 🗿** 👈 👆 ✔️ 😄 ⏩. 👆 💪 ⚙️ 👫 💭 **🔬 👆 👍 ⚙️ 💼** & 💭 ⚫️❔ 👍 🎯 👆 ⚙️, ✅ 👅 ❔ 🛠️ 🔧: - -* 💂‍♂ - 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* 🧬 (🔢 🛠️ 🏃) -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -## 💾 - -🚥 👆 🏃 **👁 🛠️ 📍 📦** 👆 🔜 ✔️ 🌅 ⚖️ 🌘 👍-🔬, ⚖, & 📉 💸 💾 🍴 🔠 👈 📦 (🌅 🌘 1️⃣ 🚥 👫 🔁). - -& ⤴️ 👆 💪 ⚒ 👈 🎏 💾 📉 & 📄 👆 📳 👆 📦 🧾 ⚙️ (🖼 **Kubernetes**). 👈 🌌 ⚫️ 🔜 💪 **🔁 📦** **💪 🎰** ✊ 🔘 🏧 💸 💾 💪 👫, & 💸 💪 🎰 🌑. - -🚥 👆 🈸 **🙅**, 👉 🔜 🎲 **🚫 ⚠**, & 👆 💪 🚫 💪 ✔ 🏋️ 💾 📉. ✋️ 🚥 👆 **⚙️ 📚 💾** (🖼 ⏮️ **🎰 🏫** 🏷), 👆 🔜 ✅ ❔ 🌅 💾 👆 😩 & 🔆 **🔢 📦** 👈 🏃 **🔠 🎰** (& 🎲 🚮 🌖 🎰 👆 🌑). - -🚥 👆 🏃 **💗 🛠️ 📍 📦** (🖼 ⏮️ 🛂 ☁ 🖼) 👆 🔜 ✔️ ⚒ 💭 👈 🔢 🛠️ ▶️ 🚫 **🍴 🌖 💾** 🌘 ⚫️❔ 💪. - -## ⏮️ 🔁 ⏭ ▶️ & 📦 - -🚥 👆 ⚙️ 📦 (✅ ☁, Kubernetes), ⤴️ 📤 2️⃣ 👑 🎯 👆 💪 ⚙️. - -### 💗 📦 - -🚥 👆 ✔️ **💗 📦**, 🎲 🔠 1️⃣ 🏃 **👁 🛠️** (🖼, **Kubernetes** 🌑), ⤴️ 👆 🔜 🎲 💚 ✔️ **🎏 📦** 🔨 👷 **⏮️ 📶** 👁 📦, 🏃 👁 🛠️, **⏭** 🏃 🔁 👨‍🏭 📦. - -/// info - -🚥 👆 ⚙️ Kubernetes, 👉 🔜 🎲 🕑 📦. - -/// - -🚥 👆 ⚙️ 💼 📤 🙅‍♂ ⚠ 🏃‍♂ 👈 ⏮️ 📶 **💗 🕰 🔗** (🖼 🚥 👆 🚫 🏃 💽 🛠️, ✋️ ✅ 🚥 💽 🔜), ⤴️ 👆 💪 🚮 👫 🔠 📦 ▶️️ ⏭ ▶️ 👑 🛠️. - -### 👁 📦 - -🚥 👆 ✔️ 🙅 🖥, ⏮️ **👁 📦** 👈 ⤴️ ▶️ 💗 **👨‍🏭 🛠️** (⚖️ 1️⃣ 🛠️), ⤴️ 👆 💪 🏃 👈 ⏮️ 🔁 🎏 📦, ▶️️ ⏭ ▶️ 🛠️ ⏮️ 📱. 🛂 ☁ 🖼 🐕‍🦺 👉 🔘. - -## 🛂 ☁ 🖼 ⏮️ 🐁 - Uvicorn - -📤 🛂 ☁ 🖼 👈 🔌 🐁 🏃‍♂ ⏮️ Uvicorn 👨‍🏭, ℹ ⏮️ 📃: [💽 👨‍🏭 - 🐁 ⏮️ Uvicorn](server-workers.md){.internal-link target=_blank}. - -👉 🖼 🔜 ⚠ ✴️ ⚠ 🔬 🔛: [📦 ⏮️ 💗 🛠️ & 🎁 💼](#_18). - -* tiangolo/uvicorn-🐁-fastapi. - -/// warning - -📤 ↕ 🤞 👈 👆 **🚫** 💪 👉 🧢 🖼 ⚖️ 🙆 🎏 🎏 1️⃣, & 🔜 👻 📆 🏗 🖼 ⚪️➡️ 🖌 [🔬 🔛: 🏗 ☁ 🖼 FastAPI](#fastapi). - -/// - -👉 🖼 ✔️ **🚘-📳** 🛠️ 🔌 ⚒ **🔢 👨‍🏭 🛠️** ⚓️ 🔛 💽 🐚 💪. - -⚫️ ✔️ **🤔 🔢**, ✋️ 👆 💪 🔀 & ℹ 🌐 📳 ⏮️ **🌐 🔢** ⚖️ 📳 📁. - -⚫️ 🐕‍🦺 🏃 **⏮️ 🔁 ⏭ ▶️** ⏮️ ✍. - -/// tip - -👀 🌐 📳 & 🎛, 🚶 ☁ 🖼 📃: Tiangolo/uvicorn-🐁-fastapi. - -/// - -### 🔢 🛠️ 🔛 🛂 ☁ 🖼 - -**🔢 🛠️** 🔛 👉 🖼 **📊 🔁** ⚪️➡️ 💽 **🐚** 💪. - -👉 ⛓ 👈 ⚫️ 🔜 🔄 **🗜** 🌅 **🎭** ⚪️➡️ 💽 💪. - -👆 💪 🔆 ⚫️ ⏮️ 📳 ⚙️ **🌐 🔢**, ♒️. - -✋️ ⚫️ ⛓ 👈 🔢 🛠️ 🪀 🔛 💽 📦 🏃, **💸 💾 🍴** 🔜 🪀 🔛 👈. - -, 🚥 👆 🈸 🍴 📚 💾 (🖼 ⏮️ 🎰 🏫 🏷), & 👆 💽 ✔️ 📚 💽 🐚 **✋️ 🐥 💾**, ⤴️ 👆 📦 💪 🔚 🆙 🔄 ⚙️ 🌅 💾 🌘 ⚫️❔ 💪, & 🤕 🎭 📚 (⚖️ 💥). 👶 - -### ✍ `Dockerfile` - -📥 ❔ 👆 🔜 ✍ `Dockerfile` ⚓️ 🔛 👉 🖼: - -```Dockerfile -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 - -COPY ./requirements.txt /app/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt - -COPY ./app /app -``` - -### 🦏 🈸 - -🚥 👆 ⏩ 📄 🔃 🏗 [🦏 🈸 ⏮️ 💗 📁](../tutorial/bigger-applications.md){.internal-link target=_blank}, 👆 `Dockerfile` 💪 ↩️ 👀 💖: - -```Dockerfile hl_lines="7" -FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9 - -COPY ./requirements.txt /app/requirements.txt - -RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt - -COPY ./app /app/app -``` - -### 🕐❔ ⚙️ - -👆 🔜 🎲 **🚫** ⚙️ 👉 🛂 🧢 🖼 (⚖️ 🙆 🎏 🎏 1️⃣) 🚥 👆 ⚙️ **Kubernetes** (⚖️ 🎏) & 👆 ⏪ ⚒ **🧬** 🌑 🎚, ⏮️ 💗 **📦**. 📚 💼, 👆 👍 📆 **🏗 🖼 ⚪️➡️ 🖌** 🔬 🔛: [🏗 ☁ 🖼 FastAPI](#fastapi). - -👉 🖼 🔜 ⚠ ✴️ 🎁 💼 🔬 🔛 [📦 ⏮️ 💗 🛠️ & 🎁 💼](#_18). 🖼, 🚥 👆 🈸 **🙅 🥃** 👈 ⚒ 🔢 🔢 🛠️ ⚓️ 🔛 💽 👷 👍, 👆 🚫 💚 😥 ⏮️ ❎ 🛠️ 🧬 🌑 🎚, & 👆 🚫 🏃 🌅 🌘 1️⃣ 📦 ⏮️ 👆 📱. ⚖️ 🚥 👆 🛠️ ⏮️ **☁ ✍**, 🏃 🔛 👁 💽, ♒️. - -## 🛠️ 📦 🖼 - -⏮️ ✔️ 📦 (☁) 🖼 📤 📚 🌌 🛠️ ⚫️. - -🖼: - -* ⏮️ **☁ ✍** 👁 💽 -* ⏮️ **Kubernetes** 🌑 -* ⏮️ ☁ 🐝 📳 🌑 -* ⏮️ ➕1️⃣ 🧰 💖 🖖 -* ⏮️ ☁ 🐕‍🦺 👈 ✊ 👆 📦 🖼 & 🛠️ ⚫️ - -## ☁ 🖼 ⏮️ 🎶 - -🚥 👆 ⚙️ 🎶 🛠️ 👆 🏗 🔗, 👆 💪 ⚙️ ☁ 👁-▶️ 🏗: - -```{ .dockerfile .annotate } -# (1) -FROM python:3.9 as requirements-stage - -# (2) -WORKDIR /tmp - -# (3) -RUN pip install poetry - -# (4) -COPY ./pyproject.toml ./poetry.lock* /tmp/ - -# (5) -RUN poetry export -f requirements.txt --output requirements.txt --without-hashes - -# (6) -FROM python:3.9 - -# (7) -WORKDIR /code - -# (8) -COPY --from=requirements-stage /tmp/requirements.txt /code/requirements.txt - -# (9) -RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt - -# (10) -COPY ./app /code/app - -# (11) -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] -``` - -1️⃣. 👉 🥇 ▶️, ⚫️ 🌟 `requirements-stage`. - -2️⃣. ⚒ `/tmp` ⏮️ 👷 📁. - - 📥 🌐❔ 👥 🔜 🏗 📁 `requirements.txt` - -3️⃣. ❎ 🎶 👉 ☁ ▶️. - -4️⃣. 📁 `pyproject.toml` & `poetry.lock` 📁 `/tmp` 📁. - - ↩️ ⚫️ ⚙️ `./poetry.lock*` (▶️ ⏮️ `*`), ⚫️ 🏆 🚫 💥 🚥 👈 📁 🚫 💪. - -5️⃣. 🏗 `requirements.txt` 📁. - -6️⃣. 👉 🏁 ▶️, 🕳 📥 🔜 🛡 🏁 📦 🖼. - -7️⃣. ⚒ ⏮️ 👷 📁 `/code`. - -8️⃣. 📁 `requirements.txt` 📁 `/code` 📁. - - 👉 📁 🕴 🖖 ⏮️ ☁ ▶️, 👈 ⚫️❔ 👥 ⚙️ `--from-requirements-stage` 📁 ⚫️. - -9️⃣. ❎ 📦 🔗 🏗 `requirements.txt` 📁. - -1️⃣0️⃣. 📁 `app` 📁 `/code` 📁. - -1️⃣1️⃣. 🏃 `uvicorn` 📋, 💬 ⚫️ ⚙️ `app` 🎚 🗄 ⚪️➡️ `app.main`. - -/// tip - -🖊 💭 🔢 👀 ⚫️❔ 🔠 ⏸ 🔨. - -/// - -**☁ ▶️** 🍕 `Dockerfile` 👈 👷 **🍕 📦 🖼** 👈 🕴 ⚙️ 🏗 📁 ⚙️ ⏪. - -🥇 ▶️ 🔜 🕴 ⚙️ **❎ 🎶** & **🏗 `requirements.txt`** ⏮️ 👆 🏗 🔗 ⚪️➡️ 🎶 `pyproject.toml` 📁. - -👉 `requirements.txt` 📁 🔜 ⚙️ ⏮️ `pip` ⏪ **⏭ ▶️**. - -🏁 📦 🖼 **🕴 🏁 ▶️** 🛡. ⏮️ ▶️(Ⓜ) 🔜 ❎. - -🕐❔ ⚙️ 🎶, ⚫️ 🔜 ⚒ 🔑 ⚙️ **☁ 👁-▶️ 🏗** ↩️ 👆 🚫 🤙 💪 ✔️ 🎶 & 🚮 🔗 ❎ 🏁 📦 🖼, 👆 **🕴 💪** ✔️ 🏗 `requirements.txt` 📁 ❎ 👆 🏗 🔗. - -⤴️ ⏭ (& 🏁) ▶️ 👆 🔜 🏗 🖼 🌅 ⚖️ 🌘 🎏 🌌 🔬 ⏭. - -### ⛅ 🤝 ❎ 🗳 - 🎶 - -🔄, 🚥 👆 🏃‍♂ 👆 📦 ⛅ 🤝 ❎ 🗳 (📐 ⚙) 💖 👌 ⚖️ Traefik, 🚮 🎛 `--proxy-headers` 📋: - -```Dockerfile -CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"] -``` - -## 🌃 - -⚙️ 📦 ⚙️ (✅ ⏮️ **☁** & **Kubernetes**) ⚫️ ▶️️ 📶 🎯 🍵 🌐 **🛠️ 🔧**: - -* 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* 🧬 (🔢 🛠️ 🏃) -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -🌅 💼, 👆 🎲 🏆 🚫 💚 ⚙️ 🙆 🧢 🖼, & ↩️ **🏗 📦 🖼 ⚪️➡️ 🖌** 1️⃣ ⚓️ 🔛 🛂 🐍 ☁ 🖼. - -✊ 💅 **✔** 👩‍🌾 `Dockerfile` & **☁ 💾** 👆 💪 **📉 🏗 🕰**, 📉 👆 📈 (& ❎ 😩). 👶 - -🎯 🎁 💼, 👆 💪 💚 ⚙️ 🛂 ☁ 🖼 FastAPI. 👶 diff --git a/docs/em/docs/deployment/https.md b/docs/em/docs/deployment/https.md deleted file mode 100644 index 6d2641a92c..0000000000 --- a/docs/em/docs/deployment/https.md +++ /dev/null @@ -1,199 +0,0 @@ -# 🔃 🇺🇸🔍 - -⚫️ ⏩ 🤔 👈 🇺🇸🔍 🕳 👈 "🛠️" ⚖️ 🚫. - -✋️ ⚫️ 🌌 🌖 🏗 🌘 👈. - -/// tip - -🚥 👆 🏃 ⚖️ 🚫 💅, 😣 ⏮️ ⏭ 📄 🔁 🔁 👩‍🌾 ⚒ 🌐 🆙 ⏮️ 🎏 ⚒. - -/// - -**💡 🔰 🇺🇸🔍**, ⚪️➡️ 🏬 🤔, ✅ https://howhttps.works/. - -🔜, ⚪️➡️ **👩‍💻 🤔**, 📥 📚 👜 ✔️ 🤯 ⏪ 💭 🔃 🇺🇸🔍: - -* 🇺🇸🔍, **💽** 💪 **✔️ "📄"** 🏗 **🥉 🥳**. - * 📚 📄 🤙 **🏆** ⚪️➡️ 🥉 🥳, 🚫 "🏗". -* 📄 ✔️ **1️⃣2️⃣🗓️**. - * 👫 **🕛**. - * & ⤴️ 👫 💪 **♻**, **🏆 🔄** ⚪️➡️ 🥉 🥳. -* 🔐 🔗 🔨 **🕸 🎚**. - * 👈 1️⃣ 🧽 **🔛 🇺🇸🔍**. - * , **📄 & 🔐** 🍵 🔨 **⏭ 🇺🇸🔍**. -* **🕸 🚫 💭 🔃 "🆔"**. 🕴 🔃 📢 📢. - * ℹ 🔃 **🎯 🆔** 📨 🚶 **🇺🇸🔍 💽**. -* **🇺🇸🔍 📄** "✔" **🎯 🆔**, ✋️ 🛠️ & 🔐 🔨 🕸 🎚, **⏭ 💭** ❔ 🆔 ➖ 🙅 ⏮️. -* **🔢**, 👈 🔜 ⛓ 👈 👆 💪 🕴 ✔️ **1️⃣ 🇺🇸🔍 📄 📍 📢 📢**. - * 🙅‍♂ 🤔 ❔ 🦏 👆 💽 ⚖️ ❔ 🤪 🔠 🈸 👆 ✔️ 🔛 ⚫️ 💪. - * 📤 **⚗** 👉, 👐. -* 📤 **↔** **🤝** 🛠️ (1️⃣ 🚚 🔐 🕸 🎚, ⏭ 🇺🇸🔍) 🤙 **👲**. - * 👉 👲 ↔ ✔ 1️⃣ 👁 💽 (⏮️ **👁 📢 📢**) ✔️ **📚 🇺🇸🔍 📄** & 🍦 **💗 🇺🇸🔍 🆔/🈸**. - * 👉 👷, **👁** 🦲 (📋) 🏃 🔛 💽, 👂 🔛 **📢 📢 📢**, 🔜 ✔️ **🌐 🇺🇸🔍 📄** 💽. -* **⏮️** 🏆 🔐 🔗, 📻 🛠️ **🇺🇸🔍**. - * 🎚 **🗜**, ✋️ 👫 ➖ 📨 ⏮️ **🇺🇸🔍 🛠️**. - -⚫️ ⚠ 💡 ✔️ **1️⃣ 📋/🇺🇸🔍 💽** 🏃 🔛 💽 (🎰, 🦠, ♒️.) & **🛠️ 🌐 🇺🇸🔍 🍕**: 📨 **🗜 🇺🇸🔍 📨**, 📨 **🗜 🇺🇸🔍 📨** ☑ 🇺🇸🔍 🈸 🏃 🎏 💽 ( **FastAPI** 🈸, 👉 💼), ✊ **🇺🇸🔍 📨** ⚪️➡️ 🈸, **🗜 ⚫️** ⚙️ ☑ **🇺🇸🔍 📄** & 📨 ⚫️ 🔙 👩‍💻 ⚙️ **🇺🇸🔍**. 👉 💽 🛎 🤙 **🤝 ❎ 🗳**. - -🎛 👆 💪 ⚙️ 🤝 ❎ 🗳: - -* Traefik (👈 💪 🍵 📄 🔕) -* 📥 (👈 💪 🍵 📄 🔕) -* 👌 -* ✳ - -## ➡️ 🗜 - -⏭ ➡️ 🗜, 👫 **🇺🇸🔍 📄** 💲 💙 🥉 🥳. - -🛠️ 📎 1️⃣ 👫 📄 ⚙️ ⚠, 🚚 📠 & 📄 😥. - -✋️ ⤴️ **➡️ 🗜** ✍. - -⚫️ 🏗 ⚪️➡️ 💾 🏛. ⚫️ 🚚 **🇺🇸🔍 📄 🆓**, 🏧 🌌. 👫 📄 ⚙️ 🌐 🐩 🔐 💂‍♂, & 📏-🖖 (🔃 3️⃣ 🗓️), **💂‍♂ 🤙 👍** ↩️ 👫 📉 🔆. - -🆔 🔐 ✔ & 📄 🏗 🔁. 👉 ✔ 🏧 🔕 👫 📄. - -💭 🏧 🛠️ & 🔕 👫 📄 👈 👆 💪 ✔️ **🔐 🇺🇸🔍, 🆓, ♾**. - -## 🇺🇸🔍 👩‍💻 - -📥 🖼 ❔ 🇺🇸🔍 🛠️ 💪 👀 💖, 🔁 🔁, 💸 🙋 ✴️ 💭 ⚠ 👩‍💻. - -### 🆔 📛 - -⚫️ 🔜 🎲 🌐 ▶️ 👆 **🏗** **🆔 📛**. ⤴️, 👆 🔜 🔗 ⚫️ 🏓 💽 (🎲 👆 🎏 ☁ 🐕‍🦺). - -👆 🔜 🎲 🤚 ☁ 💽 (🕹 🎰) ⚖️ 🕳 🎏, & ⚫️ 🔜 ✔️ 🔧 **📢 📢 📢**. - -🏓 💽(Ⓜ) 👆 🔜 🔗 ⏺ ("`A record`") ☝ **👆 🆔** 📢 **📢 📢 👆 💽**. - -👆 🔜 🎲 👉 🕐, 🥇 🕰, 🕐❔ ⚒ 🌐 🆙. - -/// tip - -👉 🆔 📛 🍕 🌌 ⏭ 🇺🇸🔍, ✋️ 🌐 🪀 🔛 🆔 & 📢 📢, ⚫️ 💸 💬 ⚫️ 📥. - -/// - -### 🏓 - -🔜 ➡️ 🎯 🔛 🌐 ☑ 🇺🇸🔍 🍕. - -🥇, 🖥 🔜 ✅ ⏮️ **🏓 💽** ⚫️❔ **📢 🆔**, 👉 💼, `someapp.example.com`. - -🏓 💽 🔜 💬 🖥 ⚙️ 🎯 **📢 📢**. 👈 🔜 📢 📢 📢 ⚙️ 👆 💽, 👈 👆 🔗 🏓 💽. - - - -### 🤝 🤝 ▶️ - -🖥 🔜 ⤴️ 🔗 ⏮️ 👈 📢 📢 🔛 **⛴ 4️⃣4️⃣3️⃣** (🇺🇸🔍 ⛴). - -🥇 🍕 📻 🛠️ 🔗 🖖 👩‍💻 & 💽 & 💭 🔐 🔑 👫 🔜 ⚙️, ♒️. - - - -👉 🔗 🖖 👩‍💻 & 💽 🛠️ 🤝 🔗 🤙 **🤝 🤝**. - -### 🤝 ⏮️ 👲 ↔ - -**🕴 1️⃣ 🛠️** 💽 💪 👂 🔛 🎯 **⛴** 🎯 **📢 📢**. 📤 💪 🎏 🛠️ 👂 🔛 🎏 ⛴ 🎏 📢 📢, ✋️ 🕴 1️⃣ 🔠 🌀 📢 📢 & ⛴. - -🤝 (🇺🇸🔍) ⚙️ 🎯 ⛴ `443` 🔢. 👈 ⛴ 👥 🔜 💪. - -🕴 1️⃣ 🛠️ 💪 👂 🔛 👉 ⛴, 🛠️ 👈 🔜 ⚫️ 🔜 **🤝 ❎ 🗳**. - -🤝 ❎ 🗳 🔜 ✔️ 🔐 1️⃣ ⚖️ 🌅 **🤝 📄** (🇺🇸🔍 📄). - -⚙️ **👲 ↔** 🔬 🔛, 🤝 ❎ 🗳 🔜 ✅ ❔ 🤝 (🇺🇸🔍) 📄 💪 ⚫️ 🔜 ⚙️ 👉 🔗, ⚙️ 1️⃣ 👈 🏏 🆔 📈 👩‍💻. - -👉 💼, ⚫️ 🔜 ⚙️ 📄 `someapp.example.com`. - - - -👩‍💻 ⏪ **💙** 👨‍💼 👈 🏗 👈 🤝 📄 (👉 💼 ➡️ 🗜, ✋️ 👥 🔜 👀 🔃 👈 ⏪), ⚫️ 💪 **✔** 👈 📄 ☑. - -⤴️, ⚙️ 📄, 👩‍💻 & 🤝 ❎ 🗳 **💭 ❔ 🗜** 🎂 **🕸 📻**. 👉 🏁 **🤝 🤝** 🍕. - -⏮️ 👉, 👩‍💻 & 💽 ✔️ **🗜 🕸 🔗**, 👉 ⚫️❔ 🤝 🚚. & ⤴️ 👫 💪 ⚙️ 👈 🔗 ▶️ ☑ **🇺🇸🔍 📻**. - -& 👈 ⚫️❔ **🇺🇸🔍** , ⚫️ ✅ **🇺🇸🔍** 🔘 **🔐 🤝 🔗** ↩️ 😁 (💽) 🕸 🔗. - -/// tip - -👀 👈 🔐 📻 🔨 **🕸 🎚**, 🚫 🇺🇸🔍 🎚. - -/// - -### 🇺🇸🔍 📨 - -🔜 👈 👩‍💻 & 💽 (🎯 🖥 & 🤝 ❎ 🗳) ✔️ **🗜 🕸 🔗**, 👫 💪 ▶️ **🇺🇸🔍 📻**. - -, 👩‍💻 📨 **🇺🇸🔍 📨**. 👉 🇺🇸🔍 📨 🔘 🗜 🤝 🔗. - - - -### 🗜 📨 - -🤝 ❎ 🗳 🔜 ⚙️ 🔐 ✔ **🗜 📨**, & 🔜 📶 **✅ (🗜) 🇺🇸🔍 📨** 🛠️ 🏃 🈸 (🖼 🛠️ ⏮️ Uvicorn 🏃‍♂ FastAPI 🈸). - - - -### 🇺🇸🔍 📨 - -🈸 🔜 🛠️ 📨 & 📨 **✅ (💽) 🇺🇸🔍 📨** 🤝 ❎ 🗳. - - - -### 🇺🇸🔍 📨 - -🤝 ❎ 🗳 🔜 ⤴️ **🗜 📨** ⚙️ ⚛ ✔ ⏭ (👈 ▶️ ⏮️ 📄 `someapp.example.com`), & 📨 ⚫️ 🔙 🖥. - -⏭, 🖥 🔜 ✔ 👈 📨 ☑ & 🗜 ⏮️ ▶️️ 🔐 🔑, ♒️. ⚫️ 🔜 ⤴️ **🗜 📨** & 🛠️ ⚫️. - - - -👩‍💻 (🖥) 🔜 💭 👈 📨 👟 ⚪️➡️ ☑ 💽 ↩️ ⚫️ ⚙️ ⚛ 👫 ✔ ⚙️ **🇺🇸🔍 📄** ⏭. - -### 💗 🈸 - -🎏 💽 (⚖️ 💽), 📤 💪 **💗 🈸**, 🖼, 🎏 🛠️ 📋 ⚖️ 💽. - -🕴 1️⃣ 🛠️ 💪 🚚 🎯 📢 & ⛴ (🤝 ❎ 🗳 👆 🖼) ✋️ 🎏 🈸/🛠️ 💪 🏃 🔛 💽(Ⓜ) 💁‍♂️, 📏 👫 🚫 🔄 ⚙️ 🎏 **🌀 📢 📢 & ⛴**. - - - -👈 🌌, 🤝 ❎ 🗳 💪 🍵 🇺🇸🔍 & 📄 **💗 🆔**, 💗 🈸, & ⤴️ 📶 📨 ▶️️ 🈸 🔠 💼. - -### 📄 🔕 - -☝ 🔮, 🔠 📄 🔜 **🕛** (🔃 3️⃣ 🗓️ ⏮️ 🏗 ⚫️). - -& ⤴️, 📤 🔜 ➕1️⃣ 📋 (💼 ⚫️ ➕1️⃣ 📋, 💼 ⚫️ 💪 🎏 🤝 ❎ 🗳) 👈 🔜 💬 ➡️ 🗜, & ♻ 📄(Ⓜ). - - - -**🤝 📄** **🔗 ⏮️ 🆔 📛**, 🚫 ⏮️ 📢 📢. - -, ♻ 📄, 🔕 📋 💪 **🎦** 🛃 (➡️ 🗜) 👈 ⚫️ 👐 **"👍" & 🎛 👈 🆔**. - -👈, & 🏗 🎏 🈸 💪, 📤 📚 🌌 ⚫️ 💪 ⚫️. 🌟 🌌: - -* **🔀 🏓 ⏺**. - * 👉, 🔕 📋 💪 🐕‍🦺 🔗 🏓 🐕‍🦺,, ⚓️ 🔛 🏓 🐕‍🦺 👆 ⚙️, 👉 5️⃣📆 ⚖️ 💪 🚫 🎛. -* **🏃 💽** (🌘 ⏮️ 📄 🛠️ 🛠️) 🔛 📢 📢 📢 🔗 ⏮️ 🆔. - * 👥 💬 🔛, 🕴 1️⃣ 🛠️ 💪 👂 🔛 🎯 📢 & ⛴. - * 👉 1️⃣ 🤔 ⚫️❔ ⚫️ 📶 ⚠ 🕐❔ 🎏 🤝 ❎ 🗳 ✊ 💅 📄 🔕 🛠️. - * ⏪, 👆 💪 ✔️ ⛔️ 🤝 ❎ 🗳 😖, ▶️ 🔕 📋 📎 📄, ⤴️ 🔗 👫 ⏮️ 🤝 ❎ 🗳, & ⤴️ ⏏ 🤝 ❎ 🗳. 👉 🚫 💯, 👆 📱(Ⓜ) 🔜 🚫 💪 ⏮️ 🕰 👈 🤝 ❎ 🗳 📆. - -🌐 👉 🔕 🛠️, ⏪ 🍦 📱, 1️⃣ 👑 🤔 ⚫️❔ 👆 🔜 💚 ✔️ **🎏 ⚙️ 🍵 🇺🇸🔍** ⏮️ 🤝 ❎ 🗳 ↩️ ⚙️ 🤝 📄 ⏮️ 🈸 💽 🔗 (✅ Uvicorn). - -## 🌃 - -✔️ **🇺🇸🔍** 📶 ⚠, & **🎯** 🏆 💼. 🌅 🎯 👆 👩‍💻 ✔️ 🚮 🤭 🇺🇸🔍 🔃 **🤔 👉 🔧** & ❔ 👫 👷. - -✋️ 🕐 👆 💭 🔰 ℹ **🇺🇸🔍 👩‍💻** 👆 💪 💪 🌀 & 🔗 🎏 🧰 ℹ 👆 🛠️ 🌐 🙅 🌌. - -⏭ 📃, 👤 🔜 🎦 👆 📚 🧱 🖼 ❔ ⚒ 🆙 **🇺🇸🔍** **FastAPI** 🈸. 👶 diff --git a/docs/em/docs/deployment/index.md b/docs/em/docs/deployment/index.md deleted file mode 100644 index 9bcf427b69..0000000000 --- a/docs/em/docs/deployment/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# 🛠️ - -🛠️ **FastAPI** 🈸 📶 ⏩. - -## ⚫️❔ 🔨 🛠️ ⛓ - -**🛠️** 🈸 ⛓ 🎭 💪 📶 ⚒ ⚫️ **💪 👩‍💻**. - -**🕸 🛠️**, ⚫️ 🛎 🔌 🚮 ⚫️ **🛰 🎰**, ⏮️ **💽 📋** 👈 🚚 👍 🎭, ⚖, ♒️, 👈 👆 **👩‍💻** 💪 **🔐** 🈸 ♻ & 🍵 🔁 ⚖️ ⚠. - -👉 🔅 **🛠️** ▶️, 🌐❔ 👆 🕧 🔀 📟, 💔 ⚫️ & ♻ ⚫️, ⛔️ & 🔁 🛠️ 💽, ♒️. - -## 🛠️ 🎛 - -📤 📚 🌌 ⚫️ ⚓️ 🔛 👆 🎯 ⚙️ 💼 & 🧰 👈 👆 ⚙️. - -👆 💪 **🛠️ 💽** 👆 ⚙️ 🌀 🧰, 👆 💪 ⚙️ **☁ 🐕‍🦺** 👈 🔨 🍕 👷 👆, ⚖️ 🎏 💪 🎛. - -👤 🔜 🎦 👆 👑 🔧 👆 🔜 🎲 ✔️ 🤯 🕐❔ 🛠️ **FastAPI** 🈸 (👐 🌅 ⚫️ ✔ 🙆 🎏 🆎 🕸 🈸). - -👆 🔜 👀 🌖 ℹ ✔️ 🤯 & ⚒ ⚫️ ⏭ 📄. 👶 diff --git a/docs/em/docs/deployment/manually.md b/docs/em/docs/deployment/manually.md deleted file mode 100644 index 4fa2d13e2d..0000000000 --- a/docs/em/docs/deployment/manually.md +++ /dev/null @@ -1,159 +0,0 @@ -# 🏃 💽 ❎ - Uvicorn - -👑 👜 👆 💪 🏃 **FastAPI** 🈸 🛰 💽 🎰 🔫 💽 📋 💖 **Uvicorn**. - -📤 3️⃣ 👑 🎛: - -* Uvicorn: ↕ 🎭 🔫 💽. -* Hypercorn: 🔫 💽 🔗 ⏮️ 🇺🇸🔍/2️⃣ & 🎻 👪 🎏 ⚒. -* 👸: 🔫 💽 🏗 ✳ 📻. - -## 💽 🎰 & 💽 📋 - -📤 🤪 ℹ 🔃 📛 ✔️ 🤯. 👶 - -🔤 "**💽**" 🛎 ⚙️ 🔗 👯‍♂️ 🛰/☁ 💻 (⚛ ⚖️ 🕹 🎰) & 📋 👈 🏃‍♂ 🔛 👈 🎰 (✅ Uvicorn). - -✔️ 👈 🤯 🕐❔ 👆 ✍ "💽" 🏢, ⚫️ 💪 🔗 1️⃣ 📚 2️⃣ 👜. - -🕐❔ 🔗 🛰 🎰, ⚫️ ⚠ 🤙 ⚫️ **💽**, ✋️ **🎰**, **💾** (🕹 🎰), **🕸**. 👈 🌐 🔗 🆎 🛰 🎰, 🛎 🏃‍♂ 💾, 🌐❔ 👆 🏃 📋. - -## ❎ 💽 📋 - -👆 💪 ❎ 🔫 🔗 💽 ⏮️: - -//// tab | Uvicorn - -* Uvicorn, 🌩-⏩ 🔫 💽, 🏗 🔛 uvloop & httptool. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -/// tip - -❎ `standard`, Uvicorn 🔜 ❎ & ⚙️ 👍 ➕ 🔗. - -👈 ✅ `uvloop`, ↕-🎭 💧-♻ `asyncio`, 👈 🚚 🦏 🛠️ 🎭 📈. - -/// - -//// - -//// tab | Hypercorn - -* Hypercorn, 🔫 💽 🔗 ⏮️ 🇺🇸🔍/2️⃣. - -
- -```console -$ pip install hypercorn - ----> 100% -``` - -
- -...⚖️ 🙆 🎏 🔫 💽. - -//// - -## 🏃 💽 📋 - -👆 💪 ⤴️ 🏃 👆 🈸 🎏 🌌 👆 ✔️ ⌛ 🔰, ✋️ 🍵 `--reload` 🎛, ✅: - -//// tab | Uvicorn - -
- -```console -$ uvicorn main:app --host 0.0.0.0 --port 80 - -INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) -``` - -
- -//// - -//// tab | Hypercorn - -
- -```console -$ hypercorn main:app --bind 0.0.0.0:80 - -Running on 0.0.0.0:8080 over http (CTRL + C to quit) -``` - -
- -//// - -/// warning - -💭 ❎ `--reload` 🎛 🚥 👆 ⚙️ ⚫️. - - `--reload` 🎛 🍴 🌅 🌅 ℹ, 🌅 ⚠, ♒️. - -⚫️ ℹ 📚 ⏮️ **🛠️**, ✋️ 👆 **🚫🔜 🚫** ⚙️ ⚫️ **🏭**. - -/// - -## Hypercorn ⏮️ 🎻 - -💃 & **FastAPI** ⚓️ 🔛 AnyIO, ❔ ⚒ 👫 🔗 ⏮️ 👯‍♂️ 🐍 🐩 🗃 & 🎻. - -👐, Uvicorn ⏳ 🕴 🔗 ⏮️ ✳, & ⚫️ 🛎 ⚙️ `uvloop`, ↕-🎭 💧-♻ `asyncio`. - -✋️ 🚥 👆 💚 🔗 ⚙️ **🎻**, ⤴️ 👆 💪 ⚙️ **Hypercorn** ⚫️ 🐕‍🦺 ⚫️. 👶 - -### ❎ Hypercorn ⏮️ 🎻 - -🥇 👆 💪 ❎ Hypercorn ⏮️ 🎻 🐕‍🦺: - -
- -```console -$ pip install "hypercorn[trio]" ----> 100% -``` - -
- -### 🏃 ⏮️ 🎻 - -⤴️ 👆 💪 🚶‍♀️ 📋 ⏸ 🎛 `--worker-class` ⏮️ 💲 `trio`: - -
- -```console -$ hypercorn main:app --worker-class trio -``` - -
- -& 👈 🔜 ▶️ Hypercorn ⏮️ 👆 📱 ⚙️ 🎻 👩‍💻. - -🔜 👆 💪 ⚙️ 🎻 🔘 👆 📱. ⚖️ 👍, 👆 💪 ⚙️ AnyIO, 🚧 👆 📟 🔗 ⏮️ 👯‍♂️ 🎻 & ✳. 👶 - -## 🛠️ 🔧 - -👫 🖼 🏃 💽 📋 (📧.Ⓜ Uvicorn), ▶️ **👁 🛠️**, 👂 🔛 🌐 📢 (`0.0.0.0`) 🔛 🔁 ⛴ (✅ `80`). - -👉 🔰 💭. ✋️ 👆 🔜 🎲 💚 ✊ 💅 🌖 👜, 💖: - -* 💂‍♂ - 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* 🧬 (🔢 🛠️ 🏃) -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -👤 🔜 💬 👆 🌅 🔃 🔠 👫 🔧, ❔ 💭 🔃 👫, & 🧱 🖼 ⏮️ 🎛 🍵 👫 ⏭ 📃. 👶 diff --git a/docs/em/docs/deployment/server-workers.md b/docs/em/docs/deployment/server-workers.md deleted file mode 100644 index eb29b2376a..0000000000 --- a/docs/em/docs/deployment/server-workers.md +++ /dev/null @@ -1,181 +0,0 @@ -# 💽 👨‍🏭 - 🐁 ⏮️ Uvicorn - -➡️ ✅ 🔙 👈 🛠️ 🔧 ⚪️➡️ ⏭: - -* 💂‍♂ - 🇺🇸🔍 -* 🏃‍♂ 🔛 🕴 -* ⏏ -* **🧬 (🔢 🛠️ 🏃)** -* 💾 -* ⏮️ 🔁 ⏭ ▶️ - -🆙 👉 ☝, ⏮️ 🌐 🔰 🩺, 👆 ✔️ 🎲 🏃‍♂ **💽 📋** 💖 Uvicorn, 🏃‍♂ **👁 🛠️**. - -🕐❔ 🛠️ 🈸 👆 🔜 🎲 💚 ✔️ **🧬 🛠️** ✊ 📈 **💗 🐚** & 💪 🍵 🌅 📨. - -👆 👀 ⏮️ 📃 🔃 [🛠️ 🔧](concepts.md){.internal-link target=_blank}, 📤 💗 🎛 👆 💪 ⚙️. - -📥 👤 🔜 🎦 👆 ❔ ⚙️ **🐁** ⏮️ **Uvicorn 👨‍🏭 🛠️**. - -/// info - -🚥 👆 ⚙️ 📦, 🖼 ⏮️ ☁ ⚖️ Kubernetes, 👤 🔜 💬 👆 🌅 🔃 👈 ⏭ 📃: [FastAPI 📦 - ☁](docker.md){.internal-link target=_blank}. - -🎯, 🕐❔ 🏃 🔛 **Kubernetes** 👆 🔜 🎲 **🚫** 💚 ⚙️ 🐁 & ↩️ 🏃 **👁 Uvicorn 🛠️ 📍 📦**, ✋️ 👤 🔜 💬 👆 🔃 ⚫️ ⏪ 👈 📃. - -/// - -## 🐁 ⏮️ Uvicorn 👨‍🏭 - -**🐁** ✴️ 🈸 💽 ⚙️ **🇨🇻 🐩**. 👈 ⛓ 👈 🐁 💪 🍦 🈸 💖 🏺 & ✳. 🐁 ⚫️ 🚫 🔗 ⏮️ **FastAPI**, FastAPI ⚙️ 🆕 **🔫 🐩**. - -✋️ 🐁 🐕‍🦺 👷 **🛠️ 👨‍💼** & 🤝 👩‍💻 💬 ⚫️ ❔ 🎯 **👨‍🏭 🛠️ 🎓** ⚙️. ⤴️ 🐁 🔜 ▶️ 1️⃣ ⚖️ 🌖 **👨‍🏭 🛠️** ⚙️ 👈 🎓. - -& **Uvicorn** ✔️ **🐁-🔗 👨‍🏭 🎓**. - -⚙️ 👈 🌀, 🐁 🔜 🚫 **🛠️ 👨‍💼**, 👂 🔛 **⛴** & **📢**. & ⚫️ 🔜 **📶** 📻 👨‍🏭 🛠️ 🏃 **Uvicorn 🎓**. - -& ⤴️ 🐁-🔗 **Uvicorn 👨‍🏭** 🎓 🔜 🈚 🏭 📊 📨 🐁 🔫 🐩 FastAPI ⚙️ ⚫️. - -## ❎ 🐁 & Uvicorn - -
- -```console -$ pip install "uvicorn[standard]" gunicorn - ----> 100% -``` - -
- -👈 🔜 ❎ 👯‍♂️ Uvicorn ⏮️ `standard` ➕ 📦 (🤚 ↕ 🎭) & 🐁. - -## 🏃 🐁 ⏮️ Uvicorn 👨‍🏭 - -⤴️ 👆 💪 🏃 🐁 ⏮️: - -
- -```console -$ gunicorn main:app --workers 4 --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:80 - -[19499] [INFO] Starting gunicorn 20.1.0 -[19499] [INFO] Listening at: http://0.0.0.0:80 (19499) -[19499] [INFO] Using worker: uvicorn.workers.UvicornWorker -[19511] [INFO] Booting worker with pid: 19511 -[19513] [INFO] Booting worker with pid: 19513 -[19514] [INFO] Booting worker with pid: 19514 -[19515] [INFO] Booting worker with pid: 19515 -[19511] [INFO] Started server process [19511] -[19511] [INFO] Waiting for application startup. -[19511] [INFO] Application startup complete. -[19513] [INFO] Started server process [19513] -[19513] [INFO] Waiting for application startup. -[19513] [INFO] Application startup complete. -[19514] [INFO] Started server process [19514] -[19514] [INFO] Waiting for application startup. -[19514] [INFO] Application startup complete. -[19515] [INFO] Started server process [19515] -[19515] [INFO] Waiting for application startup. -[19515] [INFO] Application startup complete. -``` - -
- -➡️ 👀 ⚫️❔ 🔠 👈 🎛 ⛓: - -* `main:app`: 👉 🎏 ❕ ⚙️ Uvicorn, `main` ⛓ 🐍 🕹 📛 "`main`",, 📁 `main.py`. & `app` 📛 🔢 👈 **FastAPI** 🈸. - * 👆 💪 🌈 👈 `main:app` 🌓 🐍 `import` 📄 💖: - - ```Python - from main import app - ``` - - * , ❤ `main:app` 🔜 🌓 🐍 `import` 🍕 `from main import app`. -* `--workers`: 🔢 👨‍🏭 🛠️ ⚙️, 🔠 🔜 🏃 Uvicorn 👨‍🏭, 👉 💼, 4️⃣ 👨‍🏭. -* `--worker-class`: 🐁-🔗 👨‍🏭 🎓 ⚙️ 👨‍🏭 🛠️. - * 📥 👥 🚶‍♀️ 🎓 👈 🐁 💪 🗄 & ⚙️ ⏮️: - - ```Python - import uvicorn.workers.UvicornWorker - ``` - -* `--bind`: 👉 💬 🐁 📢 & ⛴ 👂, ⚙️ ❤ (`:`) 🎏 📢 & ⛴. - * 🚥 👆 🏃‍♂ Uvicorn 🔗, ↩️ `--bind 0.0.0.0:80` (🐁 🎛) 👆 🔜 ⚙️ `--host 0.0.0.0` & `--port 80`. - -🔢, 👆 💪 👀 👈 ⚫️ 🎦 **🕹** (🛠️ 🆔) 🔠 🛠️ (⚫️ 🔢). - -👆 💪 👀 👈: - -* 🐁 **🛠️ 👨‍💼** ▶️ ⏮️ 🕹 `19499` (👆 💼 ⚫️ 🔜 🎏 🔢). -* ⤴️ ⚫️ ▶️ `Listening at: http://0.0.0.0:80`. -* ⤴️ ⚫️ 🔍 👈 ⚫️ ✔️ ⚙️ 👨‍🏭 🎓 `uvicorn.workers.UvicornWorker`. -* & ⤴️ ⚫️ ▶️ **4️⃣ 👨‍🏭**, 🔠 ⏮️ 🚮 👍 🕹: `19511`, `19513`, `19514`, & `19515`. - -🐁 🔜 ✊ 💅 🛠️ **☠️ 🛠️** & **🔁** 🆕 🕐 🚥 💚 🚧 🔢 👨‍🏭. 👈 ℹ 🍕 ⏮️ **⏏** 🔧 ⚪️➡️ 📇 🔛. - -👐, 👆 🔜 🎲 💚 ✔️ 🕳 🏞 ⚒ 💭 **⏏ 🐁** 🚥 💪, & **🏃 ⚫️ 🔛 🕴**, ♒️. - -## Uvicorn ⏮️ 👨‍🏭 - -Uvicorn ✔️ 🎛 ▶️ & 🏃 📚 **👨‍🏭 🛠️**. - -👐, 🔜, Uvicorn 🛠️ 🚚 👨‍🏭 🛠️ 🌅 📉 🌘 🐁. , 🚥 👆 💚 ✔️ 🛠️ 👨‍💼 👉 🎚 (🐍 🎚), ⤴️ ⚫️ 💪 👍 🔄 ⏮️ 🐁 🛠️ 👨‍💼. - -🙆 💼, 👆 🔜 🏃 ⚫️ 💖 👉: - -
- -```console -$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 -INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit) -INFO: Started parent process [27365] -INFO: Started server process [27368] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Started server process [27369] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Started server process [27370] -INFO: Waiting for application startup. -INFO: Application startup complete. -INFO: Started server process [27367] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -🕴 🆕 🎛 📥 `--workers` 💬 Uvicorn ▶️ 4️⃣ 👨‍🏭 🛠️. - -👆 💪 👀 👈 ⚫️ 🎦 **🕹** 🔠 🛠️, `27365` 👪 🛠️ (👉 **🛠️ 👨‍💼**) & 1️⃣ 🔠 👨‍🏭 🛠️: `27368`, `27369`, `27370`, & `27367`. - -## 🛠️ 🔧 - -📥 👆 👀 ❔ ⚙️ **🐁** (⚖️ Uvicorn) 🛠️ **Uvicorn 👨‍🏭 🛠️** **🔁** 🛠️ 🈸, ✊ 📈 **💗 🐚** 💽, & 💪 🍦 **🌅 📨**. - -⚪️➡️ 📇 🛠️ 🔧 ⚪️➡️ 🔛, ⚙️ 👨‍🏭 🔜 ✴️ ℹ ⏮️ **🧬** 🍕, & 🐥 🍖 ⏮️ **⏏**, ✋️ 👆 💪 ✊ 💅 🎏: - -* **💂‍♂ - 🇺🇸🔍** -* **🏃‍♂ 🔛 🕴** -* ***⏏*** -* 🧬 (🔢 🛠️ 🏃) -* **💾** -* **⏮️ 🔁 ⏭ ▶️** - -## 📦 & ☁ - -⏭ 📃 🔃 [FastAPI 📦 - ☁](docker.md){.internal-link target=_blank} 👤 🔜 💬 🎛 👆 💪 ⚙️ 🍵 🎏 **🛠️ 🔧**. - -👤 🔜 🎦 👆 **🛂 ☁ 🖼** 👈 🔌 **🐁 ⏮️ Uvicorn 👨‍🏭** & 🔢 📳 👈 💪 ⚠ 🙅 💼. - -📤 👤 🔜 🎦 👆 ❔ **🏗 👆 👍 🖼 ⚪️➡️ 🖌** 🏃 👁 Uvicorn 🛠️ (🍵 🐁). ⚫️ 🙅 🛠️ & 🎲 ⚫️❔ 👆 🔜 💚 🕐❔ ⚙️ 📎 📦 🧾 ⚙️ 💖 **Kubernetes**. - -## 🌃 - -👆 💪 ⚙️ **🐁** (⚖️ Uvicorn) 🛠️ 👨‍💼 ⏮️ Uvicorn 👨‍🏭 ✊ 📈 **👁-🐚 💽**, 🏃 **💗 🛠️ 🔗**. - -👆 💪 ⚙️ 👉 🧰 & 💭 🚥 👆 ⚒ 🆙 **👆 👍 🛠️ ⚙️** ⏪ ✊ 💅 🎏 🛠️ 🔧 👆. - -✅ 👅 ⏭ 📃 💡 🔃 **FastAPI** ⏮️ 📦 (✅ ☁ & Kubernetes). 👆 🔜 👀 👈 👈 🧰 ✔️ 🙅 🌌 ❎ 🎏 **🛠️ 🔧** 👍. 👶 diff --git a/docs/em/docs/deployment/versions.md b/docs/em/docs/deployment/versions.md deleted file mode 100644 index 6c9b8f9bb2..0000000000 --- a/docs/em/docs/deployment/versions.md +++ /dev/null @@ -1,93 +0,0 @@ -# 🔃 FastAPI ⏬ - -**FastAPI** ⏪ ➖ ⚙️ 🏭 📚 🈸 & ⚙️. & 💯 💰 🚧 1️⃣0️⃣0️⃣ 💯. ✋️ 🚮 🛠️ 🚚 🔜. - -🆕 ⚒ 🚮 🛎, 🐛 🔧 🛎, & 📟 🔁 📉. - -👈 ⚫️❔ ⏮️ ⏬ `0.x.x`, 👉 🎨 👈 🔠 ⏬ 💪 ⚠ ✔️ 💔 🔀. 👉 ⏩ ⚛ 🛠️ 🏛. - -👆 💪 ✍ 🏭 🈸 ⏮️ **FastAPI** ▶️️ 🔜 (& 👆 ✔️ 🎲 🔨 ⚫️ 🕰), 👆 ✔️ ⚒ 💭 👈 👆 ⚙️ ⏬ 👈 👷 ☑ ⏮️ 🎂 👆 📟. - -## 📌 👆 `fastapi` ⏬ - -🥇 👜 👆 🔜 "📌" ⏬ **FastAPI** 👆 ⚙️ 🎯 📰 ⏬ 👈 👆 💭 👷 ☑ 👆 🈸. - -🖼, ➡️ 💬 👆 ⚙️ ⏬ `0.45.0` 👆 📱. - -🚥 👆 ⚙️ `requirements.txt` 📁 👆 💪 ✔ ⏬ ⏮️: - -```txt -fastapi==0.45.0 -``` - -👈 🔜 ⛓ 👈 👆 🔜 ⚙️ ⚫️❔ ⏬ `0.45.0`. - -⚖️ 👆 💪 📌 ⚫️ ⏮️: - -```txt -fastapi>=0.45.0,<0.46.0 -``` - -👈 🔜 ⛓ 👈 👆 🔜 ⚙️ ⏬ `0.45.0` ⚖️ 🔛, ✋️ 🌘 🌘 `0.46.0`, 🖼, ⏬ `0.45.2` 🔜 🚫. - -🚥 👆 ⚙️ 🙆 🎏 🧰 🛠️ 👆 👷‍♂, 💖 🎶, Pipenv, ⚖️ 🎏, 👫 🌐 ✔️ 🌌 👈 👆 💪 ⚙️ 🔬 🎯 ⏬ 👆 📦. - -## 💪 ⏬ - -👆 💪 👀 💪 ⏬ (✅ ✅ ⚫️❔ ⏮️ 📰) [🚀 🗒](../release-notes.md){.internal-link target=_blank}. - -## 🔃 ⏬ - -📄 ⚛ 🛠️ 🏛, 🙆 ⏬ 🔛 `1.0.0` 💪 ⚠ 🚮 💔 🔀. - -FastAPI ⏩ 🏛 👈 🙆 "🐛" ⏬ 🔀 🐛 🔧 & 🚫-💔 🔀. - -/// tip - -"🐛" 🏁 🔢, 🖼, `0.2.3`, 🐛 ⏬ `3`. - -/// - -, 👆 🔜 💪 📌 ⏬ 💖: - -```txt -fastapi>=0.45.0,<0.46.0 -``` - -💔 🔀 & 🆕 ⚒ 🚮 "🇺🇲" ⏬. - -/// tip - -"🇺🇲" 🔢 🖕, 🖼, `0.2.3`, 🇺🇲 ⏬ `2`. - -/// - -## ♻ FastAPI ⏬ - -👆 🔜 🚮 💯 👆 📱. - -⏮️ **FastAPI** ⚫️ 📶 ⏩ (👏 💃), ✅ 🩺: [🔬](../tutorial/testing.md){.internal-link target=_blank} - -⏮️ 👆 ✔️ 💯, ⤴️ 👆 💪 ♻ **FastAPI** ⏬ 🌖 ⏮️ 1️⃣, & ⚒ 💭 👈 🌐 👆 📟 👷 ☑ 🏃 👆 💯. - -🚥 🌐 👷, ⚖️ ⏮️ 👆 ⚒ 💪 🔀, & 🌐 👆 💯 🚶‍♀️, ⤴️ 👆 💪 📌 👆 `fastapi` 👈 🆕 ⏮️ ⏬. - -## 🔃 💃 - -👆 🚫🔜 🚫 📌 ⏬ `starlette`. - -🎏 ⏬ **FastAPI** 🔜 ⚙️ 🎯 🆕 ⏬ 💃. - -, 👆 💪 ➡️ **FastAPI** ⚙️ ☑ 💃 ⏬. - -## 🔃 Pydantic - -Pydantic 🔌 💯 **FastAPI** ⏮️ 🚮 👍 💯, 🆕 ⏬ Pydantic (🔛 `1.0.0`) 🕧 🔗 ⏮️ FastAPI. - -👆 💪 📌 Pydantic 🙆 ⏬ 🔛 `1.0.0` 👈 👷 👆 & 🔛 `2.0.0`. - -🖼: - -```txt -pydantic>=1.2.0,<2.0.0 -``` diff --git a/docs/em/docs/features.md b/docs/em/docs/features.md deleted file mode 100644 index ccbed0cae3..0000000000 --- a/docs/em/docs/features.md +++ /dev/null @@ -1,201 +0,0 @@ -# ⚒ - -## FastAPI ⚒ - -**FastAPI** 🤝 👆 📄: - -### ⚓️ 🔛 📂 🐩 - -* 🗄 🛠️ 🏗, ✅ 📄 🛠️, 🔢, 💪 📨, 💂‍♂, ♒️. -* 🏧 📊 🏷 🧾 ⏮️ 🎻 🔗 (🗄 ⚫️ 🧢 🔛 🎻 🔗). -* 🔧 🤭 👫 🐩, ⏮️ 😔 🔬. ↩️ 👎 🧽 🔛 🔝. -* 👉 ✔ ⚙️ 🏧 **👩‍💻 📟 ⚡** 📚 🇪🇸. - -### 🏧 🩺 - -🎓 🛠️ 🧾 & 🔬 🕸 👩‍💻 🔢. 🛠️ ⚓️ 🔛 🗄, 📤 💗 🎛, 2️⃣ 🔌 🔢. - -* 🦁 🎚, ⏮️ 🎓 🔬, 🤙 & 💯 👆 🛠️ 🔗 ⚪️➡️ 🖥. - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* 🎛 🛠️ 🧾 ⏮️ 📄. - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### 🏛 🐍 - -⚫️ 🌐 ⚓️ 🔛 🐩 **🐍 3️⃣.6️⃣ 🆎** 📄 (👏 Pydantic). 🙅‍♂ 🆕 ❕ 💡. 🐩 🏛 🐍. - -🚥 👆 💪 2️⃣ ⏲ ↗️ ❔ ⚙️ 🐍 🆎 (🚥 👆 🚫 ⚙️ FastAPI), ✅ 📏 🔰: [🐍 🆎](python-types.md){.internal-link target=_blank}. - -👆 ✍ 🐩 🐍 ⏮️ 🆎: - -```Python -from datetime import date - -from pydantic import BaseModel - -# Declare a variable as a str -# and get editor support inside the function -def main(user_id: str): - return user_id - - -# A Pydantic model -class User(BaseModel): - id: int - name: str - joined: date -``` - -👈 💪 ⤴️ ⚙️ 💖: - -```Python -my_user: User = User(id=3, name="John Doe", joined="2018-07-19") - -second_user_data = { - "id": 4, - "name": "Mary", - "joined": "2018-11-30", -} - -my_second_user: User = User(**second_user_data) -``` - -/// info - -`**second_user_data` ⛓: - -🚶‍♀️ 🔑 & 💲 `second_user_data` #️⃣ 🔗 🔑-💲 ❌, 🌓: `User(id=4, name="Mary", joined="2018-11-30")` - -/// - -### 👨‍🎨 🐕‍🦺 - -🌐 🛠️ 🏗 ⏩ & 🏋️ ⚙️, 🌐 🚫 💯 🔛 💗 👨‍🎨 ⏭ ▶️ 🛠️, 🚚 🏆 🛠️ 💡. - -🏁 🐍 👩‍💻 🔬 ⚫️ 🆑 👈 🌅 ⚙️ ⚒ "✍". - -🎂 **FastAPI** 🛠️ ⚓️ 😌 👈. ✍ 👷 🌐. - -👆 🔜 🛎 💪 👟 🔙 🩺. - -📥 ❔ 👆 👨‍🎨 💪 ℹ 👆: - -* 🎙 🎙 📟: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -* 🗒: - -![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) - -👆 🔜 🤚 🛠️ 📟 👆 5️⃣📆 🤔 💪 ⏭. 🖼, `price` 🔑 🔘 🎻 💪 (👈 💪 ✔️ 🐦) 👈 👟 ⚪️➡️ 📨. - -🙅‍♂ 🌖 ⌨ ❌ 🔑 📛, 👟 🔙 & ➡ 🖖 🩺, ⚖️ 📜 🆙 & 🔽 🔎 🚥 👆 😒 ⚙️ `username` ⚖️ `user_name`. - -### 📏 - -⚫️ ✔️ 🤔 **🔢** 🌐, ⏮️ 📦 📳 🌐. 🌐 🔢 💪 👌-🎧 ⚫️❔ 👆 💪 & 🔬 🛠️ 👆 💪. - -✋️ 🔢, ⚫️ 🌐 **"👷"**. - -### 🔬 - -* 🔬 🌅 (⚖️ 🌐 ❓) 🐍 **💽 🆎**, 🔌: - * 🎻 🎚 (`dict`). - * 🎻 🎻 (`list`) ⚖ 🏬 🆎. - * 🎻 (`str`) 🏑, 🔬 🕙 & 👟 📐. - * 🔢 (`int`, `float`) ⏮️ 🕙 & 👟 💲, ♒️. - -* 🔬 🌅 😍 🆎, 💖: - * 📛. - * 📧. - * 🆔. - * ...& 🎏. - -🌐 🔬 🍵 👍-🏛 & 🏋️ **Pydantic**. - -### 💂‍♂ & 🤝 - -💂‍♂ & 🤝 🛠️. 🍵 🙆 ⚠ ⏮️ 💽 ⚖️ 📊 🏷. - -🌐 💂‍♂ ⚖ 🔬 🗄, 🔌: - -* 🇺🇸🔍 🔰. -* **Oauth2️⃣** (⏮️ **🥙 🤝**). ✅ 🔰 🔛 [Oauth2️⃣ ⏮️ 🥙](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. -* 🛠️ 🔑: - * 🎚. - * 🔢 🔢. - * 🍪, ♒️. - -➕ 🌐 💂‍♂ ⚒ ⚪️➡️ 💃 (🔌 **🎉 🍪**). - -🌐 🏗 ♻ 🧰 & 🦲 👈 ⏩ 🛠️ ⏮️ 👆 ⚙️, 📊 🏪, 🔗 & ☁ 💽, ♒️. - -### 🔗 💉 - -FastAPI 🔌 📶 ⏩ ⚙️, ✋️ 📶 🏋️ 🔗 💉 ⚙️. - -* 🔗 💪 ✔️ 🔗, 🏗 🔗 ⚖️ **"📊" 🔗**. -* 🌐 **🔁 🍵** 🛠️. -* 🌐 🔗 💪 🚚 💽 ⚪️➡️ 📨 & **↔ ➡ 🛠️** ⚛ & 🏧 🧾. -* **🏧 🔬** *➡ 🛠️* 🔢 🔬 🔗. -* 🐕‍🦺 🏗 👩‍💻 🤝 ⚙️, **💽 🔗**, ♒️. -* **🙅‍♂ ⚠** ⏮️ 💽, 🕸, ♒️. ✋️ ⏩ 🛠️ ⏮️ 🌐 👫. - -### ♾ "🔌-🔌" - -⚖️ 🎏 🌌, 🙅‍♂ 💪 👫, 🗄 & ⚙️ 📟 👆 💪. - -🙆 🛠️ 🏗 🙅 ⚙️ (⏮️ 🔗) 👈 👆 💪 ✍ "🔌-" 👆 🈸 2️⃣ ⏸ 📟 ⚙️ 🎏 📊 & ❕ ⚙️ 👆 *➡ 🛠️*. - -### 💯 - -* 1️⃣0️⃣0️⃣ 💯 💯 💰. -* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ 📟 🧢. -* ⚙️ 🏭 🈸. - -## 💃 ⚒ - -**FastAPI** 🍕 🔗 ⏮️ (& ⚓️ 🔛) 💃. , 🙆 🌖 💃 📟 👆 ✔️, 🔜 👷. - -`FastAPI` 🤙 🎧-🎓 `Starlette`. , 🚥 👆 ⏪ 💭 ⚖️ ⚙️ 💃, 🌅 🛠️ 🔜 👷 🎏 🌌. - -⏮️ **FastAPI** 👆 🤚 🌐 **💃**'Ⓜ ⚒ (FastAPI 💃 🔛 💊): - -* 🤙 🎆 🎭. ⚫️ 1️⃣ ⏩ 🐍 🛠️ 💪, 🔛 🇷🇪 ⏮️ **✳** & **🚶**. -* ** *️⃣ ** 🐕‍🦺. -* -🛠️ 🖥 📋. -* 🕴 & 🤫 🎉. -* 💯 👩‍💻 🏗 🔛 🇸🇲. -* **⚜**, 🗜, 🎻 📁, 🎏 📨. -* **🎉 & 🍪** 🐕‍🦺. -* 1️⃣0️⃣0️⃣ 💯 💯 💰. -* 1️⃣0️⃣0️⃣ 💯 🆎 ✍ ✍. - -## Pydantic ⚒ - -**FastAPI** 🍕 🔗 ⏮️ (& ⚓️ 🔛) Pydantic. , 🙆 🌖 Pydantic 📟 👆 ✔️, 🔜 👷. - -✅ 🔢 🗃 ⚓️ 🔛 Pydantic, 🐜Ⓜ, 🏭Ⓜ 💽. - -👉 ⛓ 👈 📚 💼 👆 💪 🚶‍♀️ 🎏 🎚 👆 🤚 ⚪️➡️ 📨 **🔗 💽**, 🌐 ✔ 🔁. - -🎏 ✔ 🎏 🌌 🤭, 📚 💼 👆 💪 🚶‍♀️ 🎚 👆 🤚 ⚪️➡️ 💽 **🔗 👩‍💻**. - -⏮️ **FastAPI** 👆 🤚 🌐 **Pydantic**'Ⓜ ⚒ (FastAPI ⚓️ 🔛 Pydantic 🌐 💽 🚚): - -* **🙅‍♂ 🔠**: - * 🙅‍♂ 🆕 🔗 🔑 ◾-🇪🇸 💡. - * 🚥 👆 💭 🐍 🆎 👆 💭 ❔ ⚙️ Pydantic. -* 🤾 🎆 ⏮️ 👆 **💾/🧶/🧠**: - * ↩️ Pydantic 📊 📊 👐 🎓 👆 🔬; 🚘-🛠️, 🧽, ✍ & 👆 🤔 🔜 🌐 👷 ☑ ⏮️ 👆 ✔ 💽. -* ✔ **🏗 📊**: - * ⚙️ 🔗 Pydantic 🏷, 🐍 `typing`'Ⓜ `List` & `Dict`, ♒️. - * & 💳 ✔ 🏗 💽 🔗 🎯 & 💪 🔬, ✅ & 📄 🎻 🔗. - * 👆 💪 ✔️ 🙇 **🐦 🎻** 🎚 & ✔️ 👫 🌐 ✔ & ✍. -* **🏧**: - * Pydantic ✔ 🛃 📊 🆎 🔬 ⚖️ 👆 💪 ↔ 🔬 ⏮️ 👩‍🔬 🔛 🏷 🎀 ⏮️ 💳 👨‍🎨. -* 1️⃣0️⃣0️⃣ 💯 💯 💰. diff --git a/docs/em/docs/help-fastapi.md b/docs/em/docs/help-fastapi.md deleted file mode 100644 index 9d802f9e4a..0000000000 --- a/docs/em/docs/help-fastapi.md +++ /dev/null @@ -1,269 +0,0 @@ -# ℹ FastAPI - 🤚 ℹ - -👆 💖 **FastAPI**❓ - -🔜 👆 💖 ℹ FastAPI, 🎏 👩‍💻, & 📕 ❓ - -⚖️ 🔜 👆 💖 🤚 ℹ ⏮️ **FastAPI**❓ - -📤 📶 🙅 🌌 ℹ (📚 🔌 1️⃣ ⚖️ 2️⃣ 🖊). - -& 📤 📚 🌌 🤚 ℹ 💁‍♂️. - -## 👱📔 📰 - -👆 💪 👱📔 (🐌) [**FastAPI & 👨‍👧‍👦** 📰](newsletter.md){.internal-link target=_blank} 🚧 ℹ 🔃: - -* 📰 🔃 FastAPI & 👨‍👧‍👦 👶 -* 🦮 👶 -* ⚒ 👶 -* 💔 🔀 👶 -* 💁‍♂ & 🎱 👶 - -## ⏩ FastAPI 🔛 👱📔 - -⏩ 🐶 Fastapi 🔛 **👱📔** 🤚 📰 📰 🔃 **FastAPI**. 👶 - -## ✴ **FastAPI** 📂 - -👆 💪 "✴" FastAPI 📂 (🖊 ✴ 🔼 🔝 ▶️️): https://github.com/fastapi/fastapi. 👶 👶 - -❎ ✴, 🎏 👩‍💻 🔜 💪 🔎 ⚫️ 🌅 💪 & 👀 👈 ⚫️ ✔️ ⏪ ⚠ 🎏. - -## ⌚ 📂 🗃 🚀 - -👆 💪 "⌚" FastAPI 📂 (🖊 "⌚" 🔼 🔝 ▶️️): https://github.com/fastapi/fastapi. 👶 - -📤 👆 💪 🖊 "🚀 🕴". - -🔨 ⚫️, 👆 🔜 📨 📨 (👆 📧) 🕐❔ 📤 🆕 🚀 (🆕 ⏬) **FastAPI** ⏮️ 🐛 🔧 & 🆕 ⚒. - -## 🔗 ⏮️ 📕 - -👆 💪 🔗 ⏮️ 👤 (🇹🇦 🇩🇬 / `tiangolo`), 📕. - -👆 💪: - -* ⏩ 👤 🔛 **📂**. - * 👀 🎏 📂 ℹ 🏗 👤 ✔️ ✍ 👈 💪 ℹ 👆. - * ⏩ 👤 👀 🕐❔ 👤 ✍ 🆕 📂 ℹ 🏗. -* ⏩ 👤 🔛 **👱📔** ⚖️ . - * 💬 👤 ❔ 👆 ⚙️ FastAPI (👤 💌 👂 👈). - * 👂 🕐❔ 👤 ⚒ 🎉 ⚖️ 🚀 🆕 🧰. - * 👆 💪 ⏩ 🐶 Fastapi 🔛 👱📔 (🎏 🏧). -* 🔗 ⏮️ 👤 🔛 **👱📔**. - * 👂 🕐❔ 👤 ⚒ 🎉 ⚖️ 🚀 🆕 🧰 (👐 👤 ⚙️ 👱📔 🌖 🛎 🤷 ♂). -* ✍ ⚫️❔ 👤 ✍ (⚖️ ⏩ 👤) 🔛 **🇸🇲.** ⚖️ **🔉**. - * ✍ 🎏 💭, 📄, & ✍ 🔃 🧰 👤 ✔️ ✍. - * ⏩ 👤 ✍ 🕐❔ 👤 ✍ 🕳 🆕. - -## 👱📔 🔃 **FastAPI** - -👱📔 🔃 **FastAPI** & ➡️ 👤 & 🎏 💭 ⚫️❔ 👆 💖 ⚫️. 👶 - -👤 💌 👂 🔃 ❔ **FastAPI** 💆‍♂ ⚙️, ⚫️❔ 👆 ✔️ 💖 ⚫️, ❔ 🏗/🏢 👆 ⚙️ ⚫️, ♒️. - -## 🗳 FastAPI - -* 🗳 **FastAPI** 📐. -* 🗳 **FastAPI** 📱. -* 💬 👆 ⚙️ **FastAPI** 🔛 ℹ. - -## ℹ 🎏 ⏮️ ❔ 📂 - -👆 💪 🔄 & ℹ 🎏 ⏮️ 👫 ❔: - -* 📂 💬 -* 📂 ❔ - -📚 💼 👆 5️⃣📆 ⏪ 💭 ❔ 📚 ❔. 👶 - -🚥 👆 🤝 📚 👫👫 ⏮️ 👫 ❔, 👆 🔜 ▶️️ 🛂 [FastAPI 🕴](fastapi-people.md#_2){.internal-link target=_blank}. 👶 - -💭, 🏆 ⚠ ☝: 🔄 😇. 👫👫 👟 ⏮️ 👫 😩 & 📚 💼 🚫 💭 🏆 🌌, ✋️ 🔄 🏆 👆 💪 😇. 👶 - -💭 **FastAPI** 👪 😇 & 👍. 🎏 🕰, 🚫 🚫 🎭 ⚖️ 😛 🎭 ⤵ 🎏. 👥 ✔️ ✊ 💅 🔠 🎏. - ---- - -📥 ❔ ℹ 🎏 ⏮️ ❔ (💬 ⚖️ ❔): - -### 🤔 ❔ - -* ✅ 🚥 👆 💪 🤔 ⚫️❔ **🎯** & ⚙️ 💼 👨‍💼 💬. - -* ⤴️ ✅ 🚥 ❔ (⭕ 👪 ❔) **🆑**. - -* 📚 💼 ❔ 💭 🔃 👽 ⚗ ⚪️➡️ 👩‍💻, ✋️ 📤 💪 **👍** 1️⃣. 🚥 👆 💪 🤔 ⚠ & ⚙️ 💼 👍, 👆 💪 💪 🤔 👍 **🎛 ⚗**. - -* 🚥 👆 💪 🚫 🤔 ❔, 💭 🌖 **ℹ**. - -### 🔬 ⚠ - -🌅 💼 & 🏆 ❔ 📤 🕳 🔗 👨‍💼 **⏮️ 📟**. - -📚 💼 👫 🔜 🕴 📁 🧬 📟, ✋️ 👈 🚫 🥃 **🔬 ⚠**. - -* 👆 💪 💭 👫 🚚 ⭐, 🔬, 🖼, 👈 👆 💪 **📁-📋** & 🏃 🌐 👀 🎏 ❌ ⚖️ 🎭 👫 👀, ⚖️ 🤔 👫 ⚙️ 💼 👍. - -* 🚥 👆 😟 💁‍♂️ 👍, 👆 💪 🔄 **✍ 🖼** 💖 👈 👆, 🧢 🔛 📛 ⚠. ✔️ 🤯 👈 👉 💪 ✊ 📚 🕰 & ⚫️ 💪 👻 💭 👫 ✍ ⚠ 🥇. - -### 🤔 ⚗ - -* ⏮️ 💆‍♂ 💪 🤔 ❔, 👆 💪 🤝 👫 💪 **❔**. - -* 📚 💼, ⚫️ 👍 🤔 👫 **📈 ⚠ ⚖️ ⚙️ 💼**, ↩️ 📤 5️⃣📆 👍 🌌 ❎ ⚫️ 🌘 ⚫️❔ 👫 🔄. - -### 💭 🔐 - -🚥 👫 📨, 📤 ↕ 🤞 👆 🔜 ✔️ ❎ 👫 ⚠, ㊗, **👆 💂**❗ 🦸 - -* 🔜, 🚥 👈 ❎ 👫 ⚠, 👆 💪 💭 👫: - - * 📂 💬: ™ 🏤 **❔**. - * 📂 ❔: **🔐** ❔**. - -## ⌚ 📂 🗃 - -👆 💪 "⌚" FastAPI 📂 (🖊 "⌚" 🔼 🔝 ▶️️): https://github.com/fastapi/fastapi. 👶 - -🚥 👆 🖊 "👀" ↩️ "🚀 🕴" 👆 🔜 📨 📨 🕐❔ 👱 ✍ 🆕 ❔ ⚖️ ❔. 👆 💪 ✔ 👈 👆 🕴 💚 🚨 🔃 🆕 ❔, ⚖️ 💬, ⚖️ 🎸, ♒️. - -⤴️ 👆 💪 🔄 & ℹ 👫 ❎ 👈 ❔. - -## 💭 ❔ - -👆 💪 ✍ 🆕 ❔ 📂 🗃, 🖼: - -* 💭 **❔** ⚖️ 💭 🔃 **⚠**. -* 🤔 🆕 **⚒**. - -**🗒**: 🚥 👆 ⚫️, ⤴️ 👤 🔜 💭 👆 ℹ 🎏. 👶 - -## 📄 🚲 📨 - -👆 💪 ℹ 👤 📄 🚲 📨 ⚪️➡️ 🎏. - -🔄, 🙏 🔄 👆 🏆 😇. 👶 - ---- - -📥 ⚫️❔ ✔️ 🤯 & ❔ 📄 🚲 📨: - -### 🤔 ⚠ - -* 🥇, ⚒ 💭 👆 **🤔 ⚠** 👈 🚲 📨 🔄 ❎. ⚫️ 💪 ✔️ 📏 💬 📂 💬 ⚖️ ❔. - -* 📤 👍 🤞 👈 🚲 📨 🚫 🤙 💪 ↩️ ⚠ 💪 ❎ **🎏 🌌**. ⤴️ 👆 💪 🤔 ⚖️ 💭 🔃 👈. - -### 🚫 😟 🔃 👗 - -* 🚫 😟 💁‍♂️ 🌅 🔃 👜 💖 💕 📧 👗, 👤 🔜 🥬 & 🔗 🛃 💕 ❎. - -* 🚫 😟 🔃 👗 🚫, 📤 ⏪ 🏧 🧰 ✅ 👈. - -& 🚥 📤 🙆 🎏 👗 ⚖️ ⚖ 💪, 👤 🔜 💭 🔗 👈, ⚖️ 👤 🔜 🚮 💕 🔛 🔝 ⏮️ 💪 🔀. - -### ✅ 📟 - -* ✅ & ✍ 📟, 👀 🚥 ⚫️ ⚒ 🔑, **🏃 ⚫️ 🌐** & 👀 🚥 ⚫️ 🤙 ❎ ⚠. - -* ⤴️ **🏤** 💬 👈 👆 👈, 👈 ❔ 👤 🔜 💭 👆 🤙 ✅ ⚫️. - -/// info - -👐, 👤 💪 🚫 🎯 💙 🎸 👈 ✔️ 📚 ✔. - -📚 🕰 ⚫️ ✔️ 🔨 👈 📤 🎸 ⏮️ 3️⃣, 5️⃣ ⚖️ 🌅 ✔, 🎲 ↩️ 📛 😌, ✋️ 🕐❔ 👤 ✅ 🎸, 👫 🤙 💔, ✔️ 🐛, ⚖️ 🚫 ❎ ⚠ 👫 🛄 ❎. 👶 - -, ⚫️ 🤙 ⚠ 👈 👆 🤙 ✍ & 🏃 📟, & ➡️ 👤 💭 🏤 👈 👆. 👶 - -/// - -* 🚥 🇵🇷 💪 📉 🌌, 👆 💪 💭 👈, ✋️ 📤 🙅‍♂ 💪 💁‍♂️ 😟, 📤 5️⃣📆 📚 🤔 ☝ 🎑 (& 👤 🔜 ✔️ 👇 👍 👍 👶), ⚫️ 👻 🚥 👆 💪 🎯 🔛 ⚛ 👜. - -### 💯 - -* ℹ 👤 ✅ 👈 🇵🇷 ✔️ **💯**. - -* ✅ 👈 💯 **❌** ⏭ 🇵🇷. 👶 - -* ⤴️ ✅ 👈 💯 **🚶‍♀️** ⏮️ 🇵🇷. 👶 - -* 📚 🎸 🚫 ✔️ 💯, 👆 💪 **🎗** 👫 🚮 💯, ⚖️ 👆 💪 **🤔** 💯 👆. 👈 1️⃣ 👜 👈 🍴 🌅 🕰 & 👆 💪 ℹ 📚 ⏮️ 👈. - -* ⤴️ 🏤 ⚫️❔ 👆 🔄, 👈 🌌 👤 🔜 💭 👈 👆 ✅ ⚫️. 👶 - -## ✍ 🚲 📨 - -👆 💪 [📉](contributing.md){.internal-link target=_blank} ℹ 📟 ⏮️ 🚲 📨, 🖼: - -* 🔧 🤭 👆 🔎 🔛 🧾. -* 💰 📄, 📹, ⚖️ 📻 👆 ✍ ⚖️ 🔎 🔃 FastAPI ✍ 👉 📁. - * ⚒ 💭 👆 🚮 👆 🔗 ▶️ 🔗 📄. -* ℹ [💬 🧾](contributing.md#_9){.internal-link target=_blank} 👆 🇪🇸. - * 👆 💪 ℹ 📄 ✍ ✍ 🎏. -* 🛠️ 🆕 🧾 📄. -* 🔧 ♻ ❔/🐛. - * ⚒ 💭 🚮 💯. -* 🚮 🆕 ⚒. - * ⚒ 💭 🚮 💯. - * ⚒ 💭 🚮 🧾 🚥 ⚫️ 🔗. - -## ℹ 🚧 FastAPI - -ℹ 👤 🚧 **FastAPI**❗ 👶 - -📤 📚 👷, & 🏆 ⚫️, **👆** 💪 ⚫️. - -👑 📋 👈 👆 💪 ▶️️ 🔜: - -* [ℹ 🎏 ⏮️ ❔ 📂](#i){.internal-link target=_blank} (👀 📄 🔛). -* [📄 🚲 📨](#i){.internal-link target=_blank} (👀 📄 🔛). - -👈 2️⃣ 📋 ⚫️❔ **🍴 🕰 🏆**. 👈 👑 👷 🏆 FastAPI. - -🚥 👆 💪 ℹ 👤 ⏮️ 👈, **👆 🤝 👤 🚧 FastAPI** & ⚒ 💭 ⚫️ 🚧 **🛠️ ⏩ & 👻**. 👶 - -## 🛑 💬 - -🛑 👶 😧 💬 💽 👶 & 🤙 👅 ⏮️ 🎏 FastAPI 👪. - -/// tip - -❔, 💭 👫 📂 💬, 📤 🌅 👍 🤞 👆 🔜 📨 ℹ [FastAPI 🕴](fastapi-people.md#_2){.internal-link target=_blank}. - -⚙️ 💬 🕴 🎏 🏢 💬. - -/// - -### 🚫 ⚙️ 💬 ❔ - -✔️ 🤯 👈 💬 ✔ 🌅 "🆓 💬", ⚫️ ⏩ 💭 ❔ 👈 💁‍♂️ 🏢 & 🌅 ⚠ ❔,, 👆 💪 🚫 📨 ❔. - -📂, 📄 🔜 🦮 👆 ✍ ▶️️ ❔ 👈 👆 💪 🌖 💪 🤚 👍 ❔, ⚖️ ❎ ⚠ 👆 ⏭ 💬. & 📂 👤 💪 ⚒ 💭 👤 🕧 ❔ 🌐, 🚥 ⚫️ ✊ 🕰. 👤 💪 🚫 🤙 👈 ⏮️ 💬 ⚙️. 👶 - -💬 💬 ⚙️ 🚫 💪 📇 📂, ❔ & ❔ 5️⃣📆 🤚 💸 💬. & 🕴 🕐 📂 💯 ▶️️ [FastAPI 🕴](fastapi-people.md#_2){.internal-link target=_blank}, 👆 🔜 🌅 🎲 📨 🌅 🙋 📂. - -🔛 🎏 🚄, 📤 💯 👩‍💻 💬 ⚙️, 📤 ↕ 🤞 👆 🔜 🔎 👱 💬 📤, 🌖 🌐 🕰. 👶 - -## 💰 📕 - -👆 💪 💰 🐕‍🦺 📕 (👤) 🔘 📂 💰. - -📤 👆 💪 🛍 👤 ☕ 👶 👶 💬 👏. 👶 - -& 👆 💪 ▶️️ 🥇1st ⚖️ 🌟 💰 FastAPI. 👶 👶 - -## 💰 🧰 👈 🏋️ FastAPI - -👆 ✔️ 👀 🧾, FastAPI 🧍 🔛 ⌚ 🐘, 💃 & Pydantic. - -👆 💪 💰: - -* ✡ 🍏 (Pydantic) -* 🗜 (💃, Uvicorn) - ---- - -👏 ❗ 👶 diff --git a/docs/em/docs/history-design-future.md b/docs/em/docs/history-design-future.md deleted file mode 100644 index fe36643959..0000000000 --- a/docs/em/docs/history-design-future.md +++ /dev/null @@ -1,79 +0,0 @@ -# 📖, 🔧 & 🔮 - -🕰 🏁, **FastAPI** 👩‍💻 💭: - -> ⚫️❔ 📖 👉 🏗 ❓ ⚫️ 😑 ✔️ 👟 ⚪️➡️ 🕳 👌 👩‍❤‍👨 🗓️ [...] - -📥 🐥 🍖 👈 📖. - -## 🎛 - -👤 ✔️ 🏗 🔗 ⏮️ 🏗 📄 📚 1️⃣2️⃣🗓️ (🎰 🏫, 📎 ⚙️, 🔁 👨‍🏭, ☁ 💽, ♒️), ↘️ 📚 🏉 👩‍💻. - -🍕 👈, 👤 💪 🔬, 💯 & ⚙️ 📚 🎛. - -📖 **FastAPI** 👑 🍕 📖 🚮 ⏪. - -🙆‍♀ 📄 [🎛](alternatives.md){.internal-link target=_blank}: - -
- -**FastAPI** 🚫🔜 🔀 🚥 🚫 ⏮️ 👷 🎏. - -📤 ✔️ 📚 🧰 ✍ ⏭ 👈 ✔️ ℹ 😮 🚮 🏗. - -👤 ✔️ ❎ 🏗 🆕 🛠️ 📚 1️⃣2️⃣🗓️. 🥇 👤 🔄 ❎ 🌐 ⚒ 📔 **FastAPI** ⚙️ 📚 🎏 🛠️, 🔌-🔌, & 🧰. - -✋️ ☝, 📤 🙅‍♂ 🎏 🎛 🌘 🏗 🕳 👈 🚚 🌐 👫 ⚒, ✊ 🏆 💭 ⚪️➡️ ⏮️ 🧰, & 🌀 👫 🏆 🌌 💪, ⚙️ 🇪🇸 ⚒ 👈 ➖🚫 💪 ⏭ (🐍 3️⃣.6️⃣ ➕ 🆎 🔑). - -
- -## 🔬 - -⚙️ 🌐 ⏮️ 🎛 👤 ✔️ 🤞 💡 ⚪️➡️ 🌐 👫, ✊ 💭, & 🌀 👫 🏆 🌌 👤 💪 🔎 👤 & 🏉 👩‍💻 👤 ✔️ 👷 ⏮️. - -🖼, ⚫️ 🆑 👈 🎲 ⚫️ 🔜 ⚓️ 🔛 🐩 🐍 🆎 🔑. - -, 🏆 🎯 ⚙️ ⏪ ♻ 🐩. - -, ⏭ ▶️ 📟 **FastAPI**, 👤 💸 📚 🗓️ 🎓 🔌 🗄, 🎻 🔗, Oauth2️⃣, ♒️. 🎯 👫 💛, 🔀, & 🔺. - -## 🔧 - -⤴️ 👤 💸 🕰 🔧 👩‍💻 "🛠️" 👤 💚 ✔️ 👩‍💻 (👩‍💻 ⚙️ FastAPI). - -👤 💯 📚 💭 🏆 🌟 🐍 👨‍🎨: 🗒, 🆚 📟, 🎠 🧢 👨‍🎨. - -🏁 🐍 👩‍💻 🔬, 👈 📔 🔃 8️⃣0️⃣ 💯 👩‍💻. - -⚫️ ⛓ 👈 **FastAPI** 🎯 💯 ⏮️ 👨‍🎨 ⚙️ 8️⃣0️⃣ 💯 🐍 👩‍💻. & 🏆 🎏 👨‍🎨 😑 👷 ➡, 🌐 🚮 💰 🔜 👷 🌖 🌐 👨‍🎨. - -👈 🌌 👤 💪 🔎 🏆 🌌 📉 📟 ❎ 🌅 💪, ✔️ 🛠️ 🌐, 🆎 & ❌ ✅, ♒️. - -🌐 🌌 👈 🚚 🏆 🛠️ 💡 🌐 👩‍💻. - -## 📄 - -⏮️ 🔬 📚 🎛, 👤 💭 👈 👤 🔜 ⚙️ **Pydantic** 🚮 📈. - -⤴️ 👤 📉 ⚫️, ⚒ ⚫️ 🍕 🛠️ ⏮️ 🎻 🔗, 🐕‍🦺 🎏 🌌 🔬 ⚛ 📄, & 📉 👨‍🎨 🐕‍🦺 (🆎 ✅, ✍) ⚓️ 🔛 💯 📚 👨‍🎨. - -⏮️ 🛠️, 👤 📉 **💃**, 🎏 🔑 📄. - -## 🛠️ - -🕰 👤 ▶️ 🏗 **FastAPI** ⚫️, 🏆 🍖 ⏪ 🥉, 🔧 🔬, 📄 & 🧰 🔜, & 💡 🔃 🐩 & 🔧 🆑 & 🍋. - -## 🔮 - -👉 ☝, ⚫️ ⏪ 🆑 👈 **FastAPI** ⏮️ 🚮 💭 ➖ ⚠ 📚 👫👫. - -⚫️ 💆‍♂ 👐 🤭 ⏮️ 🎛 ♣ 📚 ⚙️ 💼 👍. - -📚 👩‍💻 & 🏉 ⏪ 🪀 🔛 **FastAPI** 👫 🏗 (🔌 👤 & 👇 🏉). - -✋️, 📤 📚 📈 & ⚒ 👟. - -**FastAPI** ✔️ 👑 🔮 ⤴️. - -& [👆 ℹ](help-fastapi.md){.internal-link target=_blank} 📉 👍. diff --git a/docs/em/docs/how-to/conditional-openapi.md b/docs/em/docs/how-to/conditional-openapi.md deleted file mode 100644 index e47ea0c35c..0000000000 --- a/docs/em/docs/how-to/conditional-openapi.md +++ /dev/null @@ -1,56 +0,0 @@ -# 🎲 🗄 - -🚥 👆 💪, 👆 💪 ⚙️ ⚒ & 🌐 🔢 🔗 🗄 ✔ ⚓️ 🔛 🌐, & ❎ ⚫️ 🍕. - -## 🔃 💂‍♂, 🔗, & 🩺 - -🕵‍♂ 👆 🧾 👩‍💻 🔢 🏭 *🚫🔜 🚫* 🌌 🛡 👆 🛠️. - -👈 🚫 🚮 🙆 ➕ 💂‍♂ 👆 🛠️, *➡ 🛠️* 🔜 💪 🌐❔ 👫. - -🚥 📤 💂‍♂ ⚠ 👆 📟, ⚫️ 🔜 🔀. - -🕵‍♂ 🧾 ⚒ ⚫️ 🌅 ⚠ 🤔 ❔ 🔗 ⏮️ 👆 🛠️, & 💪 ⚒ ⚫️ 🌅 ⚠ 👆 ℹ ⚫️ 🏭. ⚫️ 💪 🤔 🎯 📨 💂‍♂ 🔘 🌌. - -🚥 👆 💚 🔐 👆 🛠️, 📤 📚 👍 👜 👆 💪, 🖼: - -* ⚒ 💭 👆 ✔️ 👍 🔬 Pydantic 🏷 👆 📨 💪 & 📨. -* 🔗 🙆 ✔ ✔ & 🔑 ⚙️ 🔗. -* 🙅 🏪 🔢 🔐, 🕴 🔐#️⃣. -* 🛠️ & ⚙️ 👍-💭 🔐 🧰, 💖 🇸🇲 & 🥙 🤝, ♒️. -* 🚮 🌅 🧽 ✔ 🎛 ⏮️ Oauth2️⃣ ↔ 🌐❔ 💪. -* ...♒️. - -👐, 👆 5️⃣📆 ✔️ 📶 🎯 ⚙️ 💼 🌐❔ 👆 🤙 💪 ❎ 🛠️ 🩺 🌐 (✅ 🏭) ⚖️ ⚓️ 🔛 📳 ⚪️➡️ 🌐 🔢. - -## 🎲 🗄 ⚪️➡️ ⚒ & 🇨🇻 { - -👆 💪 💪 ⚙️ 🎏 Pydantic ⚒ 🔗 👆 🏗 🗄 & 🩺 ⚜. - -🖼: - -{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} - -📥 👥 📣 ⚒ `openapi_url` ⏮️ 🎏 🔢 `"/openapi.json"`. - -& ⤴️ 👥 ⚙️ ⚫️ 🕐❔ 🏗 `FastAPI` 📱. - -⤴️ 👆 💪 ❎ 🗄 (✅ 🎚 🩺) ⚒ 🌐 🔢 `OPENAPI_URL` 🛁 🎻, 💖: - -
- -```console -$ OPENAPI_URL= uvicorn main:app - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -⤴️ 🚥 👆 🚶 📛 `/openapi.json`, `/docs`, ⚖️ `/redoc` 👆 🔜 🤚 `404 Not Found` ❌ 💖: - -```JSON -{ - "detail": "Not Found" -} -``` diff --git a/docs/em/docs/how-to/custom-request-and-route.md b/docs/em/docs/how-to/custom-request-and-route.md deleted file mode 100644 index 32d7fbfec5..0000000000 --- a/docs/em/docs/how-to/custom-request-and-route.md +++ /dev/null @@ -1,109 +0,0 @@ -# 🛃 📨 & APIRoute 🎓 - -💼, 👆 5️⃣📆 💚 🔐 ⚛ ⚙️ `Request` & `APIRoute` 🎓. - -🎯, 👉 5️⃣📆 👍 🎛 ⚛ 🛠️. - -🖼, 🚥 👆 💚 ✍ ⚖️ 🔬 📨 💪 ⏭ ⚫️ 🛠️ 👆 🈸. - -/// danger - -👉 "🏧" ⚒. - -🚥 👆 ▶️ ⏮️ **FastAPI** 👆 💪 💚 🚶 👉 📄. - -/// - -## ⚙️ 💼 - -⚙️ 💼 🔌: - -* 🏭 🚫-🎻 📨 💪 🎻 (✅ `msgpack`). -* 🗜 🗜-🗜 📨 💪. -* 🔁 🚨 🌐 📨 💪. - -## 🚚 🛃 📨 💪 🔢 - -➡️ 👀 ❔ ⚒ ⚙️ 🛃 `Request` 🏿 🗜 🗜 📨. - -& `APIRoute` 🏿 ⚙️ 👈 🛃 📨 🎓. - -### ✍ 🛃 `GzipRequest` 🎓 - -/// tip - -👉 🧸 🖼 🎦 ❔ ⚫️ 👷, 🚥 👆 💪 🗜 🐕‍🦺, 👆 💪 ⚙️ 🚚 [`GzipMiddleware`](../advanced/middleware.md#gzipmiddleware){.internal-link target=_blank}. - -/// - -🥇, 👥 ✍ `GzipRequest` 🎓, ❔ 🔜 📁 `Request.body()` 👩‍🔬 🗜 💪 🔍 ☑ 🎚. - -🚥 📤 🙅‍♂ `gzip` 🎚, ⚫️ 🔜 🚫 🔄 🗜 💪. - -👈 🌌, 🎏 🛣 🎓 💪 🍵 🗜 🗜 ⚖️ 🗜 📨. - -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[8:15] *} - -### ✍ 🛃 `GzipRoute` 🎓 - -⏭, 👥 ✍ 🛃 🏿 `fastapi.routing.APIRoute` 👈 🔜 ⚒ ⚙️ `GzipRequest`. - -👉 🕰, ⚫️ 🔜 📁 👩‍🔬 `APIRoute.get_route_handler()`. - -👉 👩‍🔬 📨 🔢. & 👈 🔢 ⚫️❔ 🔜 📨 📨 & 📨 📨. - -📥 👥 ⚙️ ⚫️ ✍ `GzipRequest` ⚪️➡️ ⏮️ 📨. - -{* ../../docs_src/custom_request_and_route/tutorial001.py hl[18:26] *} - -/// note | 📡 ℹ - -`Request` ✔️ `request.scope` 🔢, 👈 🐍 `dict` ⚗ 🗃 🔗 📨. - - `Request` ✔️ `request.receive`, 👈 🔢 "📨" 💪 📨. - - `scope` `dict` & `receive` 🔢 👯‍♂️ 🍕 🔫 🔧. - - & 👈 2️⃣ 👜, `scope` & `receive`, ⚫️❔ 💪 ✍ 🆕 `Request` 👐. - -💡 🌅 🔃 `Request` ✅ 💃 🩺 🔃 📨. - -/// - -🕴 👜 🔢 📨 `GzipRequest.get_route_handler` 🔨 🎏 🗜 `Request` `GzipRequest`. - -🔨 👉, 👆 `GzipRequest` 🔜 ✊ 💅 🗜 📊 (🚥 💪) ⏭ 🚶‍♀️ ⚫️ 👆 *➡ 🛠️*. - -⏮️ 👈, 🌐 🏭 ⚛ 🎏. - -✋️ ↩️ 👆 🔀 `GzipRequest.body`, 📨 💪 🔜 🔁 🗜 🕐❔ ⚫️ 📐 **FastAPI** 🕐❔ 💪. - -## 🔐 📨 💪 ⚠ 🐕‍🦺 - -/// tip - -❎ 👉 🎏 ⚠, ⚫️ 🎲 📚 ⏩ ⚙️ `body` 🛃 🐕‍🦺 `RequestValidationError` ([🚚 ❌](../tutorial/handling-errors.md#requestvalidationerror){.internal-link target=_blank}). - -✋️ 👉 🖼 ☑ & ⚫️ 🎦 ❔ 🔗 ⏮️ 🔗 🦲. - -/// - -👥 💪 ⚙️ 👉 🎏 🎯 🔐 📨 💪 ⚠ 🐕‍🦺. - -🌐 👥 💪 🍵 📨 🔘 `try`/`except` 🍫: - -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[13,15] *} - -🚥 ⚠ 📉, `Request` 👐 🔜 ↔, 👥 💪 ✍ & ⚒ ⚙️ 📨 💪 🕐❔ 🚚 ❌: - -{* ../../docs_src/custom_request_and_route/tutorial002.py hl[16:18] *} - -## 🛃 `APIRoute` 🎓 📻 - -👆 💪 ⚒ `route_class` 🔢 `APIRouter`: - -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[26] *} - -👉 🖼, *➡ 🛠️* 🔽 `router` 🔜 ⚙️ 🛃 `TimedRoute` 🎓, & 🔜 ✔️ ➕ `X-Response-Time` 🎚 📨 ⏮️ 🕰 ⚫️ ✊ 🏗 📨: - -{* ../../docs_src/custom_request_and_route/tutorial003.py hl[13:20] *} diff --git a/docs/em/docs/how-to/extending-openapi.md b/docs/em/docs/how-to/extending-openapi.md deleted file mode 100644 index c3e6c7f667..0000000000 --- a/docs/em/docs/how-to/extending-openapi.md +++ /dev/null @@ -1,83 +0,0 @@ -# ↔ 🗄 - -/// warning - -👉 👍 🏧 ⚒. 👆 🎲 💪 🚶 ⚫️. - -🚥 👆 📄 🔰 - 👩‍💻 🦮, 👆 💪 🎲 🚶 👉 📄. - -🚥 👆 ⏪ 💭 👈 👆 💪 🔀 🏗 🗄 🔗, 😣 👂. - -/// - -📤 💼 🌐❔ 👆 💪 💪 🔀 🏗 🗄 🔗. - -👉 📄 👆 🔜 👀 ❔. - -## 😐 🛠️ - -😐 (🔢) 🛠️, ⏩. - -`FastAPI` 🈸 (👐) ✔️ `.openapi()` 👩‍🔬 👈 📈 📨 🗄 🔗. - -🍕 🈸 🎚 🏗, *➡ 🛠️* `/openapi.json` (⚖️ ⚫️❔ 👆 ⚒ 👆 `openapi_url`) ®. - -⚫️ 📨 🎻 📨 ⏮️ 🏁 🈸 `.openapi()` 👩‍🔬. - -🔢, ⚫️❔ 👩‍🔬 `.openapi()` 🔨 ✅ 🏠 `.openapi_schema` 👀 🚥 ⚫️ ✔️ 🎚 & 📨 👫. - -🚥 ⚫️ 🚫, ⚫️ 🏗 👫 ⚙️ 🚙 🔢 `fastapi.openapi.utils.get_openapi`. - -& 👈 🔢 `get_openapi()` 📨 🔢: - -* `title`: 🗄 📛, 🎦 🩺. -* `version`: ⏬ 👆 🛠️, ✅ `2.5.0`. -* `openapi_version`: ⏬ 🗄 🔧 ⚙️. 🔢, ⏪: `3.0.2`. -* `description`: 📛 👆 🛠️. -* `routes`: 📇 🛣, 👫 🔠 ® *➡ 🛠️*. 👫 ✊ ⚪️➡️ `app.routes`. - -## 🔑 🔢 - -⚙️ ℹ 🔛, 👆 💪 ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗 & 🔐 🔠 🍕 👈 👆 💪. - -🖼, ➡️ 🚮 📄 🗄 ↔ 🔌 🛃 🔱. - -### 😐 **FastAPI** - -🥇, ✍ 🌐 👆 **FastAPI** 🈸 🛎: - -{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} - -### 🏗 🗄 🔗 - -⤴️, ⚙️ 🎏 🚙 🔢 🏗 🗄 🔗, 🔘 `custom_openapi()` 🔢: - -{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:20] *} - -### 🔀 🗄 🔗 - -🔜 👆 💪 🚮 📄 ↔, ❎ 🛃 `x-logo` `info` "🎚" 🗄 🔗: - -{* ../../docs_src/extending_openapi/tutorial001.py hl[21:23] *} - -### 💾 🗄 🔗 - -👆 💪 ⚙️ 🏠 `.openapi_schema` "💾", 🏪 👆 🏗 🔗. - -👈 🌌, 👆 🈸 🏆 🚫 ✔️ 🏗 🔗 🔠 🕰 👩‍💻 📂 👆 🛠️ 🩺. - -⚫️ 🔜 🏗 🕴 🕐, & ⤴️ 🎏 💾 🔗 🔜 ⚙️ ⏭ 📨. - -{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,24:25] *} - -### 🔐 👩‍🔬 - -🔜 👆 💪 ❎ `.openapi()` 👩‍🔬 ⏮️ 👆 🆕 🔢. - -{* ../../docs_src/extending_openapi/tutorial001.py hl[28] *} - -### ✅ ⚫️ - -🕐 👆 🚶 http://127.0.0.1:8000/redoc 👆 🔜 👀 👈 👆 ⚙️ 👆 🛃 🔱 (👉 🖼, **FastAPI**'Ⓜ 🔱): - - diff --git a/docs/em/docs/how-to/graphql.md b/docs/em/docs/how-to/graphql.md deleted file mode 100644 index 083e9ebd28..0000000000 --- a/docs/em/docs/how-to/graphql.md +++ /dev/null @@ -1,60 +0,0 @@ -# 🕹 - -**FastAPI** ⚓️ 🔛 **🔫** 🐩, ⚫️ 📶 ⏩ 🛠️ 🙆 **🕹** 🗃 🔗 ⏮️ 🔫. - -👆 💪 🌀 😐 FastAPI *➡ 🛠️* ⏮️ 🕹 🔛 🎏 🈸. - -/// tip - -**🕹** ❎ 📶 🎯 ⚙️ 💼. - -⚫️ ✔️ **📈** & **⚠** 🕐❔ 🔬 ⚠ **🕸 🔗**. - -⚒ 💭 👆 🔬 🚥 **💰** 👆 ⚙️ 💼 ⚖ **👐**. 👶 - -/// - -## 🕹 🗃 - -📥 **🕹** 🗃 👈 ✔️ **🔫** 🐕‍🦺. 👆 💪 ⚙️ 👫 ⏮️ **FastAPI**: - -* 🍓 👶 - * ⏮️ 🩺 FastAPI -* 👸 - * ⏮️ 🩺 FastAPI -* 🍟 - * ⏮️ 🍟 🔫 🚚 🔫 🛠️ -* - * ⏮️ 💃-Graphene3️⃣ - -## 🕹 ⏮️ 🍓 - -🚥 👆 💪 ⚖️ 💚 👷 ⏮️ **🕹**, **🍓** **👍** 🗃 ⚫️ ✔️ 🔧 🔐 **FastAPI** 🔧, ⚫️ 🌐 ⚓️ 🔛 **🆎 ✍**. - -⚓️ 🔛 👆 ⚙️ 💼, 👆 5️⃣📆 💖 ⚙️ 🎏 🗃, ✋️ 🚥 👆 💭 👤, 👤 🔜 🎲 🤔 👆 🔄 **🍓**. - -📥 🤪 🎮 ❔ 👆 💪 🛠️ 🍓 ⏮️ FastAPI: - -{* ../../docs_src/graphql/tutorial001.py hl[3,22,25:26] *} - -👆 💪 💡 🌅 🔃 🍓 🍓 🧾. - -& 🩺 🔃 🍓 ⏮️ FastAPI. - -## 🗝 `GraphQLApp` ⚪️➡️ 💃 - -⏮️ ⏬ 💃 🔌 `GraphQLApp` 🎓 🛠️ ⏮️ . - -⚫️ 😢 ⚪️➡️ 💃, ✋️ 🚥 👆 ✔️ 📟 👈 ⚙️ ⚫️, 👆 💪 💪 **↔** 💃-Graphene3️⃣, 👈 📔 🎏 ⚙️ 💼 & ✔️ **🌖 🌓 🔢**. - -/// tip - -🚥 👆 💪 🕹, 👤 🔜 👍 👆 ✅ 👅 🍓, ⚫️ ⚓️ 🔛 🆎 ✍ ↩️ 🛃 🎓 & 🆎. - -/// - -## 💡 🌅 - -👆 💪 💡 🌅 🔃 **🕹** 🛂 🕹 🧾. - -👆 💪 ✍ 🌅 🔃 🔠 👈 🗃 🔬 🔛 👫 🔗. diff --git a/docs/em/docs/index.md b/docs/em/docs/index.md deleted file mode 100644 index 5f5fc2e393..0000000000 --- a/docs/em/docs/index.md +++ /dev/null @@ -1,474 +0,0 @@ -# FastAPI - - - -

- FastAPI -

-

- FastAPI 🛠️, ↕ 🎭, ⏩ 💡, ⏩ 📟, 🔜 🏭 -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**🧾**: https://fastapi.tiangolo.com - -**ℹ 📟**: https://github.com/fastapi/fastapi - ---- - -FastAPI 🏛, ⏩ (↕-🎭), 🕸 🛠️ 🏗 🛠️ ⏮️ 🐍 3️⃣.8️⃣ ➕ ⚓️ 🔛 🐩 🐍 🆎 🔑. - -🔑 ⚒: - -* **⏩**: 📶 ↕ 🎭, 🔛 🇷🇪 ⏮️ **✳** & **🚶** (👏 💃 & Pydantic). [1️⃣ ⏩ 🐍 🛠️ 💪](#_15). -* **⏩ 📟**: 📈 🚅 🛠️ ⚒ 🔃 2️⃣0️⃣0️⃣ 💯 3️⃣0️⃣0️⃣ 💯. * -* **👩‍❤‍👨 🐛**: 📉 🔃 4️⃣0️⃣ 💯 🗿 (👩‍💻) 📉 ❌. * -* **🏋️**: 👑 👨‍🎨 🐕‍🦺. 🛠️ 🌐. 🌘 🕰 🛠️. -* **⏩**: 🔧 ⏩ ⚙️ & 💡. 🌘 🕰 👂 🩺. -* **📏**: 📉 📟 ❎. 💗 ⚒ ⚪️➡️ 🔠 🔢 📄. 👩‍❤‍👨 🐛. -* **🏋️**: 🤚 🏭-🔜 📟. ⏮️ 🏧 🎓 🧾. -* **🐩-⚓️**: ⚓️ 🔛 (& 🍕 🔗 ⏮️) 📂 🐩 🔗: 🗄 (⏪ 💭 🦁) & 🎻 🔗. - -* ⚖ ⚓️ 🔛 💯 🔛 🔗 🛠️ 🏉, 🏗 🏭 🈸. - -## 💰 - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -🎏 💰 - -## 🤔 - -"_[...] 👤 ⚙️ **FastAPI** 📚 👫 📆. [...] 👤 🤙 📆 ⚙️ ⚫️ 🌐 👇 🏉 **⚗ 🐕‍🦺 🤸‍♂**. 👫 💆‍♂ 🛠️ 🔘 🐚 **🖥** 🏬 & **📠** 🏬._" - -
🧿 🇵🇰 - 🤸‍♂ (🇦🇪)
- ---- - -"_👥 🛠️ **FastAPI** 🗃 🤖 **🎂** 💽 👈 💪 🔢 🚚 **🔮**. [👨📛]_" - -
🇮🇹 🇸🇻, 👨📛 👨📛, & 🇱🇰 🕉 🕉 - 🙃 (🇦🇪)
- ---- - -"_**📺** 🙏 📣 📂-ℹ 🚀 👆 **⚔ 🧾** 🎶 🛠️: **📨**❗ [🏗 ⏮️ **FastAPI**]_" - -
✡ 🍏, 👖 🇪🇸, 🌲 🍏 - 📺 (🇦🇪)
- ---- - -"_👤 🤭 🌕 😄 🔃 **FastAPI**. ⚫️ 🎊 ❗_" - -
✡ 🇭🇰 - 🐍 🔢 📻 🦠 (🇦🇪)
- ---- - -"_🤙, ⚫️❔ 👆 ✔️ 🏗 👀 💎 💠 & 🇵🇱. 📚 🌌, ⚫️ ⚫️❔ 👤 💚 **🤗** - ⚫️ 🤙 😍 👀 👱 🏗 👈._" - -
✡ 🗄 - 🤗 👼 (🇦🇪)
- ---- - -"_🚥 👆 👀 💡 1️⃣ **🏛 🛠️** 🏗 🎂 🔗, ✅ 👅 **FastAPI** [...] ⚫️ ⏩, ⏩ ⚙️ & ⏩ 💡 [...]_" - -"_👥 ✔️ 🎛 🤭 **FastAPI** 👆 **🔗** [...] 👤 💭 👆 🔜 💖 ⚫️ [...]_" - -
🇱🇨 🇸🇲 - ✡ Honnibal - 💥 👲 🕴 - 🌈 👼 (🇦🇪) - (🇦🇪)
- ---- - -"_🚥 🙆 👀 🏗 🏭 🐍 🛠️, 👤 🔜 🏆 👍 **FastAPI**. ⚫️ **💎 🏗**, **🙅 ⚙️** & **🏆 🛠️**, ⚫️ ✔️ ▶️️ **🔑 🦲** 👆 🛠️ 🥇 🛠️ 🎛 & 🚘 📚 🏧 & 🐕‍🦺 ✅ 👆 🕹 🔫 👨‍💻._" - -
🇹🇦 🍰 - 📻 (🇦🇪)
- ---- - -## **🏎**, FastAPI 🇳🇨 - - - -🚥 👆 🏗 📱 ⚙️ 📶 ↩️ 🕸 🛠️, ✅ 👅 **🏎**. - -**🏎** FastAPI 🐥 👪. & ⚫️ 🎯 **FastAPI 🇳🇨**. 👶 👶 👶 - -## 📄 - -🐍 3️⃣.7️⃣ ➕ - -FastAPI 🧍 🔛 ⌚ 🐘: - -* 💃 🕸 🍕. -* Pydantic 📊 🍕. - -## 👷‍♂ - -
- -```console -$ pip install "fastapi[standard]" - ----> 100% -``` - -
- -👆 🔜 💪 🔫 💽, 🏭 ✅ Uvicorn ⚖️ Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## 🖼 - -### ✍ ⚫️ - -* ✍ 📁 `main.py` ⏮️: - -```Python -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} -``` - -
-⚖️ ⚙️ async def... - -🚥 👆 📟 ⚙️ `async` / `await`, ⚙️ `async def`: - -```Python hl_lines="9 14" -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} -``` - -**🗒**: - -🚥 👆 🚫 💭, ✅ _"🏃 ❓" _ 📄 🔃 `async` & `await` 🩺. - -
- -### 🏃 ⚫️ - -🏃 💽 ⏮️: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-🔃 📋 uvicorn main:app --reload... - -📋 `uvicorn main:app` 🔗: - -* `main`: 📁 `main.py` (🐍 "🕹"). -* `app`: 🎚 ✍ 🔘 `main.py` ⏮️ ⏸ `app = FastAPI()`. -* `--reload`: ⚒ 💽 ⏏ ⏮️ 📟 🔀. 🕴 👉 🛠️. - -
- -### ✅ ⚫️ - -📂 👆 🖥 http://127.0.0.1:8000/items/5?q=somequery. - -👆 🔜 👀 🎻 📨: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -👆 ⏪ ✍ 🛠️ 👈: - -* 📨 🇺🇸🔍 📨 _➡_ `/` & `/items/{item_id}`. -* 👯‍♂️ _➡_ ✊ `GET` 🛠️ (💭 🇺🇸🔍 _👩‍🔬_). -* _➡_ `/items/{item_id}` ✔️ _➡ 🔢_ `item_id` 👈 🔜 `int`. -* _➡_ `/items/{item_id}` ✔️ 📦 `str` _🔢 = `q`. - -### 🎓 🛠️ 🩺 - -🔜 🚶 http://127.0.0.1:8000/docs. - -👆 🔜 👀 🏧 🎓 🛠️ 🧾 (🚚 🦁 🎚): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### 🎛 🛠️ 🩺 - -& 🔜, 🚶 http://127.0.0.1:8000/redoc. - -👆 🔜 👀 🎛 🏧 🧾 (🚚 📄): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## 🖼 ♻ - -🔜 🔀 📁 `main.py` 📨 💪 ⚪️➡️ `PUT` 📨. - -📣 💪 ⚙️ 🐩 🐍 🆎, 👏 Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Union[bool, None] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -💽 🔜 🔃 🔁 (↩️ 👆 🚮 `--reload` `uvicorn` 📋 🔛). - -### 🎓 🛠️ 🩺 ♻ - -🔜 🚶 http://127.0.0.1:8000/docs. - -* 🎓 🛠️ 🧾 🔜 🔁 ℹ, 🔌 🆕 💪: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* 🖊 🔛 🔼 "🔄 ⚫️ 👅", ⚫️ ✔ 👆 🥧 🔢 & 🔗 🔗 ⏮️ 🛠️: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* ⤴️ 🖊 🔛 "🛠️" 🔼, 👩‍💻 🔢 🔜 🔗 ⏮️ 👆 🛠️, 📨 🔢, 🤚 🏁 & 🎦 👫 🔛 🖥: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### 🎛 🛠️ 🩺 ♻ - -& 🔜, 🚶 http://127.0.0.1:8000/redoc. - -* 🎛 🧾 🔜 🎨 🆕 🔢 🔢 & 💪: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### 🌃 - -📄, 👆 📣 **🕐** 🆎 🔢, 💪, ♒️. 🔢 🔢. - -👆 👈 ⏮️ 🐩 🏛 🐍 🆎. - -👆 🚫 ✔️ 💡 🆕 ❕, 👩‍🔬 ⚖️ 🎓 🎯 🗃, ♒️. - -🐩 **🐍 3️⃣.7️⃣ ➕**. - -🖼, `int`: - -```Python -item_id: int -``` - -⚖️ 🌖 🏗 `Item` 🏷: - -```Python -item: Item -``` - -...& ⏮️ 👈 👁 📄 👆 🤚: - -* 👨‍🎨 🐕‍🦺, 🔌: - * 🛠️. - * 🆎 ✅. -* 🔬 💽: - * 🏧 & 🆑 ❌ 🕐❔ 📊 ❌. - * 🔬 🙇 🐦 🎻 🎚. -* 🛠️ 🔢 💽: 👟 ⚪️➡️ 🕸 🐍 💽 & 🆎. 👂 ⚪️➡️: - * 🎻. - * ➡ 🔢. - * 🔢 🔢. - * 🍪. - * 🎚. - * 📨. - * 📁. -* 🛠️ 🔢 📊: 🗜 ⚪️➡️ 🐍 💽 & 🆎 🕸 💽 (🎻): - * 🗜 🐍 🆎 (`str`, `int`, `float`, `bool`, `list`, ♒️). - * `datetime` 🎚. - * `UUID` 🎚. - * 💽 🏷. - * ...& 📚 🌖. -* 🏧 🎓 🛠️ 🧾, 🔌 2️⃣ 🎛 👩‍💻 🔢: - * 🦁 🎚. - * 📄. - ---- - -👟 🔙 ⏮️ 📟 🖼, **FastAPI** 🔜: - -* ✔ 👈 📤 `item_id` ➡ `GET` & `PUT` 📨. -* ✔ 👈 `item_id` 🆎 `int` `GET` & `PUT` 📨. - * 🚥 ⚫️ 🚫, 👩‍💻 🔜 👀 ⚠, 🆑 ❌. -* ✅ 🚥 📤 📦 🔢 🔢 📛 `q` ( `http://127.0.0.1:8000/items/foo?q=somequery`) `GET` 📨. - * `q` 🔢 📣 ⏮️ `= None`, ⚫️ 📦. - * 🍵 `None` ⚫️ 🔜 🚚 (💪 💼 ⏮️ `PUT`). -* `PUT` 📨 `/items/{item_id}`, ✍ 💪 🎻: - * ✅ 👈 ⚫️ ✔️ ✔ 🔢 `name` 👈 🔜 `str`. - * ✅ 👈 ⚫️ ✔️ ✔ 🔢 `price` 👈 ✔️ `float`. - * ✅ 👈 ⚫️ ✔️ 📦 🔢 `is_offer`, 👈 🔜 `bool`, 🚥 🎁. - * 🌐 👉 🔜 👷 🙇 🐦 🎻 🎚. -* 🗜 ⚪️➡️ & 🎻 🔁. -* 📄 🌐 ⏮️ 🗄, 👈 💪 ⚙️: - * 🎓 🧾 ⚙️. - * 🏧 👩‍💻 📟 ⚡ ⚙️, 📚 🇪🇸. -* 🚚 2️⃣ 🎓 🧾 🕸 🔢 🔗. - ---- - -👥 🖌 🧽, ✋️ 👆 ⏪ 🤚 💭 ❔ ⚫️ 🌐 👷. - -🔄 🔀 ⏸ ⏮️: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...⚪️➡️: - -```Python - ... "item_name": item.name ... -``` - -...: - -```Python - ... "item_price": item.price ... -``` - -...& 👀 ❔ 👆 👨‍🎨 🔜 🚘-🏁 🔢 & 💭 👫 🆎: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -🌅 🏁 🖼 🔌 🌅 ⚒, 👀 🔰 - 👩‍💻 🦮. - -**🚘 🚨**: 🔰 - 👩‍💻 🦮 🔌: - -* 📄 **🔢** ⚪️➡️ 🎏 🎏 🥉: **🎚**, **🍪**, **📨 🏑** & **📁**. -* ❔ ⚒ **🔬 ⚛** `maximum_length` ⚖️ `regex`. -* 📶 🏋️ & ⏩ ⚙️ **🔗 💉** ⚙️. -* 💂‍♂ & 🤝, ✅ 🐕‍🦺 **Oauth2️⃣** ⏮️ **🥙 🤝** & **🇺🇸🔍 🔰** 🔐. -* 🌅 🏧 (✋️ 😨 ⏩) ⚒ 📣 **🙇 🐦 🎻 🏷** (👏 Pydantic). -* **🕹** 🛠️ ⏮️ 🍓 & 🎏 🗃. -* 📚 ➕ ⚒ (👏 💃): - * ** *️⃣ ** - * 📶 ⏩ 💯 ⚓️ 🔛 🇸🇲 & `pytest` - * **⚜** - * **🍪 🎉** - * ...& 🌖. - -## 🎭 - -🔬 🇸🇲 📇 🎦 **FastAPI** 🈸 🏃‍♂ 🔽 Uvicorn 1️⃣ ⏩ 🐍 🛠️ 💪, 🕴 🔛 💃 & Uvicorn 👫 (⚙️ 🔘 FastAPI). (*) - -🤔 🌖 🔃 ⚫️, 👀 📄 📇. - -## 📦 🔗 - -⚙️ Pydantic: - -* email-validator - 📧 🔬. - -⚙️ 💃: - -* httpx - ✔ 🚥 👆 💚 ⚙️ `TestClient`. -* jinja2 - ✔ 🚥 👆 💚 ⚙️ 🔢 📄 📳. -* python-multipart - ✔ 🚥 👆 💚 🐕‍🦺 📨 "✍", ⏮️ `request.form()`. -* itsdangerous - ✔ `SessionMiddleware` 🐕‍🦺. -* pyyaml - ✔ 💃 `SchemaGenerator` 🐕‍🦺 (👆 🎲 🚫 💪 ⚫️ ⏮️ FastAPI). - -⚙️ FastAPI / 💃: - -* uvicorn - 💽 👈 📐 & 🍦 👆 🈸. -* orjson - ✔ 🚥 👆 💚 ⚙️ `ORJSONResponse`. -* ujson - ✔ 🚥 👆 💚 ⚙️ `UJSONResponse`. - -👆 💪 ❎ 🌐 👫 ⏮️ `pip install "fastapi[all]"`. - -## 🛂 - -👉 🏗 ® 🔽 ⚖ 🇩🇪 🛂. diff --git a/docs/em/docs/project-generation.md b/docs/em/docs/project-generation.md deleted file mode 100644 index ef6a218215..0000000000 --- a/docs/em/docs/project-generation.md +++ /dev/null @@ -1,84 +0,0 @@ -# 🏗 ⚡ - 📄 - -👆 💪 ⚙️ 🏗 🚂 🤚 ▶️, ⚫️ 🔌 📚 ▶️ ⚒ 🆙, 💂‍♂, 💽 & 🛠️ 🔗 ⏪ ⌛ 👆. - -🏗 🚂 🔜 🕧 ✔️ 📶 🙃 🖥 👈 👆 🔜 ℹ & 🛠️ 👆 👍 💪, ✋️ ⚫️ 💪 👍 ▶️ ☝ 👆 🏗. - -## 🌕 📚 FastAPI ✳ - -📂: https://github.com/tiangolo/full-stack-fastapi-postgresql - -### 🌕 📚 FastAPI ✳ - ⚒ - -* 🌕 **☁** 🛠️ (☁ 🧢). -* ☁ 🐝 📳 🛠️. -* **☁ ✍** 🛠️ & 🛠️ 🇧🇿 🛠️. -* **🏭 🔜** 🐍 🕸 💽 ⚙️ Uvicorn & 🐁. -* 🐍 **FastAPI** 👩‍💻: - * **⏩**: 📶 ↕ 🎭, 🔛 🇷🇪 ⏮️ **✳** & **🚶** (👏 💃 & Pydantic). - * **🏋️**: 👑 👨‍🎨 🐕‍🦺. 🛠️ 🌐. 🌘 🕰 🛠️. - * **⏩**: 🔧 ⏩ ⚙️ & 💡. 🌘 🕰 👂 🩺. - * **📏**: 📉 📟 ❎. 💗 ⚒ ⚪️➡️ 🔠 🔢 📄. - * **🏋️**: 🤚 🏭-🔜 📟. ⏮️ 🏧 🎓 🧾. - * **🐩-⚓️**: ⚓️ 🔛 (& 🍕 🔗 ⏮️) 📂 🐩 🔗: 🗄 & 🎻 🔗. - * **📚 🎏 ⚒** 🔌 🏧 🔬, 🛠️, 🎓 🧾, 🤝 ⏮️ Oauth2️⃣ 🥙 🤝, ♒️. -* **🔐 🔐** 🔁 🔢. -* **🥙 🤝** 🤝. -* **🇸🇲** 🏷 (🔬 🏺 ↔, 👫 💪 ⚙️ ⏮️ 🥒 👨‍🏭 🔗). -* 🔰 ▶️ 🏷 👩‍💻 (🔀 & ❎ 👆 💪). -* **⚗** 🛠️. -* **⚜** (✖️ 🇨🇳 ℹ 🤝). -* **🥒** 👨‍🏭 👈 💪 🗄 & ⚙️ 🏷 & 📟 ⚪️➡️ 🎂 👩‍💻 🍕. -* 🎂 👩‍💻 💯 ⚓️ 🔛 **✳**, 🛠️ ⏮️ ☁, 👆 💪 💯 🌕 🛠️ 🔗, 🔬 🔛 💽. ⚫️ 🏃 ☁, ⚫️ 💪 🏗 🆕 💽 🏪 ⚪️➡️ 🖌 🔠 🕰 (👆 💪 ⚙️ ✳, ✳, ✳, ⚖️ ⚫️❔ 👆 💚, & 💯 👈 🛠️ 👷). -* ⏩ 🐍 🛠️ ⏮️ **📂 💾** 🛰 ⚖️-☁ 🛠️ ⏮️ ↔ 💖 ⚛ ⚗ ⚖️ 🎙 🎙 📟 📂. -* **🎦** 🕸: - * 🏗 ⏮️ 🎦 ✳. - * **🥙 🤝** 🚚. - * 💳 🎑. - * ⏮️ 💳, 👑 🕹 🎑. - * 👑 🕹 ⏮️ 👩‍💻 🏗 & 📕. - * 👤 👩‍💻 📕. - * **🇷🇪**. - * **🎦-📻**. - * **Vuetify** 🌹 🧽 🔧 🦲. - * **📕**. - * ☁ 💽 ⚓️ 🔛 **👌** (📶 🤾 🎆 ⏮️ 🎦-📻). - * ☁ 👁-▶️ 🏗, 👆 🚫 💪 🖊 ⚖️ 💕 ✍ 📟. - * 🕸 💯 🏃 🏗 🕰 (💪 🔕 💁‍♂️). - * ⚒ 🔧 💪, ⚫️ 👷 👅 📦, ✋️ 👆 💪 🏤-🏗 ⏮️ 🎦 ✳ ⚖️ ✍ ⚫️ 👆 💪, & 🏤-⚙️ ⚫️❔ 👆 💚. -* ** *️⃣ ** ✳ 💽, 👆 💪 🔀 ⚫️ ⚙️ 📁 & ✳ 💪. -* **🥀** 🥒 👨‍🏭 ⚖. -* 📐 ⚖ 🖖 🕸 & 👩‍💻 ⏮️ **Traefik**, 👆 💪 ✔️ 👯‍♂️ 🔽 🎏 🆔, 👽 ➡, ✋️ 🍦 🎏 📦. -* Traefik 🛠️, ✅ ➡️ 🗜 **🇺🇸🔍** 📄 🏧 ⚡. -* ✳ **🆑** (🔁 🛠️), 🔌 🕸 & 👩‍💻 🔬. - -## 🌕 📚 FastAPI 🗄 - -📂: https://github.com/tiangolo/full-stack-fastapi-couchbase - -👶 👶 **⚠** 👶 👶 - -🚥 👆 ▶️ 🆕 🏗 ⚪️➡️ 🖌, ✅ 🎛 📥. - -🖼, 🏗 🚂 🌕 📚 FastAPI ✳ 💪 👍 🎛, ⚫️ 🎯 🚧 & ⚙️. & ⚫️ 🔌 🌐 🆕 ⚒ & 📈. - -👆 🆓 ⚙️ 🗄-⚓️ 🚂 🚥 👆 💚, ⚫️ 🔜 🎲 👷 👌, & 🚥 👆 ⏪ ✔️ 🏗 🏗 ⏮️ ⚫️ 👈 👌 👍 (& 👆 🎲 ⏪ ℹ ⚫️ ♣ 👆 💪). - -👆 💪 ✍ 🌅 🔃 ⚫️ 🩺 🏦. - -## 🌕 📚 FastAPI ✳ - -...💪 👟 ⏪, ⚓️ 🔛 👇 🕰 🚚 & 🎏 ⚖. 👶 👶 - -## 🎰 🏫 🏷 ⏮️ 🌈 & FastAPI - -📂: https://github.com/microsoft/cookiecutter-spacy-fastapi - -### 🎰 🏫 🏷 ⏮️ 🌈 & FastAPI - ⚒ - -* **🌈** 🕜 🏷 🛠️. -* **☁ 🧠 🔎** 📨 📁 🏗. -* **🏭 🔜** 🐍 🕸 💽 ⚙️ Uvicorn & 🐁. -* **☁ 👩‍💻** Kubernetes (🦲) 🆑/💿 🛠️ 🏗. -* **🤸‍♂** 💪 ⚒ 1️⃣ 🌈 🏗 🇪🇸 ⏮️ 🏗 🖥. -* **💪 🏧** 🎏 🏷 🛠️ (Pytorch, 🇸🇲), 🚫 🌈. diff --git a/docs/em/docs/python-types.md b/docs/em/docs/python-types.md deleted file mode 100644 index d2af23bb9a..0000000000 --- a/docs/em/docs/python-types.md +++ /dev/null @@ -1,542 +0,0 @@ -# 🐍 🆎 🎶 - -🐍 ✔️ 🐕‍🦺 📦 "🆎 🔑". - -👫 **"🆎 🔑"** 🎁 ❕ 👈 ✔ 📣 🆎 🔢. - -📣 🆎 👆 🔢, 👨‍🎨 & 🧰 💪 🤝 👆 👍 🐕‍🦺. - -👉 **⏩ 🔰 / ↗️** 🔃 🐍 🆎 🔑. ⚫️ 📔 🕴 💯 💪 ⚙️ 👫 ⏮️ **FastAPI**... ❔ 🤙 📶 🐥. - -**FastAPI** 🌐 ⚓️ 🔛 👫 🆎 🔑, 👫 🤝 ⚫️ 📚 📈 & 💰. - -✋️ 🚥 👆 🙅 ⚙️ **FastAPI**, 👆 🔜 💰 ⚪️➡️ 🏫 🍖 🔃 👫. - -/// note - -🚥 👆 🐍 🕴, & 👆 ⏪ 💭 🌐 🔃 🆎 🔑, 🚶 ⏭ 📃. - -/// - -## 🎯 - -➡️ ▶️ ⏮️ 🙅 🖼: - -```Python -{!../../docs_src/python_types/tutorial001.py!} -``` - -🤙 👉 📋 🔢: - -``` -John Doe -``` - -🔢 🔨 📄: - -* ✊ `first_name` & `last_name`. -* 🗜 🥇 🔤 🔠 1️⃣ ↖ 💼 ⏮️ `title()`. -* 🔢 👫 ⏮️ 🚀 🖕. - -```Python hl_lines="2" -{!../../docs_src/python_types/tutorial001.py!} -``` - -### ✍ ⚫️ - -⚫️ 📶 🙅 📋. - -✋️ 🔜 🌈 👈 👆 ✍ ⚫️ ⚪️➡️ 🖌. - -☝ 👆 🔜 ✔️ ▶️ 🔑 🔢, 👆 ✔️ 🔢 🔜... - -✋️ ⤴️ 👆 ✔️ 🤙 "👈 👩‍🔬 👈 🗜 🥇 🔤 ↖ 💼". - -⚫️ `upper`❓ ⚫️ `uppercase`❓ `first_uppercase`❓ `capitalize`❓ - -⤴️, 👆 🔄 ⏮️ 🗝 👩‍💻 👨‍👧‍👦, 👨‍🎨 ✍. - -👆 🆎 🥇 🔢 🔢, `first_name`, ⤴️ ❣ (`.`) & ⤴️ 🎯 `Ctrl+Space` ⏲ 🛠️. - -✋️, 😞, 👆 🤚 🕳 ⚠: - - - -### 🚮 🆎 - -➡️ 🔀 👁 ⏸ ⚪️➡️ ⏮️ ⏬. - -👥 🔜 🔀 ⚫️❔ 👉 🧬, 🔢 🔢, ⚪️➡️: - -```Python - first_name, last_name -``` - -: - -```Python - first_name: str, last_name: str -``` - -👈 ⚫️. - -👈 "🆎 🔑": - -```Python hl_lines="1" -{!../../docs_src/python_types/tutorial002.py!} -``` - -👈 🚫 🎏 📣 🔢 💲 💖 🔜 ⏮️: - -```Python - first_name="john", last_name="doe" -``` - -⚫️ 🎏 👜. - -👥 ⚙️ ❤ (`:`), 🚫 🌓 (`=`). - -& ❎ 🆎 🔑 🛎 🚫 🔀 ⚫️❔ 🔨 ⚪️➡️ ⚫️❔ 🔜 🔨 🍵 👫. - -✋️ 🔜, 🌈 👆 🔄 🖕 🏗 👈 🔢, ✋️ ⏮️ 🆎 🔑. - -🎏 ☝, 👆 🔄 ⏲ 📋 ⏮️ `Ctrl+Space` & 👆 👀: - - - -⏮️ 👈, 👆 💪 📜, 👀 🎛, ⏭ 👆 🔎 1️⃣ 👈 "💍 🔔": - - - -## 🌅 🎯 - -✅ 👉 🔢, ⚫️ ⏪ ✔️ 🆎 🔑: - -```Python hl_lines="1" -{!../../docs_src/python_types/tutorial003.py!} -``` - -↩️ 👨‍🎨 💭 🆎 🔢, 👆 🚫 🕴 🤚 🛠️, 👆 🤚 ❌ ✅: - - - -🔜 👆 💭 👈 👆 ✔️ 🔧 ⚫️, 🗜 `age` 🎻 ⏮️ `str(age)`: - -```Python hl_lines="2" -{!../../docs_src/python_types/tutorial004.py!} -``` - -## 📣 🆎 - -👆 👀 👑 🥉 📣 🆎 🔑. 🔢 🔢. - -👉 👑 🥉 👆 🔜 ⚙️ 👫 ⏮️ **FastAPI**. - -### 🙅 🆎 - -👆 💪 📣 🌐 🐩 🐍 🆎, 🚫 🕴 `str`. - -👆 💪 ⚙️, 🖼: - -* `int` -* `float` -* `bool` -* `bytes` - -```Python hl_lines="1" -{!../../docs_src/python_types/tutorial005.py!} -``` - -### 💊 🆎 ⏮️ 🆎 🔢 - -📤 📊 📊 👈 💪 🔌 🎏 💲, 💖 `dict`, `list`, `set` & `tuple`. & 🔗 💲 💪 ✔️ 👫 👍 🆎 💁‍♂️. - -👉 🆎 👈 ✔️ 🔗 🆎 🤙 "**💊**" 🆎. & ⚫️ 💪 📣 👫, ⏮️ 👫 🔗 🆎. - -📣 👈 🆎 & 🔗 🆎, 👆 💪 ⚙️ 🐩 🐍 🕹 `typing`. ⚫️ 🔀 🎯 🐕‍🦺 👫 🆎 🔑. - -#### 🆕 ⏬ 🐍 - -❕ ⚙️ `typing` **🔗** ⏮️ 🌐 ⏬, ⚪️➡️ 🐍 3️⃣.6️⃣ ⏪ 🕐, ✅ 🐍 3️⃣.9️⃣, 🐍 3️⃣.1️⃣0️⃣, ♒️. - -🐍 🏧, **🆕 ⏬** 👟 ⏮️ 📉 🐕‍🦺 👉 🆎 ✍ & 📚 💼 👆 🏆 🚫 💪 🗄 & ⚙️ `typing` 🕹 📣 🆎 ✍. - -🚥 👆 💪 ⚒ 🌖 ⏮️ ⏬ 🐍 👆 🏗, 👆 🔜 💪 ✊ 📈 👈 ➕ 🦁. 👀 🖼 🔛. - -#### 📇 - -🖼, ➡️ 🔬 🔢 `list` `str`. - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -⚪️➡️ `typing`, 🗄 `List` (⏮️ 🔠 `L`): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕. - -🆎, 🚮 `List` 👈 👆 🗄 ⚪️➡️ `typing`. - -📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -📣 🔢, ⏮️ 🎏 ❤ (`:`) ❕. - -🆎, 🚮 `list`. - -📇 🆎 👈 🔌 🔗 🆎, 👆 🚮 👫 ⬜ 🗜: - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -/// info - -👈 🔗 🆎 ⬜ 🗜 🤙 "🆎 🔢". - -👉 💼, `str` 🆎 🔢 🚶‍♀️ `List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛). - -/// - -👈 ⛓: "🔢 `items` `list`, & 🔠 🏬 👉 📇 `str`". - -/// tip - -🚥 👆 ⚙️ 🐍 3️⃣.9️⃣ ⚖️ 🔛, 👆 🚫 ✔️ 🗄 `List` ⚪️➡️ `typing`, 👆 💪 ⚙️ 🎏 🥔 `list` 🆎 ↩️. - -/// - -🔨 👈, 👆 👨‍🎨 💪 🚚 🐕‍🦺 ⏪ 🏭 🏬 ⚪️➡️ 📇: - - - -🍵 🆎, 👈 🌖 💪 🏆. - -👀 👈 🔢 `item` 1️⃣ 🔣 📇 `items`. - -& , 👨‍🎨 💭 ⚫️ `str`, & 🚚 🐕‍🦺 👈. - -#### 🔢 & ⚒ - -👆 🔜 🎏 📣 `tuple`Ⓜ & `set`Ⓜ: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -👉 ⛓: - -* 🔢 `items_t` `tuple` ⏮️ 3️⃣ 🏬, `int`, ➕1️⃣ `int`, & `str`. -* 🔢 `items_s` `set`, & 🔠 🚮 🏬 🆎 `bytes`. - -#### #️⃣ - -🔬 `dict`, 👆 🚶‍♀️ 2️⃣ 🆎 🔢, 🎏 ❕. - -🥇 🆎 🔢 🔑 `dict`. - -🥈 🆎 🔢 💲 `dict`: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -👉 ⛓: - -* 🔢 `prices` `dict`: - * 🔑 👉 `dict` 🆎 `str` (➡️ 💬, 📛 🔠 🏬). - * 💲 👉 `dict` 🆎 `float` (➡️ 💬, 🔖 🔠 🏬). - -#### 🇪🇺 - -👆 💪 📣 👈 🔢 💪 🙆 **📚 🆎**, 🖼, `int` ⚖️ `str`. - -🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 ⚙️ `Union` 🆎 ⚪️➡️ `typing` & 🚮 🔘 ⬜ 🗜 💪 🆎 🚫. - -🐍 3️⃣.1️⃣0️⃣ 📤 **🎛 ❕** 🌐❔ 👆 💪 🚮 💪 🆎 👽 ⏸ ⏸ (`|`). - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008b_py310.py!} -``` - -//// - -👯‍♂️ 💼 👉 ⛓ 👈 `item` 💪 `int` ⚖️ `str`. - -#### 🎲 `None` - -👆 💪 📣 👈 💲 💪 ✔️ 🆎, 💖 `str`, ✋️ 👈 ⚫️ 💪 `None`. - -🐍 3️⃣.6️⃣ & 🔛 (✅ 🐍 3️⃣.1️⃣0️⃣) 👆 💪 📣 ⚫️ 🏭 & ⚙️ `Optional` ⚪️➡️ `typing` 🕹. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} -``` - -⚙️ `Optional[str]` ↩️ `str` 🔜 ➡️ 👨‍🎨 ℹ 👆 🔍 ❌ 🌐❔ 👆 💪 🤔 👈 💲 🕧 `str`, 🕐❔ ⚫️ 💪 🤙 `None` 💁‍♂️. - -`Optional[Something]` 🤙 ⌨ `Union[Something, None]`, 👫 🌓. - -👉 ⛓ 👈 🐍 3️⃣.1️⃣0️⃣, 👆 💪 ⚙️ `Something | None`: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} -``` - -//// - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - 🎛 - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial009_py310.py!} -``` - -//// - -#### ⚙️ `Union` ⚖️ `Optional` - -🚥 👆 ⚙️ 🐍 ⏬ 🔛 3️⃣.1️⃣0️⃣, 📥 💁‍♂ ⚪️➡️ 👇 📶 **🤔** ☝ 🎑: - -* 👶 ❎ ⚙️ `Optional[SomeType]` -* ↩️ 👶 **⚙️ `Union[SomeType, None]`** 👶. - -👯‍♂️ 🌓 & 🔘 👫 🎏, ✋️ 👤 🔜 👍 `Union` ↩️ `Optional` ↩️ 🔤 "**📦**" 🔜 😑 🔑 👈 💲 📦, & ⚫️ 🤙 ⛓ "⚫️ 💪 `None`", 🚥 ⚫️ 🚫 📦 & ✔. - -👤 💭 `Union[SomeType, None]` 🌖 🔑 🔃 ⚫️❔ ⚫️ ⛓. - -⚫️ 🔃 🔤 & 📛. ✋️ 👈 🔤 💪 📉 ❔ 👆 & 👆 🤽‍♂ 💭 🔃 📟. - -🖼, ➡️ ✊ 👉 🔢: - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009c.py!} -``` - -🔢 `name` 🔬 `Optional[str]`, ✋️ ⚫️ **🚫 📦**, 👆 🚫🔜 🤙 🔢 🍵 🔢: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -`name` 🔢 **✔** (🚫 *📦*) ↩️ ⚫️ 🚫 ✔️ 🔢 💲. , `name` 🚫 `None` 💲: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -👍 📰, 🕐 👆 🔛 🐍 3️⃣.1️⃣0️⃣ 👆 🏆 🚫 ✔️ 😟 🔃 👈, 👆 🔜 💪 🎯 ⚙️ `|` 🔬 🇪🇺 🆎: - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009c_py310.py!} -``` - -& ⤴️ 👆 🏆 🚫 ✔️ 😟 🔃 📛 💖 `Optional` & `Union`. 👶 - -#### 💊 🆎 - -👉 🆎 👈 ✊ 🆎 🔢 ⬜ 🗜 🤙 **💊 🆎** ⚖️ **💊**, 🖼: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...& 🎏. - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -👆 💪 ⚙️ 🎏 💽 🆎 💊 (⏮️ ⬜ 🗜 & 🆎 🔘): - -* `list` -* `tuple` -* `set` -* `dict` - - & 🎏 ⏮️ 🐍 3️⃣.6️⃣, ⚪️➡️ `typing` 🕹: - -* `Union` -* `Optional` -* ...& 🎏. - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -👆 💪 ⚙️ 🎏 💽 🆎 💊 (⏮️ ⬜ 🗜 & 🆎 🔘): - -* `list` -* `tuple` -* `set` -* `dict` - - & 🎏 ⏮️ 🐍 3️⃣.6️⃣, ⚪️➡️ `typing` 🕹: - -* `Union` -* `Optional` (🎏 ⏮️ 🐍 3️⃣.6️⃣) -* ...& 🎏. - -🐍 3️⃣.1️⃣0️⃣, 🎛 ⚙️ 💊 `Union` & `Optional`, 👆 💪 ⚙️ ⏸ ⏸ (`|`) 📣 🇪🇺 🆎. - -//// - -### 🎓 🆎 - -👆 💪 📣 🎓 🆎 🔢. - -➡️ 💬 👆 ✔️ 🎓 `Person`, ⏮️ 📛: - -```Python hl_lines="1-3" -{!../../docs_src/python_types/tutorial010.py!} -``` - -⤴️ 👆 💪 📣 🔢 🆎 `Person`: - -```Python hl_lines="6" -{!../../docs_src/python_types/tutorial010.py!} -``` - -& ⤴️, 🔄, 👆 🤚 🌐 👨‍🎨 🐕‍🦺: - - - -## Pydantic 🏷 - -Pydantic 🐍 🗃 🎭 📊 🔬. - -👆 📣 "💠" 💽 🎓 ⏮️ 🔢. - -& 🔠 🔢 ✔️ 🆎. - -⤴️ 👆 ✍ 👐 👈 🎓 ⏮️ 💲 & ⚫️ 🔜 ✔ 💲, 🗜 👫 ☑ 🆎 (🚥 👈 💼) & 🤝 👆 🎚 ⏮️ 🌐 💽. - -& 👆 🤚 🌐 👨‍🎨 🐕‍🦺 ⏮️ 👈 📉 🎚. - -🖼 ⚪️➡️ 🛂 Pydantic 🩺: - -//// tab | 🐍 3️⃣.6️⃣ & 🔛 - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// - -//// tab | 🐍 3️⃣.9️⃣ & 🔛 - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | 🐍 3️⃣.1️⃣0️⃣ & 🔛 - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -/// info - -💡 🌖 🔃 Pydantic, ✅ 🚮 🩺. - -/// - -**FastAPI** 🌐 ⚓️ 🔛 Pydantic. - -👆 🔜 👀 📚 🌅 🌐 👉 💡 [🔰 - 👩‍💻 🦮](tutorial/index.md){.internal-link target=_blank}. - -/// tip - -Pydantic ✔️ 🎁 🎭 🕐❔ 👆 ⚙️ `Optional` ⚖️ `Union[Something, None]` 🍵 🔢 💲, 👆 💪 ✍ 🌅 🔃 ⚫️ Pydantic 🩺 🔃 ✔ 📦 🏑. - -/// - -## 🆎 🔑 **FastAPI** - -**FastAPI** ✊ 📈 👫 🆎 🔑 📚 👜. - -⏮️ **FastAPI** 👆 📣 🔢 ⏮️ 🆎 🔑 & 👆 🤚: - -* **👨‍🎨 🐕‍🦺**. -* **🆎 ✅**. - -...and **FastAPI** uses the same declarations : - -* **🔬 📄**: ⚪️➡️ 📨 ➡ 🔢, 🔢 🔢, 🎚, 💪, 🔗, ♒️. -* **🗜 💽**: ⚪️➡️ 📨 🚚 🆎. -* **✔ 💽**: 👟 ⚪️➡️ 🔠 📨: - * 🏭 **🏧 ❌** 📨 👩‍💻 🕐❔ 📊 ❌. -* **📄** 🛠️ ⚙️ 🗄: - * ❔ ⤴️ ⚙️ 🏧 🎓 🧾 👩‍💻 🔢. - -👉 5️⃣📆 🌐 🔊 📝. 🚫 😟. 👆 🔜 👀 🌐 👉 🎯 [🔰 - 👩‍💻 🦮](tutorial/index.md){.internal-link target=_blank}. - -⚠ 👜 👈 ⚙️ 🐩 🐍 🆎, 👁 🥉 (↩️ ❎ 🌖 🎓, 👨‍🎨, ♒️), **FastAPI** 🔜 📚 👷 👆. - -/// info - -🚥 👆 ⏪ 🚶 🔘 🌐 🔰 & 👟 🔙 👀 🌅 🔃 🆎, 👍 ℹ "🎮 🎼" ⚪️➡️ `mypy`. - -/// diff --git a/docs/em/docs/tutorial/background-tasks.md b/docs/em/docs/tutorial/background-tasks.md deleted file mode 100644 index 4cbcbc710d..0000000000 --- a/docs/em/docs/tutorial/background-tasks.md +++ /dev/null @@ -1,84 +0,0 @@ -# 🖥 📋 - -👆 💪 🔬 🖥 📋 🏃 *⏮️* 🛬 📨. - -👉 ⚠ 🛠️ 👈 💪 🔨 ⏮️ 📨, ✋️ 👈 👩‍💻 🚫 🤙 ✔️ ⌛ 🛠️ 🏁 ⏭ 📨 📨. - -👉 🔌, 🖼: - -* 📧 📨 📨 ⏮️ 🎭 🎯: - * 🔗 📧 💽 & 📨 📧 😑 "🐌" (📚 🥈), 👆 💪 📨 📨 ▶️️ ↖️ & 📨 📧 📨 🖥. -* 🏭 💽: - * 🖼, ➡️ 💬 👆 📨 📁 👈 🔜 🚶 🔘 🐌 🛠️, 👆 💪 📨 📨 "🚫" (🇺🇸🔍 2️⃣0️⃣2️⃣) & 🛠️ ⚫️ 🖥. - -## ⚙️ `BackgroundTasks` - -🥇, 🗄 `BackgroundTasks` & 🔬 🔢 👆 *➡ 🛠️ 🔢* ⏮️ 🆎 📄 `BackgroundTasks`: - -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} - -**FastAPI** 🔜 ✍ 🎚 🆎 `BackgroundTasks` 👆 & 🚶‍♀️ ⚫️ 👈 🔢. - -## ✍ 📋 🔢 - -✍ 🔢 🏃 🖥 📋. - -⚫️ 🐩 🔢 👈 💪 📨 🔢. - -⚫️ 💪 `async def` ⚖️ 😐 `def` 🔢, **FastAPI** 🔜 💭 ❔ 🍵 ⚫️ ☑. - -👉 💼, 📋 🔢 🔜 ✍ 📁 (⚖ 📨 📧). - -& ✍ 🛠️ 🚫 ⚙️ `async` & `await`, 👥 🔬 🔢 ⏮️ 😐 `def`: - -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} - -## 🚮 🖥 📋 - -🔘 👆 *➡ 🛠️ 🔢*, 🚶‍♀️ 👆 📋 🔢 *🖥 📋* 🎚 ⏮️ 👩‍🔬 `.add_task()`: - -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} - -`.add_task()` 📨 ❌: - -* 📋 🔢 🏃 🖥 (`write_notification`). -* 🙆 🔁 ❌ 👈 🔜 🚶‍♀️ 📋 🔢 ✔ (`email`). -* 🙆 🇨🇻 ❌ 👈 🔜 🚶‍♀️ 📋 🔢 (`message="some notification"`). - -## 🔗 💉 - -⚙️ `BackgroundTasks` 👷 ⏮️ 🔗 💉 ⚙️, 👆 💪 📣 🔢 🆎 `BackgroundTasks` 💗 🎚: *➡ 🛠️ 🔢*, 🔗 (☑), 🎧-🔗, ♒️. - -**FastAPI** 💭 ⚫️❔ 🔠 💼 & ❔ 🏤-⚙️ 🎏 🎚, 👈 🌐 🖥 📋 🔗 👯‍♂️ & 🏃 🖥 ⏮️: - -{* ../../docs_src/background_tasks/tutorial002.py hl[13,15,22,25] *} - -👉 🖼, 📧 🔜 ✍ `log.txt` 📁 *⏮️* 📨 📨. - -🚥 📤 🔢 📨, ⚫️ 🔜 ✍ 🕹 🖥 📋. - -& ⤴️ ➕1️⃣ 🖥 📋 🏗 *➡ 🛠️ 🔢* 🔜 ✍ 📧 ⚙️ `email` ➡ 🔢. - -## 📡 ℹ - -🎓 `BackgroundTasks` 👟 🔗 ⚪️➡️ `starlette.background`. - -⚫️ 🗄/🔌 🔗 🔘 FastAPI 👈 👆 💪 🗄 ⚫️ ⚪️➡️ `fastapi` & ❎ 😫 🗄 🎛 `BackgroundTask` (🍵 `s` 🔚) ⚪️➡️ `starlette.background`. - -🕴 ⚙️ `BackgroundTasks` (& 🚫 `BackgroundTask`), ⚫️ ⤴️ 💪 ⚙️ ⚫️ *➡ 🛠️ 🔢* 🔢 & ✔️ **FastAPI** 🍵 🎂 👆, 💖 🕐❔ ⚙️ `Request` 🎚 🔗. - -⚫️ 💪 ⚙️ `BackgroundTask` 😞 FastAPI, ✋️ 👆 ✔️ ✍ 🎚 👆 📟 & 📨 💃 `Response` 🔌 ⚫️. - -👆 💪 👀 🌖 ℹ 💃 🛂 🩺 🖥 📋. - -## ⚠ - -🚥 👆 💪 🎭 🏋️ 🖥 📊 & 👆 🚫 🎯 💪 ⚫️ 🏃 🎏 🛠️ (🖼, 👆 🚫 💪 💰 💾, 🔢, ♒️), 👆 💪 💰 ⚪️➡️ ⚙️ 🎏 🦏 🧰 💖 🥒. - -👫 😑 🚚 🌖 🏗 📳, 📧/👨‍🏭 📤 👨‍💼, 💖 ✳ ⚖️ ✳, ✋️ 👫 ✔ 👆 🏃 🖥 📋 💗 🛠️, & ✴️, 💗 💽. - -✋️ 🚥 👆 💪 🔐 🔢 & 🎚 ⚪️➡️ 🎏 **FastAPI** 📱, ⚖️ 👆 💪 🎭 🤪 🖥 📋 (💖 📨 📧 📨), 👆 💪 🎯 ⚙️ `BackgroundTasks`. - -## 🌃 - -🗄 & ⚙️ `BackgroundTasks` ⏮️ 🔢 *➡ 🛠️ 🔢* & 🔗 🚮 🖥 📋. diff --git a/docs/em/docs/tutorial/bigger-applications.md b/docs/em/docs/tutorial/bigger-applications.md deleted file mode 100644 index 78a321ae6a..0000000000 --- a/docs/em/docs/tutorial/bigger-applications.md +++ /dev/null @@ -1,530 +0,0 @@ -# 🦏 🈸 - 💗 📁 - -🚥 👆 🏗 🈸 ⚖️ 🕸 🛠️, ⚫️ 🛎 💼 👈 👆 💪 🚮 🌐 🔛 👁 📁. - -**FastAPI** 🚚 🏪 🧰 📊 👆 🈸 ⏪ 🚧 🌐 💪. - -/// info - -🚥 👆 👟 ⚪️➡️ 🏺, 👉 🔜 🌓 🏺 📗. - -/// - -## 🖼 📁 📊 - -➡️ 💬 👆 ✔️ 📁 📊 💖 👉: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -│   ├── dependencies.py -│   └── routers -│   │ ├── __init__.py -│   │ ├── items.py -│   │ └── users.py -│   └── internal -│   ├── __init__.py -│   └── admin.py -``` - -/// tip - -📤 📚 `__init__.py` 📁: 1️⃣ 🔠 📁 ⚖️ 📁. - -👉 ⚫️❔ ✔ 🏭 📟 ⚪️➡️ 1️⃣ 📁 🔘 ➕1️⃣. - -🖼, `app/main.py` 👆 💪 ✔️ ⏸ 💖: - -``` -from app.routers import items -``` - -/// - -* `app` 📁 🔌 🌐. & ⚫️ ✔️ 🛁 📁 `app/__init__.py`, ⚫️ "🐍 📦" (🗃 "🐍 🕹"): `app`. -* ⚫️ 🔌 `app/main.py` 📁. ⚫️ 🔘 🐍 📦 (📁 ⏮️ 📁 `__init__.py`), ⚫️ "🕹" 👈 📦: `app.main`. -* 📤 `app/dependencies.py` 📁, 💖 `app/main.py`, ⚫️ "🕹": `app.dependencies`. -* 📤 📁 `app/routers/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ "🐍 📦": `app.routers`. -* 📁 `app/routers/items.py` 🔘 📦, `app/routers/`,, ⚫️ 🔁: `app.routers.items`. -* 🎏 ⏮️ `app/routers/users.py`, ⚫️ ➕1️⃣ 🔁: `app.routers.users`. -* 📤 📁 `app/internal/` ⏮️ ➕1️⃣ 📁 `__init__.py`, ⚫️ ➕1️⃣ "🐍 📦": `app.internal`. -* & 📁 `app/internal/admin.py` ➕1️⃣ 🔁: `app.internal.admin`. - - - -🎏 📁 📊 ⏮️ 🏤: - -``` -. -├── app # "app" is a Python package -│   ├── __init__.py # this file makes "app" a "Python package" -│   ├── main.py # "main" module, e.g. import app.main -│   ├── dependencies.py # "dependencies" module, e.g. import app.dependencies -│   └── routers # "routers" is a "Python subpackage" -│   │ ├── __init__.py # makes "routers" a "Python subpackage" -│   │ ├── items.py # "items" submodule, e.g. import app.routers.items -│   │ └── users.py # "users" submodule, e.g. import app.routers.users -│   └── internal # "internal" is a "Python subpackage" -│   ├── __init__.py # makes "internal" a "Python subpackage" -│   └── admin.py # "admin" submodule, e.g. import app.internal.admin -``` - -## `APIRouter` - -➡️ 💬 📁 💡 🚚 👩‍💻 🔁 `/app/routers/users.py`. - -👆 💚 ✔️ *➡ 🛠️* 🔗 👆 👩‍💻 👽 ⚪️➡️ 🎂 📟, 🚧 ⚫️ 🏗. - -✋️ ⚫️ 🍕 🎏 **FastAPI** 🈸/🕸 🛠️ (⚫️ 🍕 🎏 "🐍 📦"). - -👆 💪 ✍ *➡ 🛠️* 👈 🕹 ⚙️ `APIRouter`. - -### 🗄 `APIRouter` - -👆 🗄 ⚫️ & ✍ "👐" 🎏 🌌 👆 🔜 ⏮️ 🎓 `FastAPI`: - -```Python hl_lines="1 3" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` - -### *➡ 🛠️* ⏮️ `APIRouter` - -& ⤴️ 👆 ⚙️ ⚫️ 📣 👆 *➡ 🛠️*. - -⚙️ ⚫️ 🎏 🌌 👆 🔜 ⚙️ `FastAPI` 🎓: - -```Python hl_lines="6 11 16" title="app/routers/users.py" -{!../../docs_src/bigger_applications/app/routers/users.py!} -``` - -👆 💪 💭 `APIRouter` "🐩 `FastAPI`" 🎓. - -🌐 🎏 🎛 🐕‍🦺. - -🌐 🎏 `parameters`, `responses`, `dependencies`, `tags`, ♒️. - -/// tip - -👉 🖼, 🔢 🤙 `router`, ✋️ 👆 💪 📛 ⚫️ 👐 👆 💚. - -/// - -👥 🔜 🔌 👉 `APIRouter` 👑 `FastAPI` 📱, ✋️ 🥇, ➡️ ✅ 🔗 & ➕1️⃣ `APIRouter`. - -## 🔗 - -👥 👀 👈 👥 🔜 💪 🔗 ⚙️ 📚 🥉 🈸. - -👥 🚮 👫 👫 👍 `dependencies` 🕹 (`app/dependencies.py`). - -👥 🔜 🔜 ⚙️ 🙅 🔗 ✍ 🛃 `X-Token` 🎚: - -```Python hl_lines="1 4-6" title="app/dependencies.py" -{!../../docs_src/bigger_applications/app/dependencies.py!} -``` - -/// tip - -👥 ⚙️ 💭 🎚 📉 👉 🖼. - -✋️ 🎰 💼 👆 🔜 🤚 👍 🏁 ⚙️ 🛠️ [💂‍♂ 🚙](security/index.md){.internal-link target=_blank}. - -/// - -## ➕1️⃣ 🕹 ⏮️ `APIRouter` - -➡️ 💬 👆 ✔️ 🔗 💡 🚚 "🏬" ⚪️➡️ 👆 🈸 🕹 `app/routers/items.py`. - -👆 ✔️ *➡ 🛠️* : - -* `/items/` -* `/items/{item_id}` - -⚫️ 🌐 🎏 📊 ⏮️ `app/routers/users.py`. - -✋️ 👥 💚 🙃 & 📉 📟 🍖. - -👥 💭 🌐 *➡ 🛠️* 👉 🕹 ✔️ 🎏: - -* ➡ `prefix`: `/items`. -* `tags`: (1️⃣ 🔖: `items`). -* ➕ `responses`. -* `dependencies`: 👫 🌐 💪 👈 `X-Token` 🔗 👥 ✍. - -, ↩️ ❎ 🌐 👈 🔠 *➡ 🛠️*, 👥 💪 🚮 ⚫️ `APIRouter`. - -```Python hl_lines="5-10 16 21" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` - -➡ 🔠 *➡ 🛠️* ✔️ ▶️ ⏮️ `/`, 💖: - -```Python hl_lines="1" -@router.get("/{item_id}") -async def read_item(item_id: str): - ... -``` - -...🔡 🔜 🚫 🔌 🏁 `/`. - -, 🔡 👉 💼 `/items`. - -👥 💪 🚮 📇 `tags` & ➕ `responses` 👈 🔜 ✔ 🌐 *➡ 🛠️* 🔌 👉 📻. - -& 👥 💪 🚮 📇 `dependencies` 👈 🔜 🚮 🌐 *➡ 🛠️* 📻 & 🔜 🛠️/❎ 🔠 📨 ⚒ 👫. - -/// tip - -🗒 👈, 🌅 💖 [🔗 *➡ 🛠️ 👨‍🎨*](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 🙅‍♂ 💲 🔜 🚶‍♀️ 👆 *➡ 🛠️ 🔢*. - -/// - -🔚 🏁 👈 🏬 ➡ 🔜: - -* `/items/` -* `/items/{item_id}` - -...👥 🎯. - -* 👫 🔜 ™ ⏮️ 📇 🔖 👈 🔌 👁 🎻 `"items"`. - * 👫 "🔖" ✴️ ⚠ 🏧 🎓 🧾 ⚙️ (⚙️ 🗄). -* 🌐 👫 🔜 🔌 🔁 `responses`. -* 🌐 👫 *➡ 🛠️* 🔜 ✔️ 📇 `dependencies` 🔬/🛠️ ⏭ 👫. - * 🚥 👆 📣 🔗 🎯 *➡ 🛠️*, **👫 🔜 🛠️ 💁‍♂️**. - * 📻 🔗 🛠️ 🥇, ⤴️ [`dependencies` 👨‍🎨](dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, & ⤴️ 😐 🔢 🔗. - * 👆 💪 🚮 [`Security` 🔗 ⏮️ `scopes`](../advanced/security/oauth2-scopes.md){.internal-link target=_blank}. - -/// tip - -✔️ `dependencies` `APIRouter` 💪 ⚙️, 🖼, 🚚 🤝 🎂 👪 *➡ 🛠️*. 🚥 🔗 🚫 🚮 📦 🔠 1️⃣ 👫. - -/// - -/// check - -`prefix`, `tags`, `responses`, & `dependencies` 🔢 (📚 🎏 💼) ⚒ ⚪️➡️ **FastAPI** ℹ 👆 ❎ 📟 ❎. - -/// - -### 🗄 🔗 - -👉 📟 👨‍❤‍👨 🕹 `app.routers.items`, 📁 `app/routers/items.py`. - -& 👥 💪 🤚 🔗 🔢 ⚪️➡️ 🕹 `app.dependencies`, 📁 `app/dependencies.py`. - -👥 ⚙️ ⚖ 🗄 ⏮️ `..` 🔗: - -```Python hl_lines="3" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` - -#### ❔ ⚖ 🗄 👷 - -/// tip - -🚥 👆 💭 👌 ❔ 🗄 👷, 😣 ⏭ 📄 🔛. - -/// - -👁 ❣ `.`, 💖: - -```Python -from .dependencies import get_token_header -``` - -🔜 ⛓: - -* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)... -* 🔎 🕹 `dependencies` (👽 📁 `app/routers/dependencies.py`)... -* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`. - -✋️ 👈 📁 🚫 🔀, 👆 🔗 📁 `app/dependencies.py`. - -💭 ❔ 👆 📱/📁 📊 👀 💖: - - - ---- - -2️⃣ ❣ `..`, 💖: - -```Python -from ..dependencies import get_token_header -``` - -⛓: - -* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)... -* 🚶 👪 📦 (📁 `app/`)... -* & 📤, 🔎 🕹 `dependencies` (📁 `app/dependencies.py`)... -* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`. - -👈 👷 ☑ ❗ 👶 - ---- - -🎏 🌌, 🚥 👥 ✔️ ⚙️ 3️⃣ ❣ `...`, 💖: - -```Python -from ...dependencies import get_token_header -``` - -that 🔜 ⛓: - -* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/routers/items.py`) 🖖 (📁 `app/routers/`)... -* 🚶 👪 📦 (📁 `app/`)... -* ⤴️ 🚶 👪 👈 📦 (📤 🙅‍♂ 👪 📦, `app` 🔝 🎚 👶)... -* & 📤, 🔎 🕹 `dependencies` (📁 `app/dependencies.py`)... -* & ⚪️➡️ ⚫️, 🗄 🔢 `get_token_header`. - -👈 🔜 🔗 📦 🔛 `app/`, ⏮️ 🚮 👍 📁 `__init__.py`, ♒️. ✋️ 👥 🚫 ✔️ 👈. , 👈 🔜 🚮 ❌ 👆 🖼. 👶 - -✋️ 🔜 👆 💭 ❔ ⚫️ 👷, 👆 💪 ⚙️ ⚖ 🗄 👆 👍 📱 🙅‍♂ 🤔 ❔ 🏗 👫. 👶 - -### 🚮 🛃 `tags`, `responses`, & `dependencies` - -👥 🚫 ❎ 🔡 `/items` 🚫 `tags=["items"]` 🔠 *➡ 🛠️* ↩️ 👥 🚮 👫 `APIRouter`. - -✋️ 👥 💪 🚮 _🌅_ `tags` 👈 🔜 ✔ 🎯 *➡ 🛠️*, & ➕ `responses` 🎯 👈 *➡ 🛠️*: - -```Python hl_lines="30-31" title="app/routers/items.py" -{!../../docs_src/bigger_applications/app/routers/items.py!} -``` - -/// tip - -👉 🏁 ➡ 🛠️ 🔜 ✔️ 🌀 🔖: `["items", "custom"]`. - - & ⚫️ 🔜 ✔️ 👯‍♂️ 📨 🧾, 1️⃣ `404` & 1️⃣ `403`. - -/// - -## 👑 `FastAPI` - -🔜, ➡️ 👀 🕹 `app/main.py`. - -📥 🌐❔ 👆 🗄 & ⚙️ 🎓 `FastAPI`. - -👉 🔜 👑 📁 👆 🈸 👈 👔 🌐 👯‍♂️. - -& 🏆 👆 ⚛ 🔜 🔜 🖖 🚮 👍 🎯 🕹, 👑 📁 🔜 🙅. - -### 🗄 `FastAPI` - -👆 🗄 & ✍ `FastAPI` 🎓 🛎. - -& 👥 💪 📣 [🌐 🔗](dependencies/global-dependencies.md){.internal-link target=_blank} 👈 🔜 🌀 ⏮️ 🔗 🔠 `APIRouter`: - -```Python hl_lines="1 3 7" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` - -### 🗄 `APIRouter` - -🔜 👥 🗄 🎏 🔁 👈 ✔️ `APIRouter`Ⓜ: - -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` - -📁 `app/routers/users.py` & `app/routers/items.py` 🔁 👈 🍕 🎏 🐍 📦 `app`, 👥 💪 ⚙️ 👁 ❣ `.` 🗄 👫 ⚙️ "⚖ 🗄". - -### ❔ 🏭 👷 - -📄: - -```Python -from .routers import items, users -``` - -⛓: - -* ▶️ 🎏 📦 👈 👉 🕹 (📁 `app/main.py`) 🖖 (📁 `app/`)... -* 👀 📦 `routers` (📁 `app/routers/`)... -* & ⚪️➡️ ⚫️, 🗄 🔁 `items` (📁 `app/routers/items.py`) & `users` (📁 `app/routers/users.py`)... - -🕹 `items` 🔜 ✔️ 🔢 `router` (`items.router`). 👉 🎏 1️⃣ 👥 ✍ 📁 `app/routers/items.py`, ⚫️ `APIRouter` 🎚. - -& ⤴️ 👥 🎏 🕹 `users`. - -👥 💪 🗄 👫 💖: - -```Python -from app.routers import items, users -``` - -/// info - -🥇 ⏬ "⚖ 🗄": - -```Python -from .routers import items, users -``` - -🥈 ⏬ "🎆 🗄": - -```Python -from app.routers import items, users -``` - -💡 🌅 🔃 🐍 📦 & 🕹, ✍ 🛂 🐍 🧾 🔃 🕹. - -/// - -### ❎ 📛 💥 - -👥 🏭 🔁 `items` 🔗, ↩️ 🏭 🚮 🔢 `router`. - -👉 ↩️ 👥 ✔️ ➕1️⃣ 🔢 📛 `router` 🔁 `users`. - -🚥 👥 ✔️ 🗄 1️⃣ ⏮️ 🎏, 💖: - -```Python -from .routers.items import router -from .routers.users import router -``` - -`router` ⚪️➡️ `users` 🔜 📁 1️⃣ ⚪️➡️ `items` & 👥 🚫🔜 💪 ⚙️ 👫 🎏 🕰. - -, 💪 ⚙️ 👯‍♂️ 👫 🎏 📁, 👥 🗄 🔁 🔗: - -```Python hl_lines="5" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` - -### 🔌 `APIRouter`Ⓜ `users` & `items` - -🔜, ➡️ 🔌 `router`Ⓜ ⚪️➡️ 🔁 `users` & `items`: - -```Python hl_lines="10-11" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` - -/// info - -`users.router` 🔌 `APIRouter` 🔘 📁 `app/routers/users.py`. - - & `items.router` 🔌 `APIRouter` 🔘 📁 `app/routers/items.py`. - -/// - -⏮️ `app.include_router()` 👥 💪 🚮 🔠 `APIRouter` 👑 `FastAPI` 🈸. - -⚫️ 🔜 🔌 🌐 🛣 ⚪️➡️ 👈 📻 🍕 ⚫️. - -/// note | 📡 ℹ - -⚫️ 🔜 🤙 🔘 ✍ *➡ 🛠️* 🔠 *➡ 🛠️* 👈 📣 `APIRouter`. - -, ⛅ 🎑, ⚫️ 🔜 🤙 👷 🚥 🌐 🎏 👁 📱. - -/// - -/// check - -👆 🚫 ✔️ 😟 🔃 🎭 🕐❔ ✅ 📻. - -👉 🔜 ✊ ⏲ & 🔜 🕴 🔨 🕴. - -⚫️ 🏆 🚫 📉 🎭. 👶 - -/// - -### 🔌 `APIRouter` ⏮️ 🛃 `prefix`, `tags`, `responses`, & `dependencies` - -🔜, ➡️ 🌈 👆 🏢 🤝 👆 `app/internal/admin.py` 📁. - -⚫️ 🔌 `APIRouter` ⏮️ 📡 *➡ 🛠️* 👈 👆 🏢 💰 🖖 📚 🏗. - -👉 🖼 ⚫️ 🔜 💎 🙅. ✋️ ➡️ 💬 👈 ↩️ ⚫️ 💰 ⏮️ 🎏 🏗 🏢, 👥 🚫🔜 🔀 ⚫️ & 🚮 `prefix`, `dependencies`, `tags`, ♒️. 🔗 `APIRouter`: - -```Python hl_lines="3" title="app/internal/admin.py" -{!../../docs_src/bigger_applications/app/internal/admin.py!} -``` - -✋️ 👥 💚 ⚒ 🛃 `prefix` 🕐❔ ✅ `APIRouter` 👈 🌐 🚮 *➡ 🛠️* ▶️ ⏮️ `/admin`, 👥 💚 🔐 ⚫️ ⏮️ `dependencies` 👥 ⏪ ✔️ 👉 🏗, & 👥 💚 🔌 `tags` & `responses`. - -👥 💪 📣 🌐 👈 🍵 ✔️ 🔀 ⏮️ `APIRouter` 🚶‍♀️ 👈 🔢 `app.include_router()`: - -```Python hl_lines="14-17" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` - -👈 🌌, ⏮️ `APIRouter` 🔜 🚧 ⚗, 👥 💪 💰 👈 🎏 `app/internal/admin.py` 📁 ⏮️ 🎏 🏗 🏢. - -🏁 👈 👆 📱, 🔠 *➡ 🛠️* ⚪️➡️ `admin` 🕹 🔜 ✔️: - -* 🔡 `/admin`. -* 🔖 `admin`. -* 🔗 `get_token_header`. -* 📨 `418`. 👶 - -✋️ 👈 🔜 🕴 📉 👈 `APIRouter` 👆 📱, 🚫 🙆 🎏 📟 👈 ⚙️ ⚫️. - -, 🖼, 🎏 🏗 💪 ⚙️ 🎏 `APIRouter` ⏮️ 🎏 🤝 👩‍🔬. - -### 🔌 *➡ 🛠️* - -👥 💪 🚮 *➡ 🛠️* 🔗 `FastAPI` 📱. - -📥 👥 ⚫️... 🎦 👈 👥 💪 🤷: - -```Python hl_lines="21-23" title="app/main.py" -{!../../docs_src/bigger_applications/app/main.py!} -``` - -& ⚫️ 🔜 👷 ☑, 👯‍♂️ ⏮️ 🌐 🎏 *➡ 🛠️* 🚮 ⏮️ `app.include_router()`. - -/// info | 📶 📡 ℹ - -**🗒**: 👉 📶 📡 ℹ 👈 👆 🎲 💪 **🚶**. - ---- - - `APIRouter`Ⓜ 🚫 "🗻", 👫 🚫 👽 ⚪️➡️ 🎂 🈸. - -👉 ↩️ 👥 💚 🔌 👫 *➡ 🛠️* 🗄 🔗 & 👩‍💻 🔢. - -👥 🚫🔜 ❎ 👫 & "🗻" 👫 ➡ 🎂, *➡ 🛠️* "🖖" (🏤-✍), 🚫 🔌 🔗. - -/// - -## ✅ 🏧 🛠️ 🩺 - -🔜, 🏃 `uvicorn`, ⚙️ 🕹 `app.main` & 🔢 `app`: - -
- -```console -$ uvicorn app.main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -& 📂 🩺 http://127.0.0.1:8000/docs. - -👆 🔜 👀 🏧 🛠️ 🩺, ✅ ➡ ⚪️➡️ 🌐 🔁, ⚙️ ☑ ➡ (& 🔡) & ☑ 🔖: - - - -## 🔌 🎏 📻 💗 🕰 ⏮️ 🎏 `prefix` - -👆 💪 ⚙️ `.include_router()` 💗 🕰 ⏮️ *🎏* 📻 ⚙️ 🎏 🔡. - -👉 💪 ⚠, 🖼, 🎦 🎏 🛠️ 🔽 🎏 🔡, ✅ `/api/v1` & `/api/latest`. - -👉 🏧 ⚙️ 👈 👆 5️⃣📆 🚫 🤙 💪, ✋️ ⚫️ 📤 💼 👆. - -## 🔌 `APIRouter` ➕1️⃣ - -🎏 🌌 👆 💪 🔌 `APIRouter` `FastAPI` 🈸, 👆 💪 🔌 `APIRouter` ➕1️⃣ `APIRouter` ⚙️: - -```Python -router.include_router(other_router) -``` - -⚒ 💭 👆 ⚫️ ⏭ 🔌 `router` `FastAPI` 📱, 👈 *➡ 🛠️* ⚪️➡️ `other_router` 🔌. diff --git a/docs/em/docs/tutorial/body-fields.md b/docs/em/docs/tutorial/body-fields.md deleted file mode 100644 index f202284b5b..0000000000 --- a/docs/em/docs/tutorial/body-fields.md +++ /dev/null @@ -1,60 +0,0 @@ -# 💪 - 🏑 - -🎏 🌌 👆 💪 📣 🌖 🔬 & 🗃 *➡ 🛠️ 🔢* 🔢 ⏮️ `Query`, `Path` & `Body`, 👆 💪 📣 🔬 & 🗃 🔘 Pydantic 🏷 ⚙️ Pydantic `Field`. - -## 🗄 `Field` - -🥇, 👆 ✔️ 🗄 ⚫️: - -{* ../../docs_src/body_fields/tutorial001.py hl[4] *} - -/// warning - -👀 👈 `Field` 🗄 🔗 ⚪️➡️ `pydantic`, 🚫 ⚪️➡️ `fastapi` 🌐 🎂 (`Query`, `Path`, `Body`, ♒️). - -/// - -## 📣 🏷 🔢 - -👆 💪 ⤴️ ⚙️ `Field` ⏮️ 🏷 🔢: - -{* ../../docs_src/body_fields/tutorial001.py hl[11:14] *} - -`Field` 👷 🎏 🌌 `Query`, `Path` & `Body`, ⚫️ ✔️ 🌐 🎏 🔢, ♒️. - -/// note | 📡 ℹ - -🤙, `Query`, `Path` & 🎏 👆 🔜 👀 ⏭ ✍ 🎚 🏿 ⚠ `Param` 🎓, ❔ ⚫️ 🏿 Pydantic `FieldInfo` 🎓. - - & Pydantic `Field` 📨 👐 `FieldInfo` 👍. - -`Body` 📨 🎚 🏿 `FieldInfo` 🔗. & 📤 🎏 👆 🔜 👀 ⏪ 👈 🏿 `Body` 🎓. - -💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. - -/// - -/// tip - -👀 ❔ 🔠 🏷 🔢 ⏮️ 🆎, 🔢 💲 & `Field` ✔️ 🎏 📊 *➡ 🛠️ 🔢* 🔢, ⏮️ `Field` ↩️ `Path`, `Query` & `Body`. - -/// - -## 🚮 ➕ ℹ - -👆 💪 📣 ➕ ℹ `Field`, `Query`, `Body`, ♒️. & ⚫️ 🔜 🔌 🏗 🎻 🔗. - -👆 🔜 💡 🌅 🔃 ❎ ➕ ℹ ⏪ 🩺, 🕐❔ 🏫 📣 🖼. - -/// warning - -➕ 🔑 🚶‍♀️ `Field` 🔜 🎁 📉 🗄 🔗 👆 🈸. -👫 🔑 5️⃣📆 🚫 🎯 🍕 🗄 🔧, 🗄 🧰, 🖼 [🗄 💳](https://validator.swagger.io/), 5️⃣📆 🚫 👷 ⏮️ 👆 🏗 🔗. - -/// - -## 🌃 - -👆 💪 ⚙️ Pydantic `Field` 📣 ➕ 🔬 & 🗃 🏷 🔢. - -👆 💪 ⚙️ ➕ 🇨🇻 ❌ 🚶‍♀️ 🌖 🎻 🔗 🗃. diff --git a/docs/em/docs/tutorial/body-multiple-params.md b/docs/em/docs/tutorial/body-multiple-params.md deleted file mode 100644 index 3a2f2bd547..0000000000 --- a/docs/em/docs/tutorial/body-multiple-params.md +++ /dev/null @@ -1,171 +0,0 @@ -# 💪 - 💗 🔢 - -🔜 👈 👥 ✔️ 👀 ❔ ⚙️ `Path` & `Query`, ➡️ 👀 🌅 🏧 ⚙️ 📨 💪 📄. - -## 🌀 `Path`, `Query` & 💪 🔢 - -🥇, ↗️, 👆 💪 🌀 `Path`, `Query` & 📨 💪 🔢 📄 ➡ & **FastAPI** 🔜 💭 ⚫️❔. - -& 👆 💪 📣 💪 🔢 📦, ⚒ 🔢 `None`: - -{* ../../docs_src/body_multiple_params/tutorial001.py hl[19:21] *} - -/// note - -👀 👈, 👉 💼, `item` 👈 🔜 ✊ ⚪️➡️ 💪 📦. ⚫️ ✔️ `None` 🔢 💲. - -/// - -## 💗 💪 🔢 - -⏮️ 🖼, *➡ 🛠️* 🔜 ⌛ 🎻 💪 ⏮️ 🔢 `Item`, 💖: - -```JSON -{ - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2 -} -``` - -✋️ 👆 💪 📣 💗 💪 🔢, ✅ `item` & `user`: - -{* ../../docs_src/body_multiple_params/tutorial002.py hl[22] *} - -👉 💼, **FastAPI** 🔜 👀 👈 📤 🌅 🌘 1️⃣ 💪 🔢 🔢 (2️⃣ 🔢 👈 Pydantic 🏷). - -, ⚫️ 🔜 ⤴️ ⚙️ 🔢 📛 🔑 (🏑 📛) 💪, & ⌛ 💪 💖: - -```JSON -{ - "item": { - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2 - }, - "user": { - "username": "dave", - "full_name": "Dave Grohl" - } -} -``` - -/// note - -👀 👈 ✋️ `item` 📣 🎏 🌌 ⏭, ⚫️ 🔜 ⌛ 🔘 💪 ⏮️ 🔑 `item`. - -/// - -**FastAPI** 🔜 🏧 🛠️ ⚪️➡️ 📨, 👈 🔢 `item` 📨 ⚫️ 🎯 🎚 & 🎏 `user`. - -⚫️ 🔜 🎭 🔬 ⚗ 💽, & 🔜 📄 ⚫️ 💖 👈 🗄 🔗 & 🏧 🩺. - -## ⭐ 💲 💪 - -🎏 🌌 📤 `Query` & `Path` 🔬 ➕ 💽 🔢 & ➡ 🔢, **FastAPI** 🚚 🌓 `Body`. - -🖼, ↔ ⏮️ 🏷, 👆 💪 💭 👈 👆 💚 ✔️ ➕1️⃣ 🔑 `importance` 🎏 💪, 🥈 `item` & `user`. - -🚥 👆 📣 ⚫️, ↩️ ⚫️ ⭐ 💲, **FastAPI** 🔜 🤔 👈 ⚫️ 🔢 🔢. - -✋️ 👆 💪 💡 **FastAPI** 😥 ⚫️ ➕1️⃣ 💪 🔑 ⚙️ `Body`: - -{* ../../docs_src/body_multiple_params/tutorial003.py hl[22] *} - -👉 💼, **FastAPI** 🔜 ⌛ 💪 💖: - -```JSON -{ - "item": { - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2 - }, - "user": { - "username": "dave", - "full_name": "Dave Grohl" - }, - "importance": 5 -} -``` - -🔄, ⚫️ 🔜 🗜 📊 🆎, ✔, 📄, ♒️. - -## 💗 💪 = & 🔢 - -↗️, 👆 💪 📣 🌖 🔢 🔢 🕐❔ 👆 💪, 🌖 🙆 💪 🔢. - -, 🔢, ⭐ 💲 🔬 🔢 🔢, 👆 🚫 ✔️ 🎯 🚮 `Query`, 👆 💪: - -```Python -q: Union[str, None] = None -``` - -⚖️ 🐍 3️⃣.1️⃣0️⃣ & 🔛: - -```Python -q: str | None = None -``` - -🖼: - -{* ../../docs_src/body_multiple_params/tutorial004.py hl[27] *} - -/// info - -`Body` ✔️ 🌐 🎏 ➕ 🔬 & 🗃 🔢 `Query`,`Path` & 🎏 👆 🔜 👀 ⏪. - -/// - -## ⏯ 👁 💪 🔢 - -➡️ 💬 👆 🕴 ✔️ 👁 `item` 💪 🔢 ⚪️➡️ Pydantic 🏷 `Item`. - -🔢, **FastAPI** 🔜 ⤴️ ⌛ 🚮 💪 🔗. - -✋️ 🚥 👆 💚 ⚫️ ⌛ 🎻 ⏮️ 🔑 `item` & 🔘 ⚫️ 🏷 🎚, ⚫️ 🔨 🕐❔ 👆 📣 ➕ 💪 🔢, 👆 💪 ⚙️ 🎁 `Body` 🔢 `embed`: - -```Python -item: Item = Body(embed=True) -``` - -: - -{* ../../docs_src/body_multiple_params/tutorial005.py hl[17] *} - -👉 💼 **FastAPI** 🔜 ⌛ 💪 💖: - -```JSON hl_lines="2" -{ - "item": { - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2 - } -} -``` - -↩️: - -```JSON -{ - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2 -} -``` - -## 🌃 - -👆 💪 🚮 💗 💪 🔢 👆 *➡ 🛠️ 🔢*, ✋️ 📨 💪 🕴 ✔️ 👁 💪. - -✋️ **FastAPI** 🔜 🍵 ⚫️, 🤝 👆 ☑ 📊 👆 🔢, & ✔ & 📄 ☑ 🔗 *➡ 🛠️*. - -👆 💪 📣 ⭐ 💲 📨 🍕 💪. - -& 👆 💪 💡 **FastAPI** ⏯ 💪 🔑 🕐❔ 📤 🕴 👁 🔢 📣. diff --git a/docs/em/docs/tutorial/body-nested-models.md b/docs/em/docs/tutorial/body-nested-models.md deleted file mode 100644 index 6c8d5a6100..0000000000 --- a/docs/em/docs/tutorial/body-nested-models.md +++ /dev/null @@ -1,247 +0,0 @@ -# 💪 - 🔁 🏷 - -⏮️ **FastAPI**, 👆 💪 🔬, ✔, 📄, & ⚙️ 🎲 🙇 🐦 🏷 (👏 Pydantic). - -## 📇 🏑 - -👆 💪 🔬 🔢 🏾. 🖼, 🐍 `list`: - -{* ../../docs_src/body_nested_models/tutorial001.py hl[14] *} - -👉 🔜 ⚒ `tags` 📇, 👐 ⚫️ 🚫 📣 🆎 🔣 📇. - -## 📇 🏑 ⏮️ 🆎 🔢 - -✋️ 🐍 ✔️ 🎯 🌌 📣 📇 ⏮️ 🔗 🆎, ⚖️ "🆎 🔢": - -### 🗄 ⌨ `List` - -🐍 3️⃣.9️⃣ & 🔛 👆 💪 ⚙️ 🐩 `list` 📣 👫 🆎 ✍ 👥 🔜 👀 🔛. 👶 - -✋️ 🐍 ⏬ ⏭ 3️⃣.9️⃣ (3️⃣.6️⃣ & 🔛), 👆 🥇 💪 🗄 `List` ⚪️➡️ 🐩 🐍 `typing` 🕹: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - -### 📣 `list` ⏮️ 🆎 🔢 - -📣 🆎 👈 ✔️ 🆎 🔢 (🔗 🆎), 💖 `list`, `dict`, `tuple`: - -* 🚥 👆 🐍 ⏬ 🔅 🌘 3️⃣.9️⃣, 🗄 👫 🌓 ⏬ ⚪️➡️ `typing` 🕹 -* 🚶‍♀️ 🔗 🆎(Ⓜ) "🆎 🔢" ⚙️ ⬜ 🗜: `[` & `]` - -🐍 3️⃣.9️⃣ ⚫️ 🔜: - -```Python -my_list: list[str] -``` - -⏬ 🐍 ⏭ 3️⃣.9️⃣, ⚫️ 🔜: - -```Python -from typing import List - -my_list: List[str] -``` - -👈 🌐 🐩 🐍 ❕ 🆎 📄. - -⚙️ 👈 🎏 🐩 ❕ 🏷 🔢 ⏮️ 🔗 🆎. - -, 👆 🖼, 👥 💪 ⚒ `tags` 🎯 "📇 🎻": - -{* ../../docs_src/body_nested_models/tutorial002.py hl[14] *} - -## ⚒ 🆎 - -✋️ ⤴️ 👥 💭 🔃 ⚫️, & 🤔 👈 🔖 🚫🔜 🚫 🔁, 👫 🔜 🎲 😍 🎻. - -& 🐍 ✔️ 🎁 💽 🆎 ⚒ 😍 🏬, `set`. - -⤴️ 👥 💪 📣 `tags` ⚒ 🎻: - -{* ../../docs_src/body_nested_models/tutorial003.py hl[1,14] *} - -⏮️ 👉, 🚥 👆 📨 📨 ⏮️ ❎ 📊, ⚫️ 🔜 🗜 ⚒ 😍 🏬. - -& 🕐❔ 👆 🔢 👈 📊, 🚥 ℹ ✔️ ❎, ⚫️ 🔜 🔢 ⚒ 😍 🏬. - -& ⚫️ 🔜 ✍ / 📄 ➡️ 💁‍♂️. - -## 🐦 🏷 - -🔠 🔢 Pydantic 🏷 ✔️ 🆎. - -✋️ 👈 🆎 💪 ⚫️ ➕1️⃣ Pydantic 🏷. - -, 👆 💪 📣 🙇 🐦 🎻 "🎚" ⏮️ 🎯 🔢 📛, 🆎 & 🔬. - -🌐 👈, 🎲 🐦. - -### 🔬 📊 - -🖼, 👥 💪 🔬 `Image` 🏷: - -{* ../../docs_src/body_nested_models/tutorial004.py hl[9:11] *} - -### ⚙️ 📊 🆎 - -& ⤴️ 👥 💪 ⚙️ ⚫️ 🆎 🔢: - -{* ../../docs_src/body_nested_models/tutorial004.py hl[20] *} - -👉 🔜 ⛓ 👈 **FastAPI** 🔜 ⌛ 💪 🎏: - -```JSON -{ - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2, - "tags": ["rock", "metal", "bar"], - "image": { - "url": "http://example.com/baz.jpg", - "name": "The Foo live" - } -} -``` - -🔄, 🤸 👈 📄, ⏮️ **FastAPI** 👆 🤚: - -* 👨‍🎨 🐕‍🦺 (🛠️, ♒️), 🐦 🏷 -* 💽 🛠️ -* 💽 🔬 -* 🏧 🧾 - -## 🎁 🆎 & 🔬 - -↖️ ⚪️➡️ 😐 ⭐ 🆎 💖 `str`, `int`, `float`, ♒️. 👆 💪 ⚙️ 🌅 🏗 ⭐ 🆎 👈 😖 ⚪️➡️ `str`. - -👀 🌐 🎛 👆 ✔️, 🛒 🩺 Pydantic 😍 🆎. 👆 🔜 👀 🖼 ⏭ 📃. - -🖼, `Image` 🏷 👥 ✔️ `url` 🏑, 👥 💪 📣 ⚫️ ↩️ `str`, Pydantic `HttpUrl`: - -{* ../../docs_src/body_nested_models/tutorial005.py hl[4,10] *} - -🎻 🔜 ✅ ☑ 📛, & 📄 🎻 🔗 / 🗄 ✅. - -## 🔢 ⏮️ 📇 📊 - -👆 💪 ⚙️ Pydantic 🏷 🏾 `list`, `set`, ♒️: - -{* ../../docs_src/body_nested_models/tutorial006.py hl[20] *} - -👉 🔜 ⌛ (🗜, ✔, 📄, ♒️) 🎻 💪 💖: - -```JSON hl_lines="11" -{ - "name": "Foo", - "description": "The pretender", - "price": 42.0, - "tax": 3.2, - "tags": [ - "rock", - "metal", - "bar" - ], - "images": [ - { - "url": "http://example.com/baz.jpg", - "name": "The Foo live" - }, - { - "url": "http://example.com/dave.jpg", - "name": "The Baz" - } - ] -} -``` - -/// info - -👀 ❔ `images` 🔑 🔜 ✔️ 📇 🖼 🎚. - -/// - -## 🙇 🐦 🏷 - -👆 💪 🔬 🎲 🙇 🐦 🏷: - -{* ../../docs_src/body_nested_models/tutorial007.py hl[9,14,20,23,27] *} - -/// info - -👀 ❔ `Offer` ✔️ 📇 `Item`Ⓜ, ❔ 🔄 ✔️ 📦 📇 `Image`Ⓜ - -/// - -## 💪 😁 📇 - -🚥 🔝 🎚 💲 🎻 💪 👆 ⌛ 🎻 `array` (🐍 `list`), 👆 💪 📣 🆎 🔢 🔢, 🎏 Pydantic 🏷: - -```Python -images: List[Image] -``` - -⚖️ 🐍 3️⃣.9️⃣ & 🔛: - -```Python -images: list[Image] -``` - -: - -{* ../../docs_src/body_nested_models/tutorial008.py hl[15] *} - -## 👨‍🎨 🐕‍🦺 🌐 - -& 👆 🤚 👨‍🎨 🐕‍🦺 🌐. - -🏬 🔘 📇: - - - -👆 🚫 🚫 🤚 👉 😇 👨‍🎨 🐕‍🦺 🚥 👆 👷 🔗 ⏮️ `dict` ↩️ Pydantic 🏷. - -✋️ 👆 🚫 ✔️ 😟 🔃 👫 👯‍♂️, 📨 #️⃣ 🗜 🔁 & 👆 🔢 🗜 🔁 🎻 💁‍♂️. - -## 💪 ❌ `dict`Ⓜ - -👆 💪 📣 💪 `dict` ⏮️ 🔑 🆎 & 💲 🎏 🆎. - -🍵 ✔️ 💭 ⏪ ⚫️❔ ☑ 🏑/🔢 📛 (🔜 💼 ⏮️ Pydantic 🏷). - -👉 🔜 ⚠ 🚥 👆 💚 📨 🔑 👈 👆 🚫 ⏪ 💭. - ---- - -🎏 ⚠ 💼 🕐❔ 👆 💚 ✔️ 🔑 🎏 🆎, ✅ `int`. - -👈 ⚫️❔ 👥 🔜 👀 📥. - -👉 💼, 👆 🔜 🚫 🙆 `dict` 📏 ⚫️ ✔️ `int` 🔑 ⏮️ `float` 💲: - -{* ../../docs_src/body_nested_models/tutorial009.py hl[9] *} - -/// tip - -✔️ 🤯 👈 🎻 🕴 🐕‍🦺 `str` 🔑. - -✋️ Pydantic ✔️ 🏧 💽 🛠️. - -👉 ⛓ 👈, ✋️ 👆 🛠️ 👩‍💻 💪 🕴 📨 🎻 🔑, 📏 👈 🎻 🔌 😁 🔢, Pydantic 🔜 🗜 👫 & ✔ 👫. - - & `dict` 👆 📨 `weights` 🔜 🤙 ✔️ `int` 🔑 & `float` 💲. - -/// - -## 🌃 - -⏮️ **FastAPI** 👆 ✔️ 🔆 💪 🚚 Pydantic 🏷, ⏪ 🚧 👆 📟 🙅, 📏 & 😍. - -✋️ ⏮️ 🌐 💰: - -* 👨‍🎨 🐕‍🦺 (🛠️ 🌐 ❗) -* 💽 🛠️ (.Ⓜ.. ✍ / 🛠️) -* 💽 🔬 -* 🔗 🧾 -* 🏧 🩺 diff --git a/docs/em/docs/tutorial/body-updates.md b/docs/em/docs/tutorial/body-updates.md deleted file mode 100644 index 7e2fbfaf79..0000000000 --- a/docs/em/docs/tutorial/body-updates.md +++ /dev/null @@ -1,100 +0,0 @@ -# 💪 - ℹ - -## ℹ ❎ ⏮️ `PUT` - -ℹ 🏬 👆 💪 ⚙️ 🇺🇸🔍 `PUT` 🛠️. - -👆 💪 ⚙️ `jsonable_encoder` 🗜 🔢 💽 📊 👈 💪 🏪 🎻 (✅ ⏮️ ☁ 💽). 🖼, 🏭 `datetime` `str`. - -{* ../../docs_src/body_updates/tutorial001.py hl[30:35] *} - -`PUT` ⚙️ 📨 💽 👈 🔜 ❎ ♻ 💽. - -### ⚠ 🔃 ❎ - -👈 ⛓ 👈 🚥 👆 💚 ℹ 🏬 `bar` ⚙️ `PUT` ⏮️ 💪 ⚗: - -```Python -{ - "name": "Barz", - "price": 3, - "description": None, -} -``` - -↩️ ⚫️ 🚫 🔌 ⏪ 🏪 🔢 `"tax": 20.2`, 🔢 🏷 🔜 ✊ 🔢 💲 `"tax": 10.5`. - -& 📊 🔜 🖊 ⏮️ 👈 "🆕" `tax` `10.5`. - -## 🍕 ℹ ⏮️ `PATCH` - -👆 💪 ⚙️ 🇺🇸🔍 `PATCH` 🛠️ *🍕* ℹ 💽. - -👉 ⛓ 👈 👆 💪 📨 🕴 💽 👈 👆 💚 ℹ, 🍂 🎂 🐣. - -/// note - -`PATCH` 🌘 🛎 ⚙️ & 💭 🌘 `PUT`. - - & 📚 🏉 ⚙️ 🕴 `PUT`, 🍕 ℹ. - -👆 **🆓** ⚙️ 👫 👐 👆 💚, **FastAPI** 🚫 🚫 🙆 🚫. - -✋️ 👉 🦮 🎦 👆, 🌖 ⚖️ 🌘, ❔ 👫 🎯 ⚙️. - -/// - -### ⚙️ Pydantic `exclude_unset` 🔢 - -🚥 👆 💚 📨 🍕 ℹ, ⚫️ 📶 ⚠ ⚙️ 🔢 `exclude_unset` Pydantic 🏷 `.dict()`. - -💖 `item.dict(exclude_unset=True)`. - -👈 🔜 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ 🕐❔ 🏗 `item` 🏷, 🚫 🔢 💲. - -⤴️ 👆 💪 ⚙️ 👉 🏗 `dict` ⏮️ 🕴 💽 👈 ⚒ (📨 📨), 🚫 🔢 💲: - -{* ../../docs_src/body_updates/tutorial002.py hl[34] *} - -### ⚙️ Pydantic `update` 🔢 - -🔜, 👆 💪 ✍ 📁 ♻ 🏷 ⚙️ `.copy()`, & 🚶‍♀️ `update` 🔢 ⏮️ `dict` ⚗ 💽 ℹ. - -💖 `stored_item_model.copy(update=update_data)`: - -{* ../../docs_src/body_updates/tutorial002.py hl[35] *} - -### 🍕 ℹ 🌃 - -📄, ✔ 🍕 ℹ 👆 🔜: - -* (⚗) ⚙️ `PATCH` ↩️ `PUT`. -* 🗃 🏪 💽. -* 🚮 👈 💽 Pydantic 🏷. -* 🏗 `dict` 🍵 🔢 💲 ⚪️➡️ 🔢 🏷 (⚙️ `exclude_unset`). - * 👉 🌌 👆 💪 ℹ 🕴 💲 🤙 ⚒ 👩‍💻, ↩️ 🔐 💲 ⏪ 🏪 ⏮️ 🔢 💲 👆 🏷. -* ✍ 📁 🏪 🏷, 🛠️ ⚫️ 🔢 ⏮️ 📨 🍕 ℹ (⚙️ `update` 🔢). -* 🗜 📁 🏷 🕳 👈 💪 🏪 👆 💽 (🖼, ⚙️ `jsonable_encoder`). - * 👉 ⭐ ⚙️ 🏷 `.dict()` 👩‍🔬 🔄, ✋️ ⚫️ ⚒ 💭 (& 🗜) 💲 💽 🆎 👈 💪 🗜 🎻, 🖼, `datetime` `str`. -* 🖊 💽 👆 💽. -* 📨 ℹ 🏷. - -{* ../../docs_src/body_updates/tutorial002.py hl[30:37] *} - -/// tip - -👆 💪 🤙 ⚙️ 👉 🎏 ⚒ ⏮️ 🇺🇸🔍 `PUT` 🛠️. - -✋️ 🖼 📥 ⚙️ `PATCH` ↩️ ⚫️ ✍ 👫 ⚙️ 💼. - -/// - -/// note - -👀 👈 🔢 🏷 ✔. - -, 🚥 👆 💚 📨 🍕 ℹ 👈 💪 🚫 🌐 🔢, 👆 💪 ✔️ 🏷 ⏮️ 🌐 🔢 ™ 📦 (⏮️ 🔢 💲 ⚖️ `None`). - -🔬 ⚪️➡️ 🏷 ⏮️ 🌐 📦 💲 **ℹ** & 🏷 ⏮️ ✔ 💲 **🏗**, 👆 💪 ⚙️ 💭 🔬 [➕ 🏷](extra-models.md){.internal-link target=_blank}. - -/// diff --git a/docs/em/docs/tutorial/body.md b/docs/em/docs/tutorial/body.md deleted file mode 100644 index 09e1d7ccaa..0000000000 --- a/docs/em/docs/tutorial/body.md +++ /dev/null @@ -1,162 +0,0 @@ -# 📨 💪 - -🕐❔ 👆 💪 📨 📊 ⚪️➡️ 👩‍💻 (➡️ 💬, 🖥) 👆 🛠️, 👆 📨 ⚫️ **📨 💪**. - -**📨** 💪 📊 📨 👩‍💻 👆 🛠️. **📨** 💪 💽 👆 🛠️ 📨 👩‍💻. - -👆 🛠️ 🌖 🕧 ✔️ 📨 **📨** 💪. ✋️ 👩‍💻 🚫 🎯 💪 📨 **📨** 💪 🌐 🕰. - -📣 **📨** 💪, 👆 ⚙️ Pydantic 🏷 ⏮️ 🌐 👫 🏋️ & 💰. - -/// info - -📨 💽, 👆 🔜 ⚙️ 1️⃣: `POST` (🌅 ⚠), `PUT`, `DELETE` ⚖️ `PATCH`. - -📨 💪 ⏮️ `GET` 📨 ✔️ ⚠ 🎭 🔧, 👐, ⚫️ 🐕‍🦺 FastAPI, 🕴 📶 🏗/😕 ⚙️ 💼. - -⚫️ 🚫, 🎓 🩺 ⏮️ 🦁 🎚 🏆 🚫 🎦 🧾 💪 🕐❔ ⚙️ `GET`, & 🗳 🖕 💪 🚫 🐕‍🦺 ⚫️. - -/// - -## 🗄 Pydantic `BaseModel` - -🥇, 👆 💪 🗄 `BaseModel` ⚪️➡️ `pydantic`: - -{* ../../docs_src/body/tutorial001.py hl[4] *} - -## ✍ 👆 💽 🏷 - -⤴️ 👆 📣 👆 💽 🏷 🎓 👈 😖 ⚪️➡️ `BaseModel`. - -⚙️ 🐩 🐍 🆎 🌐 🔢: - -{* ../../docs_src/body/tutorial001.py hl[7:11] *} - -🎏 🕐❔ 📣 🔢 🔢, 🕐❔ 🏷 🔢 ✔️ 🔢 💲, ⚫️ 🚫 ✔. ⏪, ⚫️ ✔. ⚙️ `None` ⚒ ⚫️ 📦. - -🖼, 👉 🏷 🔛 📣 🎻 "`object`" (⚖️ 🐍 `dict`) 💖: - -```JSON -{ - "name": "Foo", - "description": "An optional description", - "price": 45.2, - "tax": 3.5 -} -``` - -... `description` & `tax` 📦 (⏮️ 🔢 💲 `None`), 👉 🎻 "`object`" 🔜 ☑: - -```JSON -{ - "name": "Foo", - "price": 45.2 -} -``` - -## 📣 ⚫️ 🔢 - -🚮 ⚫️ 👆 *➡ 🛠️*, 📣 ⚫️ 🎏 🌌 👆 📣 ➡ & 🔢 🔢: - -{* ../../docs_src/body/tutorial001.py hl[18] *} - -...& 📣 🚮 🆎 🏷 👆 ✍, `Item`. - -## 🏁 - -⏮️ 👈 🐍 🆎 📄, **FastAPI** 🔜: - -* ✍ 💪 📨 🎻. -* 🗜 🔗 🆎 (🚥 💪). -* ✔ 💽. - * 🚥 💽 ❌, ⚫️ 🔜 📨 👌 & 🆑 ❌, ☠️ ⚫️❔ 🌐❔ & ⚫️❔ ❌ 📊. -* 🤝 👆 📨 📊 🔢 `item`. - * 👆 📣 ⚫️ 🔢 🆎 `Item`, 👆 🔜 ✔️ 🌐 👨‍🎨 🐕‍🦺 (🛠️, ♒️) 🌐 🔢 & 👫 🆎. -* 🏗 🎻 🔗 🔑 👆 🏷, 👆 💪 ⚙️ 👫 🙆 🙆 👆 💖 🚥 ⚫️ ⚒ 🔑 👆 🏗. -* 👈 🔗 🔜 🍕 🏗 🗄 🔗, & ⚙️ 🏧 🧾 . - -## 🏧 🩺 - -🎻 🔗 👆 🏷 🔜 🍕 👆 🗄 🏗 🔗, & 🔜 🎦 🎓 🛠️ 🩺: - - - -& 🔜 ⚙️ 🛠️ 🩺 🔘 🔠 *➡ 🛠️* 👈 💪 👫: - - - -## 👨‍🎨 🐕‍🦺 - -👆 👨‍🎨, 🔘 👆 🔢 👆 🔜 🤚 🆎 🔑 & 🛠️ 🌐 (👉 🚫🔜 🔨 🚥 👆 📨 `dict` ↩️ Pydantic 🏷): - - - -👆 🤚 ❌ ✅ ❌ 🆎 🛠️: - - - -👉 🚫 🤞, 🎂 🛠️ 🏗 🤭 👈 🔧. - -& ⚫️ 🙇 💯 🔧 🌓, ⏭ 🙆 🛠️, 🚚 ⚫️ 🔜 👷 ⏮️ 🌐 👨‍🎨. - -📤 🔀 Pydantic ⚫️ 🐕‍🦺 👉. - -⏮️ 🖼 ✊ ⏮️ 🎙 🎙 📟. - -✋️ 👆 🔜 🤚 🎏 👨‍🎨 🐕‍🦺 ⏮️ 🗒 & 🌅 🎏 🐍 👨‍🎨: - - - -/// tip - -🚥 👆 ⚙️ 🗒 👆 👨‍🎨, 👆 💪 ⚙️ Pydantic 🗒 📁. - -⚫️ 📉 👨‍🎨 🐕‍🦺 Pydantic 🏷, ⏮️: - -* 🚘-🛠️ -* 🆎 ✅ -* 🛠️ -* 🔎 -* 🔬 - -/// - -## ⚙️ 🏷 - -🔘 🔢, 👆 💪 🔐 🌐 🔢 🏷 🎚 🔗: - -{* ../../docs_src/body/tutorial002.py hl[21] *} - -## 📨 💪 ➕ ➡ 🔢 - -👆 💪 📣 ➡ 🔢 & 📨 💪 🎏 🕰. - -**FastAPI** 🔜 🤔 👈 🔢 🔢 👈 🏏 ➡ 🔢 🔜 **✊ ⚪️➡️ ➡**, & 👈 🔢 🔢 👈 📣 Pydantic 🏷 🔜 **✊ ⚪️➡️ 📨 💪**. - -{* ../../docs_src/body/tutorial003.py hl[17:18] *} - -## 📨 💪 ➕ ➡ ➕ 🔢 🔢 - -👆 💪 📣 **💪**, **➡** & **🔢** 🔢, 🌐 🎏 🕰. - -**FastAPI** 🔜 🤔 🔠 👫 & ✊ 📊 ⚪️➡️ ☑ 🥉. - -{* ../../docs_src/body/tutorial004.py hl[18] *} - -🔢 🔢 🔜 🤔 ⏩: - -* 🚥 🔢 📣 **➡**, ⚫️ 🔜 ⚙️ ➡ 🔢. -* 🚥 🔢 **⭐ 🆎** (💖 `int`, `float`, `str`, `bool`, ♒️) ⚫️ 🔜 🔬 **🔢** 🔢. -* 🚥 🔢 📣 🆎 **Pydantic 🏷**, ⚫️ 🔜 🔬 📨 **💪**. - -/// note - -FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`. - - `Union` `Union[str, None]` 🚫 ⚙️ FastAPI, ✋️ 🔜 ✔ 👆 👨‍🎨 🤝 👆 👍 🐕‍🦺 & 🔍 ❌. - -/// - -## 🍵 Pydantic - -🚥 👆 🚫 💚 ⚙️ Pydantic 🏷, 👆 💪 ⚙️ **💪** 🔢. 👀 🩺 [💪 - 💗 🔢: ⭐ 💲 💪](body-multiple-params.md#_2){.internal-link target=_blank}. diff --git a/docs/em/docs/tutorial/cookie-params.md b/docs/em/docs/tutorial/cookie-params.md deleted file mode 100644 index 4699fe2a54..0000000000 --- a/docs/em/docs/tutorial/cookie-params.md +++ /dev/null @@ -1,35 +0,0 @@ -# 🍪 🔢 - -👆 💪 🔬 🍪 🔢 🎏 🌌 👆 🔬 `Query` & `Path` 🔢. - -## 🗄 `Cookie` - -🥇 🗄 `Cookie`: - -{* ../../docs_src/cookie_params/tutorial001.py hl[3] *} - -## 📣 `Cookie` 🔢 - -⤴️ 📣 🍪 🔢 ⚙️ 🎏 📊 ⏮️ `Path` & `Query`. - -🥇 💲 🔢 💲, 👆 💪 🚶‍♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢: - -{* ../../docs_src/cookie_params/tutorial001.py hl[9] *} - -/// note | 📡 ℹ - -`Cookie` "👭" 🎓 `Path` & `Query`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓. - -✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `Cookie` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. - -/// - -/// info - -📣 🍪, 👆 💪 ⚙️ `Cookie`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢. - -/// - -## 🌃 - -📣 🍪 ⏮️ `Cookie`, ⚙️ 🎏 ⚠ ⚓ `Query` & `Path`. diff --git a/docs/em/docs/tutorial/cors.md b/docs/em/docs/tutorial/cors.md deleted file mode 100644 index 44ab4adc59..0000000000 --- a/docs/em/docs/tutorial/cors.md +++ /dev/null @@ -1,85 +0,0 @@ -# ⚜ (✖️-🇨🇳 ℹ 🤝) - -⚜ ⚖️ "✖️-🇨🇳 ℹ 🤝" 🔗 ⚠ 🕐❔ 🕸 🏃‍♂ 🖥 ✔️ 🕸 📟 👈 🔗 ⏮️ 👩‍💻, & 👩‍💻 🎏 "🇨🇳" 🌘 🕸. - -## 🇨🇳 - -🇨🇳 🌀 🛠️ (`http`, `https`), 🆔 (`myapp.com`, `localhost`, `localhost.tiangolo.com`), & ⛴ (`80`, `443`, `8080`). - -, 🌐 👫 🎏 🇨🇳: - -* `http://localhost` -* `https://localhost` -* `http://localhost:8080` - -🚥 👫 🌐 `localhost`, 👫 ⚙️ 🎏 🛠️ ⚖️ ⛴,, 👫 🎏 "🇨🇳". - -## 🔁 - -, ➡️ 💬 👆 ✔️ 🕸 🏃 👆 🖥 `http://localhost:8080`, & 🚮 🕸 🔄 🔗 ⏮️ 👩‍💻 🏃 `http://localhost` (↩️ 👥 🚫 ✔ ⛴, 🖥 🔜 🤔 🔢 ⛴ `80`). - -⤴️, 🖥 🔜 📨 🇺🇸🔍 `OPTIONS` 📨 👩‍💻, & 🚥 👩‍💻 📨 ☑ 🎚 ✔ 📻 ⚪️➡️ 👉 🎏 🇨🇳 (`http://localhost:8080`) ⤴️ 🖥 🔜 ➡️ 🕸 🕸 📨 🚮 📨 👩‍💻. - -🏆 👉, 👩‍💻 🔜 ✔️ 📇 "✔ 🇨🇳". - -👉 💼, ⚫️ 🔜 ✔️ 🔌 `http://localhost:8080` 🕸 👷 ☑. - -## 🃏 - -⚫️ 💪 📣 📇 `"*"` ("🃏") 💬 👈 🌐 ✔. - -✋️ 👈 🔜 🕴 ✔ 🎯 🆎 📻, 🚫 🌐 👈 🔌 🎓: 🍪, ✔ 🎚 💖 📚 ⚙️ ⏮️ 📨 🤝, ♒️. - -, 🌐 👷 ☑, ⚫️ 👻 ✔ 🎯 ✔ 🇨🇳. - -## ⚙️ `CORSMiddleware` - -👆 💪 🔗 ⚫️ 👆 **FastAPI** 🈸 ⚙️ `CORSMiddleware`. - -* 🗄 `CORSMiddleware`. -* ✍ 📇 ✔ 🇨🇳 (🎻). -* 🚮 ⚫️ "🛠️" 👆 **FastAPI** 🈸. - -👆 💪 ✔ 🚥 👆 👩‍💻 ✔: - -* 🎓 (✔ 🎚, 🍪, ♒️). -* 🎯 🇺🇸🔍 👩‍🔬 (`POST`, `PUT`) ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`. -* 🎯 🇺🇸🔍 🎚 ⚖️ 🌐 👫 ⏮️ 🃏 `"*"`. - -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} - -🔢 🔢 ⚙️ `CORSMiddleware` 🛠️ 🚫 🔢, 👆 🔜 💪 🎯 🛠️ 🎯 🇨🇳, 👩‍🔬, ⚖️ 🎚, ✔ 🖥 ✔ ⚙️ 👫 ✖️-🆔 🔑. - -📄 ❌ 🐕‍🦺: - -* `allow_origins` - 📇 🇨🇳 👈 🔜 ✔ ⚒ ✖️-🇨🇳 📨. 🤶 Ⓜ. `['https://example.org', 'https://www.example.org']`. 👆 💪 ⚙️ `['*']` ✔ 🙆 🇨🇳. -* `allow_origin_regex` - 🎻 🎻 🏏 🛡 🇨🇳 👈 🔜 ✔ ⚒ ✖️-🇨🇳 📨. ✅ `'https://.*\.example\.org'`. -* `allow_methods` - 📇 🇺🇸🔍 👩‍🔬 👈 🔜 ✔ ✖️-🇨🇳 📨. 🔢 `['GET']`. 👆 💪 ⚙️ `['*']` ✔ 🌐 🐩 👩‍🔬. -* `allow_headers` - 📇 🇺🇸🔍 📨 🎚 👈 🔜 🐕‍🦺 ✖️-🇨🇳 📨. 🔢 `[]`. 👆 💪 ⚙️ `['*']` ✔ 🌐 🎚. `Accept`, `Accept-Language`, `Content-Language` & `Content-Type` 🎚 🕧 ✔ 🙅 ⚜ 📨. -* `allow_credentials` - 🎦 👈 🍪 🔜 🐕‍🦺 ✖️-🇨🇳 📨. 🔢 `False`. , `allow_origins` 🚫🔜 ⚒ `['*']` 🎓 ✔, 🇨🇳 🔜 ✔. -* `expose_headers` - 🎦 🙆 📨 🎚 👈 🔜 ⚒ ♿ 🖥. 🔢 `[]`. -* `max_age` - ⚒ 🔆 🕰 🥈 🖥 💾 ⚜ 📨. 🔢 `600`. - -🛠️ 📨 2️⃣ 🎯 🆎 🇺🇸🔍 📨... - -### ⚜ 🛫 📨 - -👉 🙆 `OPTIONS` 📨 ⏮️ `Origin` & `Access-Control-Request-Method` 🎚. - -👉 💼 🛠️ 🔜 🆘 📨 📨 & 📨 ⏮️ ☑ ⚜ 🎚, & 👯‍♂️ `200` ⚖️ `400` 📨 🎓 🎯. - -### 🙅 📨 - -🙆 📨 ⏮️ `Origin` 🎚. 👉 💼 🛠️ 🔜 🚶‍♀️ 📨 🔘 😐, ✋️ 🔜 🔌 ☑ ⚜ 🎚 🔛 📨. - -## 🌅 ℹ - -🌖 ℹ 🔃 , ✅ 🦎 ⚜ 🧾. - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.middleware.cors import CORSMiddleware`. - -**FastAPI** 🚚 📚 🛠️ `fastapi.middleware` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 🛠️ 👟 🔗 ⚪️➡️ 💃. - -/// diff --git a/docs/em/docs/tutorial/debugging.md b/docs/em/docs/tutorial/debugging.md deleted file mode 100644 index 97e61a763f..0000000000 --- a/docs/em/docs/tutorial/debugging.md +++ /dev/null @@ -1,113 +0,0 @@ -# 🛠️ - -👆 💪 🔗 🕹 👆 👨‍🎨, 🖼 ⏮️ 🎙 🎙 📟 ⚖️ 🗒. - -## 🤙 `uvicorn` - -👆 FastAPI 🈸, 🗄 & 🏃 `uvicorn` 🔗: - -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} - -### 🔃 `__name__ == "__main__"` - -👑 🎯 `__name__ == "__main__"` ✔️ 📟 👈 🛠️ 🕐❔ 👆 📁 🤙 ⏮️: - -
- -```console -$ python myapp.py -``` - -
- -✋️ 🚫 🤙 🕐❔ ➕1️⃣ 📁 🗄 ⚫️, 💖: - -```Python -from myapp import app -``` - -#### 🌅 ℹ - -➡️ 💬 👆 📁 🌟 `myapp.py`. - -🚥 👆 🏃 ⚫️ ⏮️: - -
- -```console -$ python myapp.py -``` - -
- -⤴️ 🔗 🔢 `__name__` 👆 📁, ✍ 🔁 🐍, 🔜 ✔️ 💲 🎻 `"__main__"`. - -, 📄: - -```Python - uvicorn.run(app, host="0.0.0.0", port=8000) -``` - -🔜 🏃. - ---- - -👉 🏆 🚫 🔨 🚥 👆 🗄 👈 🕹 (📁). - -, 🚥 👆 ✔️ ➕1️⃣ 📁 `importer.py` ⏮️: - -```Python -from myapp import app - -# Some more code -``` - -👈 💼, 🏧 🔢 🔘 `myapp.py` 🔜 🚫 ✔️ 🔢 `__name__` ⏮️ 💲 `"__main__"`. - -, ⏸: - -```Python - uvicorn.run(app, host="0.0.0.0", port=8000) -``` - -🔜 🚫 🛠️. - -/// info - -🌅 ℹ, ✅ 🛂 🐍 🩺. - -/// - -## 🏃 👆 📟 ⏮️ 👆 🕹 - -↩️ 👆 🏃 Uvicorn 💽 🔗 ⚪️➡️ 👆 📟, 👆 💪 🤙 👆 🐍 📋 (👆 FastAPI 🈸) 🔗 ⚪️➡️ 🕹. - ---- - -🖼, 🎙 🎙 📟, 👆 💪: - -* 🚶 "ℹ" 🎛. -* "🚮 📳...". -* 🖊 "🐍" -* 🏃 🕹 ⏮️ 🎛 "`Python: Current File (Integrated Terminal)`". - -⚫️ 🔜 ⤴️ ▶️ 💽 ⏮️ 👆 **FastAPI** 📟, ⛔️ 👆 0️⃣, ♒️. - -📥 ❔ ⚫️ 💪 👀: - - - ---- - -🚥 👆 ⚙️ 🗒, 👆 💪: - -* 📂 "🏃" 🍣. -* 🖊 🎛 "ℹ...". -* ⤴️ 🔑 🍣 🎦 🆙. -* 🖊 📁 ℹ (👉 💼, `main.py`). - -⚫️ 🔜 ⤴️ ▶️ 💽 ⏮️ 👆 **FastAPI** 📟, ⛔️ 👆 0️⃣, ♒️. - -📥 ❔ ⚫️ 💪 👀: - - diff --git a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md deleted file mode 100644 index 41938bc7b8..0000000000 --- a/docs/em/docs/tutorial/dependencies/classes-as-dependencies.md +++ /dev/null @@ -1,180 +0,0 @@ -# 🎓 🔗 - -⏭ 🤿 ⏬ 🔘 **🔗 💉** ⚙️, ➡️ ♻ ⏮️ 🖼. - -## `dict` ⚪️➡️ ⏮️ 🖼 - -⏮️ 🖼, 👥 🛬 `dict` ⚪️➡️ 👆 🔗 ("☑"): - -{* ../../docs_src/dependencies/tutorial001.py hl[9] *} - -✋️ ⤴️ 👥 🤚 `dict` 🔢 `commons` *➡ 🛠️ 🔢*. - -& 👥 💭 👈 👨‍🎨 💪 🚫 🚚 📚 🐕‍🦺 (💖 🛠️) `dict`Ⓜ, ↩️ 👫 💪 🚫 💭 👫 🔑 & 💲 🆎. - -👥 💪 👍... - -## ⚫️❔ ⚒ 🔗 - -🆙 🔜 👆 ✔️ 👀 🔗 📣 🔢. - -✋️ 👈 🚫 🕴 🌌 📣 🔗 (👐 ⚫️ 🔜 🎲 🌖 ⚠). - -🔑 ⚖ 👈 🔗 🔜 "🇧🇲". - -"**🇧🇲**" 🐍 🕳 👈 🐍 💪 "🤙" 💖 🔢. - -, 🚥 👆 ✔️ 🎚 `something` (👈 💪 _🚫_ 🔢) & 👆 💪 "🤙" ⚫️ (🛠️ ⚫️) 💖: - -```Python -something() -``` - -⚖️ - -```Python -something(some_argument, some_keyword_argument="foo") -``` - -⤴️ ⚫️ "🇧🇲". - -## 🎓 🔗 - -👆 5️⃣📆 👀 👈 ✍ 👐 🐍 🎓, 👆 ⚙️ 👈 🎏 ❕. - -🖼: - -```Python -class Cat: - def __init__(self, name: str): - self.name = name - - -fluffy = Cat(name="Mr Fluffy") -``` - -👉 💼, `fluffy` 👐 🎓 `Cat`. - -& ✍ `fluffy`, 👆 "🤙" `Cat`. - -, 🐍 🎓 **🇧🇲**. - -⤴️, **FastAPI**, 👆 💪 ⚙️ 🐍 🎓 🔗. - -⚫️❔ FastAPI 🤙 ✅ 👈 ⚫️ "🇧🇲" (🔢, 🎓 ⚖️ 🕳 🙆) & 🔢 🔬. - -🚥 👆 🚶‍♀️ "🇧🇲" 🔗 **FastAPI**, ⚫️ 🔜 🔬 🔢 👈 "🇧🇲", & 🛠️ 👫 🎏 🌌 🔢 *➡ 🛠️ 🔢*. ✅ 🎧-🔗. - -👈 ✔ 🇧🇲 ⏮️ 🙅‍♂ 🔢 🌐. 🎏 ⚫️ 🔜 *➡ 🛠️ 🔢* ⏮️ 🙅‍♂ 🔢. - -⤴️, 👥 💪 🔀 🔗 "☑" `common_parameters` ⚪️➡️ 🔛 🎓 `CommonQueryParams`: - -{* ../../docs_src/dependencies/tutorial002.py hl[11:15] *} - -💸 🙋 `__init__` 👩‍🔬 ⚙️ ✍ 👐 🎓: - -{* ../../docs_src/dependencies/tutorial002.py hl[12] *} - -...⚫️ ✔️ 🎏 🔢 👆 ⏮️ `common_parameters`: - -{* ../../docs_src/dependencies/tutorial001.py hl[9] *} - -📚 🔢 ⚫️❔ **FastAPI** 🔜 ⚙️ "❎" 🔗. - -👯‍♂️ 💼, ⚫️ 🔜 ✔️: - -* 📦 `q` 🔢 🔢 👈 `str`. -* `skip` 🔢 🔢 👈 `int`, ⏮️ 🔢 `0`. -* `limit` 🔢 🔢 👈 `int`, ⏮️ 🔢 `100`. - -👯‍♂️ 💼 💽 🔜 🗜, ✔, 📄 🔛 🗄 🔗, ♒️. - -## ⚙️ ⚫️ - -🔜 👆 💪 📣 👆 🔗 ⚙️ 👉 🎓. - -{* ../../docs_src/dependencies/tutorial002.py hl[19] *} - -**FastAPI** 🤙 `CommonQueryParams` 🎓. 👉 ✍ "👐" 👈 🎓 & 👐 🔜 🚶‍♀️ 🔢 `commons` 👆 🔢. - -## 🆎 ✍ 🆚 `Depends` - -👀 ❔ 👥 ✍ `CommonQueryParams` 🕐 🔛 📟: - -```Python -commons: CommonQueryParams = Depends(CommonQueryParams) -``` - -🏁 `CommonQueryParams`,: - -```Python -... = Depends(CommonQueryParams) -``` - -...⚫️❔ **FastAPI** 🔜 🤙 ⚙️ 💭 ⚫️❔ 🔗. - -⚪️➡️ ⚫️ 👈 FastAPI 🔜 ⚗ 📣 🔢 & 👈 ⚫️❔ FastAPI 🔜 🤙 🤙. - ---- - -👉 💼, 🥇 `CommonQueryParams`,: - -```Python -commons: CommonQueryParams ... -``` - -...🚫 ✔️ 🙆 🎁 🔑 **FastAPI**. FastAPI 🏆 🚫 ⚙️ ⚫️ 💽 🛠️, 🔬, ♒️. (⚫️ ⚙️ `= Depends(CommonQueryParams)` 👈). - -👆 💪 🤙 ✍: - -```Python -commons = Depends(CommonQueryParams) -``` - -...: - -{* ../../docs_src/dependencies/tutorial003.py hl[19] *} - -✋️ 📣 🆎 💡 👈 🌌 👆 👨‍🎨 🔜 💭 ⚫️❔ 🔜 🚶‍♀️ 🔢 `commons`, & ⤴️ ⚫️ 💪 ℹ 👆 ⏮️ 📟 🛠️, 🆎 ✅, ♒️: - - - -## ⌨ - -✋️ 👆 👀 👈 👥 ✔️ 📟 🔁 📥, ✍ `CommonQueryParams` 🕐: - -```Python -commons: CommonQueryParams = Depends(CommonQueryParams) -``` - -**FastAPI** 🚚 ⌨ 👫 💼, 🌐❔ 🔗 *🎯* 🎓 👈 **FastAPI** 🔜 "🤙" ✍ 👐 🎓 ⚫️. - -📚 🎯 💼, 👆 💪 📄: - -↩️ ✍: - -```Python -commons: CommonQueryParams = Depends(CommonQueryParams) -``` - -...👆 ✍: - -```Python -commons: CommonQueryParams = Depends() -``` - -👆 📣 🔗 🆎 🔢, & 👆 ⚙️ `Depends()` 🚮 "🔢" 💲 (👈 ⏮️ `=`) 👈 🔢 🔢, 🍵 🙆 🔢 `Depends()`, ↩️ ✔️ ✍ 🌕 🎓 *🔄* 🔘 `Depends(CommonQueryParams)`. - -🎏 🖼 🔜 ⤴️ 👀 💖: - -{* ../../docs_src/dependencies/tutorial004.py hl[19] *} - -...& **FastAPI** 🔜 💭 ⚫️❔. - -/// tip - -🚥 👈 😑 🌅 😨 🌘 👍, 🤷‍♂ ⚫️, 👆 🚫 *💪* ⚫️. - -⚫️ ⌨. ↩️ **FastAPI** 💅 🔃 🤝 👆 📉 📟 🔁. - -/// diff --git a/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md b/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md deleted file mode 100644 index ab144a497b..0000000000 --- a/docs/em/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md +++ /dev/null @@ -1,69 +0,0 @@ -# 🔗 ➡ 🛠️ 👨‍🎨 - -💼 👆 🚫 🤙 💪 📨 💲 🔗 🔘 👆 *➡ 🛠️ 🔢*. - -⚖️ 🔗 🚫 📨 💲. - -✋️ 👆 💪 ⚫️ 🛠️/❎. - -📚 💼, ↩️ 📣 *➡ 🛠️ 🔢* 🔢 ⏮️ `Depends`, 👆 💪 🚮 `list` `dependencies` *➡ 🛠️ 👨‍🎨*. - -## 🚮 `dependencies` *➡ 🛠️ 👨‍🎨* - -*➡ 🛠️ 👨‍🎨* 📨 📦 ❌ `dependencies`. - -⚫️ 🔜 `list` `Depends()`: - -{* ../../docs_src/dependencies/tutorial006.py hl[17] *} - -👉 🔗 🔜 🛠️/❎ 🎏 🌌 😐 🔗. ✋️ 👫 💲 (🚥 👫 📨 🙆) 🏆 🚫 🚶‍♀️ 👆 *➡ 🛠️ 🔢*. - -/// tip - -👨‍🎨 ✅ ♻ 🔢 🔢, & 🎦 👫 ❌. - -⚙️ 👉 `dependencies` *➡ 🛠️ 👨‍🎨* 👆 💪 ⚒ 💭 👫 🛠️ ⏪ ❎ 👨‍🎨/🏭 ❌. - -⚫️ 💪 ℹ ❎ 😨 🆕 👩‍💻 👈 👀 ♻ 🔢 👆 📟 & 💪 💭 ⚫️ 🙃. - -/// - -/// info - -👉 🖼 👥 ⚙️ 💭 🛃 🎚 `X-Key` & `X-Token`. - -✋️ 🎰 💼, 🕐❔ 🛠️ 💂‍♂, 👆 🔜 🤚 🌖 💰 ⚪️➡️ ⚙️ 🛠️ [💂‍♂ 🚙 (⏭ 📃)](../security/index.md){.internal-link target=_blank}. - -/// - -## 🔗 ❌ & 📨 💲 - -👆 💪 ⚙️ 🎏 🔗 *🔢* 👆 ⚙️ 🛎. - -### 🔗 📄 - -👫 💪 📣 📨 📄 (💖 🎚) ⚖️ 🎏 🎧-🔗: - -{* ../../docs_src/dependencies/tutorial006.py hl[6,11] *} - -### 🤚 ⚠ - -👫 🔗 💪 `raise` ⚠, 🎏 😐 🔗: - -{* ../../docs_src/dependencies/tutorial006.py hl[8,13] *} - -### 📨 💲 - -& 👫 💪 📨 💲 ⚖️ 🚫, 💲 🏆 🚫 ⚙️. - -, 👆 💪 🏤-⚙️ 😐 🔗 (👈 📨 💲) 👆 ⏪ ⚙️ 👱 🙆, & ✋️ 💲 🏆 🚫 ⚙️, 🔗 🔜 🛠️: - -{* ../../docs_src/dependencies/tutorial006.py hl[9,14] *} - -## 🔗 👪 *➡ 🛠️* - -⏪, 🕐❔ 👂 🔃 ❔ 📊 🦏 🈸 ([🦏 🈸 - 💗 📁](../../tutorial/bigger-applications.md){.internal-link target=_blank}), 🎲 ⏮️ 💗 📁, 👆 🔜 💡 ❔ 📣 👁 `dependencies` 🔢 👪 *➡ 🛠️*. - -## 🌐 🔗 - -⏭ 👥 🔜 👀 ❔ 🚮 🔗 🎂 `FastAPI` 🈸, 👈 👫 ✔ 🔠 *➡ 🛠️*. diff --git a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md deleted file mode 100644 index 1b37b1cf29..0000000000 --- a/docs/em/docs/tutorial/dependencies/dependencies-with-yield.md +++ /dev/null @@ -1,232 +0,0 @@ -# 🔗 ⏮️ 🌾 - -FastAPI 🐕‍🦺 🔗 👈 ➕ 🔁 ⏮️ 🏁. - -👉, ⚙️ `yield` ↩️ `return`, & ✍ ➕ 🔁 ⏮️. - -/// tip - -⚒ 💭 ⚙️ `yield` 1️⃣ 👁 🕰. - -/// - -/// note | 📡 ℹ - -🙆 🔢 👈 ☑ ⚙️ ⏮️: - -* `@contextlib.contextmanager` ⚖️ -* `@contextlib.asynccontextmanager` - -🔜 ☑ ⚙️ **FastAPI** 🔗. - -👐, FastAPI ⚙️ 📚 2️⃣ 👨‍🎨 🔘. - -/// - -## 💽 🔗 ⏮️ `yield` - -🖼, 👆 💪 ⚙️ 👉 ✍ 💽 🎉 & 🔐 ⚫️ ⏮️ 🏁. - -🕴 📟 ⏭ & 🔌 `yield` 📄 🛠️ ⏭ 📨 📨: - -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} - -🌾 💲 ⚫️❔ 💉 🔘 *➡ 🛠️* & 🎏 🔗: - -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} - -📟 📄 `yield` 📄 🛠️ ⏮️ 📨 ✔️ 🚚: - -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} - -/// tip - -👆 💪 ⚙️ `async` ⚖️ 😐 🔢. - -**FastAPI** 🔜 ▶️️ 👜 ⏮️ 🔠, 🎏 ⏮️ 😐 🔗. - -/// - -## 🔗 ⏮️ `yield` & `try` - -🚥 👆 ⚙️ `try` 🍫 🔗 ⏮️ `yield`, 👆 🔜 📨 🙆 ⚠ 👈 🚮 🕐❔ ⚙️ 🔗. - -🖼, 🚥 📟 ☝ 🖕, ➕1️⃣ 🔗 ⚖️ *➡ 🛠️*, ⚒ 💽 💵 "💾" ⚖️ ✍ 🙆 🎏 ❌, 👆 🔜 📨 ⚠ 👆 🔗. - -, 👆 💪 👀 👈 🎯 ⚠ 🔘 🔗 ⏮️ `except SomeException`. - -🎏 🌌, 👆 💪 ⚙️ `finally` ⚒ 💭 🚪 📶 🛠️, 🙅‍♂ 🤔 🚥 📤 ⚠ ⚖️ 🚫. - -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} - -## 🎧-🔗 ⏮️ `yield` - -👆 💪 ✔️ 🎧-🔗 & "🌲" 🎧-🔗 🙆 📐 & 💠, & 🙆 ⚖️ 🌐 👫 💪 ⚙️ `yield`. - -**FastAPI** 🔜 ⚒ 💭 👈 "🚪 📟" 🔠 🔗 ⏮️ `yield` 🏃 ☑ ✔. - -🖼, `dependency_c` 💪 ✔️ 🔗 🔛 `dependency_b`, & `dependency_b` 🔛 `dependency_a`: - -{* ../../docs_src/dependencies/tutorial008.py hl[4,12,20] *} - -& 🌐 👫 💪 ⚙️ `yield`. - -👉 💼 `dependency_c`, 🛠️ 🚮 🚪 📟, 💪 💲 ⚪️➡️ `dependency_b` (📥 📛 `dep_b`) 💪. - -& , 🔄, `dependency_b` 💪 💲 ⚪️➡️ `dependency_a` (📥 📛 `dep_a`) 💪 🚮 🚪 📟. - -{* ../../docs_src/dependencies/tutorial008.py hl[16:17,24:25] *} - -🎏 🌌, 👆 💪 ✔️ 🔗 ⏮️ `yield` & `return` 🌀. - -& 👆 💪 ✔️ 👁 🔗 👈 🚚 📚 🎏 🔗 ⏮️ `yield`, ♒️. - -👆 💪 ✔️ 🙆 🌀 🔗 👈 👆 💚. - -**FastAPI** 🔜 ⚒ 💭 🌐 🏃 ☑ ✔. - -/// note | 📡 ℹ - -👉 👷 👏 🐍 🔑 👨‍💼. - -**FastAPI** ⚙️ 👫 🔘 🏆 👉. - -/// - -## 🔗 ⏮️ `yield` & `HTTPException` - -👆 👀 👈 👆 💪 ⚙️ 🔗 ⏮️ `yield` & ✔️ `try` 🍫 👈 ✊ ⚠. - -⚫️ 5️⃣📆 😋 🤚 `HTTPException` ⚖️ 🎏 🚪 📟, ⏮️ `yield`. ✋️ **⚫️ 🏆 🚫 👷**. - -🚪 📟 🔗 ⏮️ `yield` 🛠️ *⏮️* 📨 📨, [⚠ 🐕‍🦺](../handling-errors.md#_4){.internal-link target=_blank} 🔜 ✔️ ⏪ 🏃. 📤 🕳 😽 ⚠ 🚮 👆 🔗 🚪 📟 (⏮️ `yield`). - -, 🚥 👆 🤚 `HTTPException` ⏮️ `yield`, 🔢 (⚖️ 🙆 🛃) ⚠ 🐕‍🦺 👈 ✊ `HTTPException`Ⓜ & 📨 🇺🇸🔍 4️⃣0️⃣0️⃣ 📨 🏆 🚫 📤 ✊ 👈 ⚠ 🚫🔜. - -👉 ⚫️❔ ✔ 🕳 ⚒ 🔗 (✅ 💽 🎉), 🖼, ⚙️ 🖥 📋. - -🖥 📋 🏃 *⏮️* 📨 ✔️ 📨. 📤 🙅‍♂ 🌌 🤚 `HTTPException` ↩️ 📤 🚫 🌌 🔀 📨 👈 *⏪ 📨*. - -✋️ 🚥 🖥 📋 ✍ 💽 ❌, 🌘 👆 💪 💾 ⚖️ 😬 🔐 🎉 🔗 ⏮️ `yield`, & 🎲 🕹 ❌ ⚖️ 📄 ⚫️ 🛰 🕵 ⚙️. - -🚥 👆 ✔️ 📟 👈 👆 💭 💪 🤚 ⚠, 🏆 😐/"🙃" 👜 & 🚮 `try` 🍫 👈 📄 📟. - -🚥 👆 ✔️ 🛃 ⚠ 👈 👆 🔜 💖 🍵 *⏭* 🛬 📨 & 🎲 ❎ 📨, 🎲 🙋‍♀ `HTTPException`, ✍ [🛃 ⚠ 🐕‍🦺](../handling-errors.md#_4){.internal-link target=_blank}. - -/// tip - -👆 💪 🤚 ⚠ 🔌 `HTTPException` *⏭* `yield`. ✋️ 🚫 ⏮️. - -/// - -🔁 🛠️ 🌅 ⚖️ 🌘 💖 👉 📊. 🕰 💧 ⚪️➡️ 🔝 🔝. & 🔠 🏓 1️⃣ 🍕 🔗 ⚖️ 🛠️ 📟. - -```mermaid -sequenceDiagram - -participant client as Client -participant handler as Exception handler -participant dep as Dep with yield -participant operation as Path Operation -participant tasks as Background tasks - - Note over client,tasks: Can raise exception for dependency, handled after response is sent - Note over client,operation: Can raise HTTPException and can change the response - client ->> dep: Start request - Note over dep: Run code up to yield - opt raise - dep -->> handler: Raise HTTPException - handler -->> client: HTTP error response - dep -->> dep: Raise other exception - end - dep ->> operation: Run dependency, e.g. DB session - opt raise - operation -->> dep: Raise HTTPException - dep -->> handler: Auto forward exception - handler -->> client: HTTP error response - operation -->> dep: Raise other exception - dep -->> handler: Auto forward exception - end - operation ->> client: Return response to client - Note over client,operation: Response is already sent, can't change it anymore - opt Tasks - operation -->> tasks: Send background tasks - end - opt Raise other exception - tasks -->> dep: Raise other exception - end - Note over dep: After yield - opt Handle other exception - dep -->> dep: Handle exception, can't change response. E.g. close DB session. - end -``` - -/// info - -🕴 **1️⃣ 📨** 🔜 📨 👩‍💻. ⚫️ 💪 1️⃣ ❌ 📨 ⚖️ ⚫️ 🔜 📨 ⚪️➡️ *➡ 🛠️*. - -⏮️ 1️⃣ 📚 📨 📨, 🙅‍♂ 🎏 📨 💪 📨. - -/// - -/// tip - -👉 📊 🎦 `HTTPException`, ✋️ 👆 💪 🤚 🙆 🎏 ⚠ ❔ 👆 ✍ [🛃 ⚠ 🐕‍🦺](../handling-errors.md#_4){.internal-link target=_blank}. - -🚥 👆 🤚 🙆 ⚠, ⚫️ 🔜 🚶‍♀️ 🔗 ⏮️ 🌾, 🔌 `HTTPException`, & ⤴️ **🔄** ⚠ 🐕‍🦺. 🚥 📤 🙅‍♂ ⚠ 🐕‍🦺 👈 ⚠, ⚫️ 🔜 ⤴️ 🍵 🔢 🔗 `ServerErrorMiddleware`, 🛬 5️⃣0️⃣0️⃣ 🇺🇸🔍 👔 📟, ➡️ 👩‍💻 💭 👈 📤 ❌ 💽. - -/// - -## 🔑 👨‍💼 - -### ⚫️❔ "🔑 👨‍💼" - -"🔑 👨‍💼" 🙆 👈 🐍 🎚 👈 👆 💪 ⚙️ `with` 📄. - -🖼, 👆 💪 ⚙️ `with` ✍ 📁: - -```Python -with open("./somefile.txt") as f: - contents = f.read() - print(contents) -``` - -🔘, `open("./somefile.txt")` ✍ 🎚 👈 🤙 "🔑 👨‍💼". - -🕐❔ `with` 🍫 🏁, ⚫️ ⚒ 💭 🔐 📁, 🚥 📤 ⚠. - -🕐❔ 👆 ✍ 🔗 ⏮️ `yield`, **FastAPI** 🔜 🔘 🗜 ⚫️ 🔑 👨‍💼, & 🌀 ⚫️ ⏮️ 🎏 🔗 🧰. - -### ⚙️ 🔑 👨‍💼 🔗 ⏮️ `yield` - -/// warning - -👉, 🌅 ⚖️ 🌘, "🏧" 💭. - -🚥 👆 ▶️ ⏮️ **FastAPI** 👆 💪 💚 🚶 ⚫️ 🔜. - -/// - -🐍, 👆 💪 ✍ 🔑 👨‍💼 🏗 🎓 ⏮️ 2️⃣ 👩‍🔬: `__enter__()` & `__exit__()`. - -👆 💪 ⚙️ 👫 🔘 **FastAPI** 🔗 ⏮️ `yield` ⚙️ -`with` ⚖️ `async with` 📄 🔘 🔗 🔢: - -{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *} - -/// tip - -➕1️⃣ 🌌 ✍ 🔑 👨‍💼 ⏮️: - -* `@contextlib.contextmanager` ⚖️ -* `@contextlib.asynccontextmanager` - -⚙️ 👫 🎀 🔢 ⏮️ 👁 `yield`. - -👈 ⚫️❔ **FastAPI** ⚙️ 🔘 🔗 ⏮️ `yield`. - -✋️ 👆 🚫 ✔️ ⚙️ 👨‍🎨 FastAPI 🔗 (& 👆 🚫🔜 🚫). - -FastAPI 🔜 ⚫️ 👆 🔘. - -/// diff --git a/docs/em/docs/tutorial/dependencies/global-dependencies.md b/docs/em/docs/tutorial/dependencies/global-dependencies.md deleted file mode 100644 index 5a22e5f1c8..0000000000 --- a/docs/em/docs/tutorial/dependencies/global-dependencies.md +++ /dev/null @@ -1,15 +0,0 @@ -# 🌐 🔗 - -🆎 🈸 👆 💪 💚 🚮 🔗 🎂 🈸. - -🎏 🌌 👆 💪 [🚮 `dependencies` *➡ 🛠️ 👨‍🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank}, 👆 💪 🚮 👫 `FastAPI` 🈸. - -👈 💼, 👫 🔜 ✔ 🌐 *➡ 🛠️* 🈸: - -{* ../../docs_src/dependencies/tutorial012.py hl[15] *} - -& 🌐 💭 📄 🔃 [❎ `dependencies` *➡ 🛠️ 👨‍🎨*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} ✔, ✋️ 👉 💼, 🌐 *➡ 🛠️* 📱. - -## 🔗 👪 *➡ 🛠️* - -⏪, 🕐❔ 👂 🔃 ❔ 📊 🦏 🈸 ([🦏 🈸 - 💗 📁](../../tutorial/bigger-applications.md){.internal-link target=_blank}), 🎲 ⏮️ 💗 📁, 👆 🔜 💡 ❔ 📣 👁 `dependencies` 🔢 👪 *➡ 🛠️*. diff --git a/docs/em/docs/tutorial/dependencies/index.md b/docs/em/docs/tutorial/dependencies/index.md deleted file mode 100644 index ce87d9ee48..0000000000 --- a/docs/em/docs/tutorial/dependencies/index.md +++ /dev/null @@ -1,212 +0,0 @@ -# 🔗 - -**FastAPI** ✔️ 📶 🏋️ ✋️ 🏋️ **🔗 💉** ⚙️. - -⚫️ 🏗 📶 🙅 ⚙️, & ⚒ ⚫️ 📶 ⏩ 🙆 👩‍💻 🛠️ 🎏 🦲 ⏮️ **FastAPI**. - -## ⚫️❔ "🔗 💉" - -**"🔗 💉"** ⛓, 📋, 👈 📤 🌌 👆 📟 (👉 💼, 👆 *➡ 🛠️ 🔢*) 📣 👜 👈 ⚫️ 🚚 👷 & ⚙️: "🔗". - -& ⤴️, 👈 ⚙️ (👉 💼 **FastAPI**) 🔜 ✊ 💅 🔨 ⚫️❔ 💪 🚚 👆 📟 ⏮️ 📚 💪 🔗 ("💉" 🔗). - -👉 📶 ⚠ 🕐❔ 👆 💪: - -* ✔️ 💰 ⚛ (🎏 📟 ⚛ 🔄 & 🔄). -* 💰 💽 🔗. -* 🛠️ 💂‍♂, 🤝, 🔑 📄, ♒️. -* & 📚 🎏 👜... - -🌐 👫, ⏪ 📉 📟 🔁. - -## 🥇 🔁 - -➡️ 👀 📶 🙅 🖼. ⚫️ 🔜 🙅 👈 ⚫️ 🚫 📶 ⚠, 🔜. - -✋️ 👉 🌌 👥 💪 🎯 🔛 ❔ **🔗 💉** ⚙️ 👷. - -### ✍ 🔗, ⚖️ "☑" - -➡️ 🥇 🎯 🔛 🔗. - -⚫️ 🔢 👈 💪 ✊ 🌐 🎏 🔢 👈 *➡ 🛠️ 🔢* 💪 ✊: - -{* ../../docs_src/dependencies/tutorial001.py hl[8:11] *} - -👈 ⚫️. - -**2️⃣ ⏸**. - -& ⚫️ ✔️ 🎏 💠 & 📊 👈 🌐 👆 *➡ 🛠️ 🔢* ✔️. - -👆 💪 💭 ⚫️ *➡ 🛠️ 🔢* 🍵 "👨‍🎨" (🍵 `@app.get("/some-path")`). - -& ⚫️ 💪 📨 🕳 👆 💚. - -👉 💼, 👉 🔗 ⌛: - -* 📦 🔢 🔢 `q` 👈 `str`. -* 📦 🔢 🔢 `skip` 👈 `int`, & 🔢 `0`. -* 📦 🔢 🔢 `limit` 👈 `int`, & 🔢 `100`. - -& ⤴️ ⚫️ 📨 `dict` ⚗ 📚 💲. - -### 🗄 `Depends` - -{* ../../docs_src/dependencies/tutorial001.py hl[3] *} - -### 📣 🔗, "⚓️" - -🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️. ⏮️ 👆 *➡ 🛠️ 🔢* 🔢, ⚙️ `Depends` ⏮️ 🆕 🔢: - -{* ../../docs_src/dependencies/tutorial001.py hl[15,20] *} - -👐 👆 ⚙️ `Depends` 🔢 👆 🔢 🎏 🌌 👆 ⚙️ `Body`, `Query`, ♒️, `Depends` 👷 👄 🎏. - -👆 🕴 🤝 `Depends` 👁 🔢. - -👉 🔢 🔜 🕳 💖 🔢. - -& 👈 🔢 ✊ 🔢 🎏 🌌 👈 *➡ 🛠️ 🔢* . - -/// tip - -👆 🔜 👀 ⚫️❔ 🎏 "👜", ↖️ ⚪️➡️ 🔢, 💪 ⚙️ 🔗 ⏭ 📃. - -/// - -🕐❔ 🆕 📨 🛬, **FastAPI** 🔜 ✊ 💅: - -* 🤙 👆 🔗 ("☑") 🔢 ⏮️ ☑ 🔢. -* 🤚 🏁 ⚪️➡️ 👆 🔢. -* 🛠️ 👈 🏁 🔢 👆 *➡ 🛠️ 🔢*. - -```mermaid -graph TB - -common_parameters(["common_parameters"]) -read_items["/items/"] -read_users["/users/"] - -common_parameters --> read_items -common_parameters --> read_users -``` - -👉 🌌 👆 ✍ 🔗 📟 🕐 & **FastAPI** ✊ 💅 🤙 ⚫️ 👆 *➡ 🛠️*. - -/// check - -👀 👈 👆 🚫 ✔️ ✍ 🎁 🎓 & 🚶‍♀️ ⚫️ 👱 **FastAPI** "®" ⚫️ ⚖️ 🕳 🎏. - -👆 🚶‍♀️ ⚫️ `Depends` & **FastAPI** 💭 ❔ 🎂. - -/// - -## `async` ⚖️ 🚫 `async` - -🔗 🔜 🤙 **FastAPI** (🎏 👆 *➡ 🛠️ 🔢*), 🎏 🚫 ✔ ⏪ 🔬 👆 🔢. - -👆 💪 ⚙️ `async def` ⚖️ 😐 `def`. - -& 👆 💪 📣 🔗 ⏮️ `async def` 🔘 😐 `def` *➡ 🛠️ 🔢*, ⚖️ `def` 🔗 🔘 `async def` *➡ 🛠️ 🔢*, ♒️. - -⚫️ 🚫 🤔. **FastAPI** 🔜 💭 ⚫️❔. - -/// note - -🚥 👆 🚫 💭, ✅ [🔁: *"🏃 ❓" *](../../async.md){.internal-link target=_blank} 📄 🔃 `async` & `await` 🩺. - -/// - -## 🛠️ ⏮️ 🗄 - -🌐 📨 📄, 🔬 & 📄 👆 🔗 (& 🎧-🔗) 🔜 🛠️ 🎏 🗄 🔗. - -, 🎓 🩺 🔜 ✔️ 🌐 ℹ ⚪️➡️ 👫 🔗 💁‍♂️: - - - -## 🙅 ⚙️ - -🚥 👆 👀 ⚫️, *➡ 🛠️ 🔢* 📣 ⚙️ 🕐❔ *➡* & *🛠️* 🏏, & ⤴️ **FastAPI** ✊ 💅 🤙 🔢 ⏮️ ☑ 🔢, ❎ 📊 ⚪️➡️ 📨. - -🤙, 🌐 (⚖️ 🏆) 🕸 🛠️ 👷 👉 🎏 🌌. - -👆 🙅 🤙 👈 🔢 🔗. 👫 🤙 👆 🛠️ (👉 💼, **FastAPI**). - -⏮️ 🔗 💉 ⚙️, 👆 💪 💬 **FastAPI** 👈 👆 *➡ 🛠️ 🔢* "🪀" 🔛 🕳 🙆 👈 🔜 🛠️ ⏭ 👆 *➡ 🛠️ 🔢*, & **FastAPI** 🔜 ✊ 💅 🛠️ ⚫️ & "💉" 🏁. - -🎏 ⚠ ⚖ 👉 🎏 💭 "🔗 💉": - -* ℹ -* 🐕‍🦺 -* 🐕‍🦺 -* 💉 -* 🦲 - -## **FastAPI** 🔌-🔌 - -🛠️ & "🔌-"Ⓜ 💪 🏗 ⚙️ **🔗 💉** ⚙️. ✋️ 👐, 📤 🤙 **🙅‍♂ 💪 ✍ "🔌-🔌"**, ⚙️ 🔗 ⚫️ 💪 📣 ♾ 🔢 🛠️ & 🔗 👈 ▶️️ 💪 👆 *➡ 🛠️ 🔢*. - -& 🔗 💪 ✍ 📶 🙅 & 🏋️ 🌌 👈 ✔ 👆 🗄 🐍 📦 👆 💪, & 🛠️ 👫 ⏮️ 👆 🛠️ 🔢 👩‍❤‍👨 ⏸ 📟, *🌖*. - -👆 🔜 👀 🖼 👉 ⏭ 📃, 🔃 🔗 & ☁ 💽, 💂‍♂, ♒️. - -## **FastAPI** 🔗 - -🦁 🔗 💉 ⚙️ ⚒ **FastAPI** 🔗 ⏮️: - -* 🌐 🔗 💽 -* ☁ 💽 -* 🔢 📦 -* 🔢 🔗 -* 🤝 & ✔ ⚙️ -* 🛠️ ⚙️ ⚖ ⚙️ -* 📨 💽 💉 ⚙️ -* ♒️. - -## 🙅 & 🏋️ - -👐 🔗 🔗 💉 ⚙️ 📶 🙅 🔬 & ⚙️, ⚫️ 📶 🏋️. - -👆 💪 🔬 🔗 👈 🔄 💪 🔬 🔗 👫. - -🔚, 🔗 🌲 🔗 🏗, & **🔗 💉** ⚙️ ✊ 💅 🔬 🌐 👉 🔗 👆 (& 👫 🎧-🔗) & 🚚 (💉) 🏁 🔠 🔁. - -🖼, ➡️ 💬 👆 ✔️ 4️⃣ 🛠️ 🔗 (*➡ 🛠️*): - -* `/items/public/` -* `/items/private/` -* `/users/{user_id}/activate` -* `/items/pro/` - -⤴️ 👆 💪 🚮 🎏 ✔ 📄 🔠 👫 ⏮️ 🔗 & 🎧-🔗: - -```mermaid -graph TB - -current_user(["current_user"]) -active_user(["active_user"]) -admin_user(["admin_user"]) -paying_user(["paying_user"]) - -public["/items/public/"] -private["/items/private/"] -activate_user["/users/{user_id}/activate"] -pro_items["/items/pro/"] - -current_user --> active_user -active_user --> admin_user -active_user --> paying_user - -current_user --> public -active_user --> private -admin_user --> activate_user -paying_user --> pro_items -``` - -## 🛠️ ⏮️ **🗄** - -🌐 👫 🔗, ⏪ 📣 👫 📄, 🚮 🔢, 🔬, ♒️. 👆 *➡ 🛠️*. - -**FastAPI** 🔜 ✊ 💅 🚮 ⚫️ 🌐 🗄 🔗, 👈 ⚫️ 🎦 🎓 🧾 ⚙️. diff --git a/docs/em/docs/tutorial/dependencies/sub-dependencies.md b/docs/em/docs/tutorial/dependencies/sub-dependencies.md deleted file mode 100644 index 6d622e952f..0000000000 --- a/docs/em/docs/tutorial/dependencies/sub-dependencies.md +++ /dev/null @@ -1,86 +0,0 @@ -# 🎧-🔗 - -👆 💪 ✍ 🔗 👈 ✔️ **🎧-🔗**. - -👫 💪 **⏬** 👆 💪 👫. - -**FastAPI** 🔜 ✊ 💅 🔬 👫. - -## 🥇 🔗 "☑" - -👆 💪 ✍ 🥇 🔗 ("☑") 💖: - -{* ../../docs_src/dependencies/tutorial005.py hl[8:9] *} - -⚫️ 📣 📦 🔢 🔢 `q` `str`, & ⤴️ ⚫️ 📨 ⚫️. - -👉 🙅 (🚫 📶 ⚠), ✋️ 🔜 ℹ 👥 🎯 🔛 ❔ 🎧-🔗 👷. - -## 🥈 🔗, "☑" & "⚓️" - -⤴️ 👆 💪 ✍ ➕1️⃣ 🔗 🔢 ("☑") 👈 🎏 🕰 📣 🔗 🚮 👍 (⚫️ "⚓️" 💁‍♂️): - -{* ../../docs_src/dependencies/tutorial005.py hl[13] *} - -➡️ 🎯 🔛 🔢 📣: - -* ✋️ 👉 🔢 🔗 ("☑") ⚫️, ⚫️ 📣 ➕1️⃣ 🔗 (⚫️ "🪀" 🔛 🕳 🙆). - * ⚫️ 🪀 🔛 `query_extractor`, & 🛠️ 💲 📨 ⚫️ 🔢 `q`. -* ⚫️ 📣 📦 `last_query` 🍪, `str`. - * 🚥 👩‍💻 🚫 🚚 🙆 🔢 `q`, 👥 ⚙️ 🏁 🔢 ⚙️, ❔ 👥 🖊 🍪 ⏭. - -## ⚙️ 🔗 - -⤴️ 👥 💪 ⚙️ 🔗 ⏮️: - -{* ../../docs_src/dependencies/tutorial005.py hl[22] *} - -/// info - -👀 👈 👥 🕴 📣 1️⃣ 🔗 *➡ 🛠️ 🔢*, `query_or_cookie_extractor`. - -✋️ **FastAPI** 🔜 💭 👈 ⚫️ ✔️ ❎ `query_extractor` 🥇, 🚶‍♀️ 🏁 👈 `query_or_cookie_extractor` ⏪ 🤙 ⚫️. - -/// - -```mermaid -graph TB - -query_extractor(["query_extractor"]) -query_or_cookie_extractor(["query_or_cookie_extractor"]) - -read_query["/items/"] - -query_extractor --> query_or_cookie_extractor --> read_query -``` - -## ⚙️ 🎏 🔗 💗 🕰 - -🚥 1️⃣ 👆 🔗 📣 💗 🕰 🎏 *➡ 🛠️*, 🖼, 💗 🔗 ✔️ ⚠ 🎧-🔗, **FastAPI** 🔜 💭 🤙 👈 🎧-🔗 🕴 🕐 📍 📨. - -& ⚫️ 🔜 🖊 📨 💲 "💾" & 🚶‍♀️ ⚫️ 🌐 "⚓️" 👈 💪 ⚫️ 👈 🎯 📨, ↩️ 🤙 🔗 💗 🕰 🎏 📨. - -🏧 😐 🌐❔ 👆 💭 👆 💪 🔗 🤙 🔠 🔁 (🎲 💗 🕰) 🎏 📨 ↩️ ⚙️ "💾" 💲, 👆 💪 ⚒ 🔢 `use_cache=False` 🕐❔ ⚙️ `Depends`: - -```Python hl_lines="1" -async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)): - return {"fresh_value": fresh_value} -``` - -## 🌃 - -↖️ ⚪️➡️ 🌐 🎀 🔤 ⚙️ 📥, **🔗 💉** ⚙️ 🙅. - -🔢 👈 👀 🎏 *➡ 🛠️ 🔢*. - -✋️, ⚫️ 📶 🏋️, & ✔ 👆 📣 🎲 🙇 🐦 🔗 "📊" (🌲). - -/// tip - -🌐 👉 💪 🚫 😑 ⚠ ⏮️ 👫 🙅 🖼. - -✋️ 👆 🔜 👀 ❔ ⚠ ⚫️ 📃 🔃 **💂‍♂**. - - & 👆 🔜 👀 💸 📟 ⚫️ 🔜 🖊 👆. - -/// diff --git a/docs/em/docs/tutorial/encoder.md b/docs/em/docs/tutorial/encoder.md deleted file mode 100644 index ad05f701e2..0000000000 --- a/docs/em/docs/tutorial/encoder.md +++ /dev/null @@ -1,35 +0,0 @@ -# 🎻 🔗 🔢 - -📤 💼 🌐❔ 👆 5️⃣📆 💪 🗜 💽 🆎 (💖 Pydantic 🏷) 🕳 🔗 ⏮️ 🎻 (💖 `dict`, `list`, ♒️). - -🖼, 🚥 👆 💪 🏪 ⚫️ 💽. - -👈, **FastAPI** 🚚 `jsonable_encoder()` 🔢. - -## ⚙️ `jsonable_encoder` - -➡️ 🌈 👈 👆 ✔️ 💽 `fake_db` 👈 🕴 📨 🎻 🔗 💽. - -🖼, ⚫️ 🚫 📨 `datetime` 🎚, 👈 🚫 🔗 ⏮️ 🎻. - -, `datetime` 🎚 🔜 ✔️ 🗜 `str` ⚗ 💽 💾 📁. - -🎏 🌌, 👉 💽 🚫🔜 📨 Pydantic 🏷 (🎚 ⏮️ 🔢), 🕴 `dict`. - -👆 💪 ⚙️ `jsonable_encoder` 👈. - -⚫️ 📨 🎚, 💖 Pydantic 🏷, & 📨 🎻 🔗 ⏬: - -{* ../../docs_src/encoder/tutorial001.py hl[5,22] *} - -👉 🖼, ⚫️ 🔜 🗜 Pydantic 🏷 `dict`, & `datetime` `str`. - -🏁 🤙 ⚫️ 🕳 👈 💪 🗜 ⏮️ 🐍 🐩 `json.dumps()`. - -⚫️ 🚫 📨 ⭕ `str` ⚗ 💽 🎻 📁 (🎻). ⚫️ 📨 🐍 🐩 💽 📊 (✅ `dict`) ⏮️ 💲 & 🎧-💲 👈 🌐 🔗 ⏮️ 🎻. - -/// note - -`jsonable_encoder` 🤙 ⚙️ **FastAPI** 🔘 🗜 💽. ✋️ ⚫️ ⚠ 📚 🎏 😐. - -/// diff --git a/docs/em/docs/tutorial/extra-data-types.md b/docs/em/docs/tutorial/extra-data-types.md deleted file mode 100644 index f15a74b4a3..0000000000 --- a/docs/em/docs/tutorial/extra-data-types.md +++ /dev/null @@ -1,62 +0,0 @@ -# ➕ 💽 🆎 - -🆙 🔜, 👆 ✔️ ⚙️ ⚠ 📊 🆎, 💖: - -* `int` -* `float` -* `str` -* `bool` - -✋️ 👆 💪 ⚙️ 🌅 🏗 📊 🆎. - -& 👆 🔜 ✔️ 🎏 ⚒ 👀 🆙 🔜: - -* 👑 👨‍🎨 🐕‍🦺. -* 💽 🛠️ ⚪️➡️ 📨 📨. -* 💽 🛠️ 📨 💽. -* 💽 🔬. -* 🏧 ✍ & 🧾. - -## 🎏 💽 🆎 - -📥 🌖 📊 🆎 👆 💪 ⚙️: - -* `UUID`: - * 🐩 "⭐ 😍 🆔", ⚠ 🆔 📚 💽 & ⚙️. - * 📨 & 📨 🔜 🎨 `str`. -* `datetime.datetime`: - * 🐍 `datetime.datetime`. - * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `2008-09-15T15:53:00+05:00`. -* `datetime.date`: - * 🐍 `datetime.date`. - * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `2008-09-15`. -* `datetime.time`: - * 🐍 `datetime.time`. - * 📨 & 📨 🔜 🎨 `str` 💾 8️⃣6️⃣0️⃣1️⃣ 📁, 💖: `14:23:55.003`. -* `datetime.timedelta`: - * 🐍 `datetime.timedelta`. - * 📨 & 📨 🔜 🎨 `float` 🌐 🥈. - * Pydantic ✔ 🎦 ⚫️ "💾 8️⃣6️⃣0️⃣1️⃣ 🕰 ➕ 🔢", 👀 🩺 🌅 ℹ. -* `frozenset`: - * 📨 & 📨, 😥 🎏 `set`: - * 📨, 📇 🔜 ✍, ❎ ❎ & 🏭 ⚫️ `set`. - * 📨, `set` 🔜 🗜 `list`. - * 🏗 🔗 🔜 ✔ 👈 `set` 💲 😍 (⚙️ 🎻 🔗 `uniqueItems`). -* `bytes`: - * 🐩 🐍 `bytes`. - * 📨 & 📨 🔜 😥 `str`. - * 🏗 🔗 🔜 ✔ 👈 ⚫️ `str` ⏮️ `binary` "📁". -* `Decimal`: - * 🐩 🐍 `Decimal`. - * 📨 & 📨, 🍵 🎏 `float`. -* 👆 💪 ✅ 🌐 ☑ Pydantic 📊 🆎 📥: Pydantic 📊 🆎. - -## 🖼 - -📥 🖼 *➡ 🛠️* ⏮️ 🔢 ⚙️ 🔛 🆎. - -{* ../../docs_src/extra_data_types/tutorial001.py hl[1,3,12:16] *} - -🗒 👈 🔢 🔘 🔢 ✔️ 👫 🐠 💽 🆎, & 👆 💪, 🖼, 🎭 😐 📅 🎭, 💖: - -{* ../../docs_src/extra_data_types/tutorial001.py hl[18:19] *} diff --git a/docs/em/docs/tutorial/extra-models.md b/docs/em/docs/tutorial/extra-models.md deleted file mode 100644 index 19ab5b7986..0000000000 --- a/docs/em/docs/tutorial/extra-models.md +++ /dev/null @@ -1,211 +0,0 @@ -# ➕ 🏷 - -▶️ ⏮️ ⏮️ 🖼, ⚫️ 🔜 ⚠ ✔️ 🌅 🌘 1️⃣ 🔗 🏷. - -👉 ✴️ 💼 👩‍💻 🏷, ↩️: - -* **🔢 🏷** 💪 💪 ✔️ 🔐. -* **🔢 🏷** 🔜 🚫 ✔️ 🔐. -* **💽 🏷** 🔜 🎲 💪 ✔️ #️⃣ 🔐. - -/// danger - -🙅 🏪 👩‍💻 🔢 🔐. 🕧 🏪 "🔐 #️⃣" 👈 👆 💪 ⤴️ ✔. - -🚥 👆 🚫 💭, 👆 🔜 💡 ⚫️❔ "🔐#️⃣" [💂‍♂ 📃](security/simple-oauth2.md#_4){.internal-link target=_blank}. - -/// - -## 💗 🏷 - -📥 🏢 💭 ❔ 🏷 💪 👀 💖 ⏮️ 👫 🔐 🏑 & 🥉 🌐❔ 👫 ⚙️: - -{* ../../docs_src/extra_models/tutorial001.py hl[9,11,16,22,24,29:30,33:35,40:41] *} - -### 🔃 `**user_in.dict()` - -#### Pydantic `.dict()` - -`user_in` Pydantic 🏷 🎓 `UserIn`. - -Pydantic 🏷 ✔️ `.dict()` 👩‍🔬 👈 📨 `dict` ⏮️ 🏷 💽. - -, 🚥 👥 ✍ Pydantic 🎚 `user_in` 💖: - -```Python -user_in = UserIn(username="john", password="secret", email="john.doe@example.com") -``` - -& ⤴️ 👥 🤙: - -```Python -user_dict = user_in.dict() -``` - -👥 🔜 ✔️ `dict` ⏮️ 💽 🔢 `user_dict` (⚫️ `dict` ↩️ Pydantic 🏷 🎚). - -& 🚥 👥 🤙: - -```Python -print(user_dict) -``` - -👥 🔜 🤚 🐍 `dict` ⏮️: - -```Python -{ - 'username': 'john', - 'password': 'secret', - 'email': 'john.doe@example.com', - 'full_name': None, -} -``` - -#### 🎁 `dict` - -🚥 👥 ✊ `dict` 💖 `user_dict` & 🚶‍♀️ ⚫️ 🔢 (⚖️ 🎓) ⏮️ `**user_dict`, 🐍 🔜 "🎁" ⚫️. ⚫️ 🔜 🚶‍♀️ 🔑 & 💲 `user_dict` 🔗 🔑-💲 ❌. - -, ▶️ ⏮️ `user_dict` ⚪️➡️ 🔛, ✍: - -```Python -UserInDB(**user_dict) -``` - -🔜 🏁 🕳 🌓: - -```Python -UserInDB( - username="john", - password="secret", - email="john.doe@example.com", - full_name=None, -) -``` - -⚖️ 🌅 ⚫️❔, ⚙️ `user_dict` 🔗, ⏮️ ⚫️❔ 🎚 ⚫️ 💪 ✔️ 🔮: - -```Python -UserInDB( - username = user_dict["username"], - password = user_dict["password"], - email = user_dict["email"], - full_name = user_dict["full_name"], -) -``` - -#### Pydantic 🏷 ⚪️➡️ 🎚 ➕1️⃣ - -🖼 🔛 👥 🤚 `user_dict` ⚪️➡️ `user_in.dict()`, 👉 📟: - -```Python -user_dict = user_in.dict() -UserInDB(**user_dict) -``` - -🔜 🌓: - -```Python -UserInDB(**user_in.dict()) -``` - -...↩️ `user_in.dict()` `dict`, & ⤴️ 👥 ⚒ 🐍 "🎁" ⚫️ 🚶‍♀️ ⚫️ `UserInDB` 🔠 ⏮️ `**`. - -, 👥 🤚 Pydantic 🏷 ⚪️➡️ 💽 ➕1️⃣ Pydantic 🏷. - -#### 🎁 `dict` & ➕ 🇨🇻 - -& ⤴️ ❎ ➕ 🇨🇻 ❌ `hashed_password=hashed_password`, 💖: - -```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) -``` - -...🔚 🆙 💆‍♂ 💖: - -```Python -UserInDB( - username = user_dict["username"], - password = user_dict["password"], - email = user_dict["email"], - full_name = user_dict["full_name"], - hashed_password = hashed_password, -) -``` - -/// warning - -🔗 🌖 🔢 🤖 💪 💧 💽, ✋️ 👫 ↗️ 🚫 🚚 🙆 🎰 💂‍♂. - -/// - -## 📉 ❎ - -📉 📟 ❎ 1️⃣ 🐚 💭 **FastAPI**. - -📟 ❎ 📈 🤞 🐛, 💂‍♂ ❔, 📟 🔁 ❔ (🕐❔ 👆 ℹ 1️⃣ 🥉 ✋️ 🚫 🎏), ♒️. - -& 👉 🏷 🌐 🤝 📚 💽 & ❎ 🔢 📛 & 🆎. - -👥 💪 👻. - -👥 💪 📣 `UserBase` 🏷 👈 🍦 🧢 👆 🎏 🏷. & ⤴️ 👥 💪 ⚒ 🏿 👈 🏷 👈 😖 🚮 🔢 (🆎 📄, 🔬, ♒️). - -🌐 💽 🛠️, 🔬, 🧾, ♒️. 🔜 👷 🛎. - -👈 🌌, 👥 💪 📣 🔺 🖖 🏷 (⏮️ 🔢 `password`, ⏮️ `hashed_password` & 🍵 🔐): - -{* ../../docs_src/extra_models/tutorial002.py hl[9,15:16,19:20,23:24] *} - -## `Union` ⚖️ `anyOf` - -👆 💪 📣 📨 `Union` 2️⃣ 🆎, 👈 ⛓, 👈 📨 🔜 🙆 2️⃣. - -⚫️ 🔜 🔬 🗄 ⏮️ `anyOf`. - -👈, ⚙️ 🐩 🐍 🆎 🔑 `typing.Union`: - -/// note - -🕐❔ ⚖ `Union`, 🔌 🏆 🎯 🆎 🥇, ⏩ 🌘 🎯 🆎. 🖼 🔛, 🌖 🎯 `PlaneItem` 👟 ⏭ `CarItem` `Union[PlaneItem, CarItem]`. - -/// - -{* ../../docs_src/extra_models/tutorial003.py hl[1,14:15,18:20,33] *} - -### `Union` 🐍 3️⃣.1️⃣0️⃣ - -👉 🖼 👥 🚶‍♀️ `Union[PlaneItem, CarItem]` 💲 ❌ `response_model`. - -↩️ 👥 🚶‍♀️ ⚫️ **💲 ❌** ↩️ 🚮 ⚫️ **🆎 ✍**, 👥 ✔️ ⚙️ `Union` 🐍 3️⃣.1️⃣0️⃣. - -🚥 ⚫️ 🆎 ✍ 👥 💪 ✔️ ⚙️ ⏸ ⏸,: - -```Python -some_variable: PlaneItem | CarItem -``` - -✋️ 🚥 👥 🚮 👈 `response_model=PlaneItem | CarItem` 👥 🔜 🤚 ❌, ↩️ 🐍 🔜 🔄 🎭 **❌ 🛠️** 🖖 `PlaneItem` & `CarItem` ↩️ 🔬 👈 🆎 ✍. - -## 📇 🏷 - -🎏 🌌, 👆 💪 📣 📨 📇 🎚. - -👈, ⚙️ 🐩 🐍 `typing.List` (⚖️ `list` 🐍 3️⃣.9️⃣ & 🔛): - -{* ../../docs_src/extra_models/tutorial004.py hl[1,20] *} - -## 📨 ⏮️ ❌ `dict` - -👆 💪 📣 📨 ⚙️ ✅ ❌ `dict`, 📣 🆎 🔑 & 💲, 🍵 ⚙️ Pydantic 🏷. - -👉 ⚠ 🚥 👆 🚫 💭 ☑ 🏑/🔢 📛 (👈 🔜 💪 Pydantic 🏷) ⏪. - -👉 💼, 👆 💪 ⚙️ `typing.Dict` (⚖️ `dict` 🐍 3️⃣.9️⃣ & 🔛): - -{* ../../docs_src/extra_models/tutorial005.py hl[1,8] *} - -## 🌃 - -⚙️ 💗 Pydantic 🏷 & 😖 ➡ 🔠 💼. - -👆 🚫 💪 ✔️ 👁 💽 🏷 📍 👨‍💼 🚥 👈 👨‍💼 🔜 💪 ✔️ 🎏 "🇵🇸". 💼 ⏮️ 👩‍💻 "👨‍💼" ⏮️ 🇵🇸 ✅ `password`, `password_hash` & 🙅‍♂ 🔐. diff --git a/docs/em/docs/tutorial/first-steps.md b/docs/em/docs/tutorial/first-steps.md deleted file mode 100644 index f9bb3fb756..0000000000 --- a/docs/em/docs/tutorial/first-steps.md +++ /dev/null @@ -1,335 +0,0 @@ -# 🥇 🔁 - -🙅 FastAPI 📁 💪 👀 💖 👉: - -{* ../../docs_src/first_steps/tutorial001.py *} - -📁 👈 📁 `main.py`. - -🏃 🖖 💽: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -/// note - -📋 `uvicorn main:app` 🔗: - -* `main`: 📁 `main.py` (🐍 "🕹"). -* `app`: 🎚 ✍ 🔘 `main.py` ⏮️ ⏸ `app = FastAPI()`. -* `--reload`: ⚒ 💽 ⏏ ⏮️ 📟 🔀. 🕴 ⚙️ 🛠️. - -/// - -🔢, 📤 ⏸ ⏮️ 🕳 💖: - -```hl_lines="4" -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -👈 ⏸ 🎦 📛 🌐❔ 👆 📱 ➖ 🍦, 👆 🇧🇿 🎰. - -### ✅ ⚫️ - -📂 👆 🖥 http://127.0.0.1:8000. - -👆 🔜 👀 🎻 📨: - -```JSON -{"message": "Hello World"} -``` - -### 🎓 🛠️ 🩺 - -🔜 🚶 http://127.0.0.1:8000/docs. - -👆 🔜 👀 🏧 🎓 🛠️ 🧾 (🚚 🦁 🎚): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### 🎛 🛠️ 🩺 - -& 🔜, 🚶 http://127.0.0.1:8000/redoc. - -👆 🔜 👀 🎛 🏧 🧾 (🚚 📄): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -### 🗄 - -**FastAPI** 🏗 "🔗" ⏮️ 🌐 👆 🛠️ ⚙️ **🗄** 🐩 ⚖ 🔗. - -#### "🔗" - -"🔗" 🔑 ⚖️ 📛 🕳. 🚫 📟 👈 🛠️ ⚫️, ✋️ 📝 📛. - -#### 🛠️ "🔗" - -👉 💼, 🗄 🔧 👈 🤔 ❔ 🔬 🔗 👆 🛠️. - -👉 🔗 🔑 🔌 👆 🛠️ ➡, 💪 🔢 👫 ✊, ♒️. - -#### 💽 "🔗" - -⚖ "🔗" 💪 🔗 💠 💽, 💖 🎻 🎚. - -👈 💼, ⚫️ 🔜 ⛓ 🎻 🔢, & 📊 🆎 👫 ✔️, ♒️. - -#### 🗄 & 🎻 🔗 - -🗄 🔬 🛠️ 🔗 👆 🛠️. & 👈 🔗 🔌 🔑 (⚖️ "🔗") 📊 📨 & 📨 👆 🛠️ ⚙️ **🎻 🔗**, 🐩 🎻 📊 🔗. - -#### ✅ `openapi.json` - -🚥 👆 😟 🔃 ❔ 🍣 🗄 🔗 👀 💖, FastAPI 🔁 🏗 🎻 (🔗) ⏮️ 📛 🌐 👆 🛠️. - -👆 💪 👀 ⚫️ 🔗: http://127.0.0.1:8000/openapi.json. - -⚫️ 🔜 🎦 🎻 ▶️ ⏮️ 🕳 💖: - -```JSON -{ - "openapi": "3.0.2", - "info": { - "title": "FastAPI", - "version": "0.1.0" - }, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - - - -... -``` - -#### ⚫️❔ 🗄 - -🗄 🔗 ⚫️❔ 🏋️ 2️⃣ 🎓 🧾 ⚙️ 🔌. - -& 📤 💯 🎛, 🌐 ⚓️ 🔛 🗄. 👆 💪 💪 🚮 🙆 📚 🎛 👆 🈸 🏗 ⏮️ **FastAPI**. - -👆 💪 ⚙️ ⚫️ 🏗 📟 🔁, 👩‍💻 👈 🔗 ⏮️ 👆 🛠️. 🖼, 🕸, 📱 ⚖️ ☁ 🈸. - -## 🌃, 🔁 🔁 - -### 🔁 1️⃣: 🗄 `FastAPI` - -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} - -`FastAPI` 🐍 🎓 👈 🚚 🌐 🛠️ 👆 🛠️. - -/// note | 📡 ℹ - -`FastAPI` 🎓 👈 😖 🔗 ⚪️➡️ `Starlette`. - -👆 💪 ⚙️ 🌐 💃 🛠️ ⏮️ `FastAPI` 💁‍♂️. - -/// - -### 🔁 2️⃣: ✍ `FastAPI` "👐" - -{* ../../docs_src/first_steps/tutorial001.py hl[3] *} - -📥 `app` 🔢 🔜 "👐" 🎓 `FastAPI`. - -👉 🔜 👑 ☝ 🔗 ✍ 🌐 👆 🛠️. - -👉 `app` 🎏 1️⃣ 🔗 `uvicorn` 📋: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -🚥 👆 ✍ 👆 📱 💖: - -{* ../../docs_src/first_steps/tutorial002.py hl[3] *} - -& 🚮 ⚫️ 📁 `main.py`, ⤴️ 👆 🔜 🤙 `uvicorn` 💖: - -
- -```console -$ uvicorn main:my_awesome_api --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -### 🔁 3️⃣: ✍ *➡ 🛠️* - -#### ➡ - -"➡" 📥 🔗 🏁 🍕 📛 ▶️ ⚪️➡️ 🥇 `/`. - -, 📛 💖: - -``` -https://example.com/items/foo -``` - -...➡ 🔜: - -``` -/items/foo -``` - -/// info - -"➡" 🛎 🤙 "🔗" ⚖️ "🛣". - -/// - -⏪ 🏗 🛠️, "➡" 👑 🌌 🎏 "⚠" & "ℹ". - -#### 🛠️ - -"🛠️" 📥 🔗 1️⃣ 🇺🇸🔍 "👩‍🔬". - -1️⃣: - -* `POST` -* `GET` -* `PUT` -* `DELETE` - -...& 🌅 😍 🕐: - -* `OPTIONS` -* `HEAD` -* `PATCH` -* `TRACE` - -🇺🇸🔍 🛠️, 👆 💪 🔗 🔠 ➡ ⚙️ 1️⃣ (⚖️ 🌅) 👫 "👩‍🔬". - ---- - -🕐❔ 🏗 🔗, 👆 🛎 ⚙️ 👫 🎯 🇺🇸🔍 👩‍🔬 🎭 🎯 🎯. - -🛎 👆 ⚙️: - -* `POST`: ✍ 💽. -* `GET`: ✍ 💽. -* `PUT`: ℹ 💽. -* `DELETE`: ❎ 💽. - -, 🗄, 🔠 🇺🇸🔍 👩‍🔬 🤙 "🛠️". - -👥 🔜 🤙 👫 "**🛠️**" 💁‍♂️. - -#### 🔬 *➡ 🛠️ 👨‍🎨* - -{* ../../docs_src/first_steps/tutorial001.py hl[6] *} - -`@app.get("/")` 💬 **FastAPI** 👈 🔢 ▶️️ 🔛 🈚 🚚 📨 👈 🚶: - -* ➡ `/` -* ⚙️ get 🛠️ - -/// info | `@decorator` ℹ - -👈 `@something` ❕ 🐍 🤙 "👨‍🎨". - -👆 🚮 ⚫️ 🔛 🔝 🔢. 💖 📶 📔 👒 (👤 💭 👈 🌐❔ ⚖ 👟 ⚪️➡️). - - "👨‍🎨" ✊ 🔢 🔛 & 🔨 🕳 ⏮️ ⚫️. - -👆 💼, 👉 👨‍🎨 💬 **FastAPI** 👈 🔢 🔛 🔗 **➡** `/` ⏮️ **🛠️** `get`. - -⚫️ "**➡ 🛠️ 👨‍🎨**". - -/// - -👆 💪 ⚙️ 🎏 🛠️: - -* `@app.post()` -* `@app.put()` -* `@app.delete()` - -& 🌅 😍 🕐: - -* `@app.options()` -* `@app.head()` -* `@app.patch()` -* `@app.trace()` - -/// tip - -👆 🆓 ⚙️ 🔠 🛠️ (🇺🇸🔍 👩‍🔬) 👆 🎋. - -**FastAPI** 🚫 🛠️ 🙆 🎯 🔑. - -ℹ 📥 🎁 📄, 🚫 📄. - -🖼, 🕐❔ ⚙️ 🕹 👆 🛎 🎭 🌐 🎯 ⚙️ 🕴 `POST` 🛠️. - -/// - -### 🔁 4️⃣: 🔬 **➡ 🛠️ 🔢** - -👉 👆 "**➡ 🛠️ 🔢**": - -* **➡**: `/`. -* **🛠️**: `get`. -* **🔢**: 🔢 🔛 "👨‍🎨" (🔛 `@app.get("/")`). - -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} - -👉 🐍 🔢. - -⚫️ 🔜 🤙 **FastAPI** 🕐❔ ⚫️ 📨 📨 📛 "`/`" ⚙️ `GET` 🛠️. - -👉 💼, ⚫️ `async` 🔢. - ---- - -👆 💪 🔬 ⚫️ 😐 🔢 ↩️ `async def`: - -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} - -/// note - -🚥 👆 🚫 💭 🔺, ✅ [🔁: *"🏃 ❓"*](../async.md#_2){.internal-link target=_blank}. - -/// - -### 🔁 5️⃣: 📨 🎚 - -{* ../../docs_src/first_steps/tutorial001.py hl[8] *} - -👆 💪 📨 `dict`, `list`, ⭐ 💲 `str`, `int`, ♒️. - -👆 💪 📨 Pydantic 🏷 (👆 🔜 👀 🌅 🔃 👈 ⏪). - -📤 📚 🎏 🎚 & 🏷 👈 🔜 🔁 🗜 🎻 (🔌 🐜, ♒️). 🔄 ⚙️ 👆 💕 🕐, ⚫️ 🏆 🎲 👈 👫 ⏪ 🐕‍🦺. - -## 🌃 - -* 🗄 `FastAPI`. -* ✍ `app` 👐. -* ✍ **➡ 🛠️ 👨‍🎨** (💖 `@app.get("/")`). -* ✍ **➡ 🛠️ 🔢** (💖 `def root(): ...` 🔛). -* 🏃 🛠️ 💽 (💖 `uvicorn main:app --reload`). diff --git a/docs/em/docs/tutorial/handling-errors.md b/docs/em/docs/tutorial/handling-errors.md deleted file mode 100644 index 6d72775976..0000000000 --- a/docs/em/docs/tutorial/handling-errors.md +++ /dev/null @@ -1,257 +0,0 @@ -# 🚚 ❌ - -📤 📚 ⚠ 🌐❔ 👆 💪 🚨 ❌ 👩‍💻 👈 ⚙️ 👆 🛠️. - -👉 👩‍💻 💪 🖥 ⏮️ 🕸, 📟 ⚪️➡️ 👱 🙆, ☁ 📳, ♒️. - -👆 💪 💪 💬 👩‍💻 👈: - -* 👩‍💻 🚫 ✔️ 🥃 😌 👈 🛠️. -* 👩‍💻 🚫 ✔️ 🔐 👈 ℹ. -* 🏬 👩‍💻 🔄 🔐 🚫 🔀. -* ♒️. - -👫 💼, 👆 🔜 🛎 📨 **🇺🇸🔍 👔 📟** ↔ **4️⃣0️⃣0️⃣** (⚪️➡️ 4️⃣0️⃣0️⃣ 4️⃣9️⃣9️⃣). - -👉 🎏 2️⃣0️⃣0️⃣ 🇺🇸🔍 👔 📟 (⚪️➡️ 2️⃣0️⃣0️⃣ 2️⃣9️⃣9️⃣). 👈 "2️⃣0️⃣0️⃣" 👔 📟 ⛓ 👈 😫 📤 "🏆" 📨. - -👔 📟 4️⃣0️⃣0️⃣ ↔ ⛓ 👈 📤 ❌ ⚪️➡️ 👩‍💻. - -💭 🌐 👈 **"4️⃣0️⃣4️⃣ 🚫 🔎"** ❌ (& 🤣) ❓ - -## ⚙️ `HTTPException` - -📨 🇺🇸🔍 📨 ⏮️ ❌ 👩‍💻 👆 ⚙️ `HTTPException`. - -### 🗄 `HTTPException` - -{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} - -### 🤚 `HTTPException` 👆 📟 - -`HTTPException` 😐 🐍 ⚠ ⏮️ 🌖 📊 🔗 🔗. - -↩️ ⚫️ 🐍 ⚠, 👆 🚫 `return` ⚫️, 👆 `raise` ⚫️. - -👉 ⛓ 👈 🚥 👆 🔘 🚙 🔢 👈 👆 🤙 🔘 👆 *➡ 🛠️ 🔢*, & 👆 🤚 `HTTPException` ⚪️➡️ 🔘 👈 🚙 🔢, ⚫️ 🏆 🚫 🏃 🎂 📟 *➡ 🛠️ 🔢*, ⚫️ 🔜 ❎ 👈 📨 ▶️️ ↖️ & 📨 🇺🇸🔍 ❌ ⚪️➡️ `HTTPException` 👩‍💻. - -💰 🙋‍♀ ⚠ 🤭 `return`😅 💲 🔜 🌖 ⭐ 📄 🔃 🔗 & 💂‍♂. - -👉 🖼, 🕐❔ 👩‍💻 📨 🏬 🆔 👈 🚫 🔀, 🤚 ⚠ ⏮️ 👔 📟 `404`: - -{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} - -### 📉 📨 - -🚥 👩‍💻 📨 `http://example.com/items/foo` ( `item_id` `"foo"`), 👈 👩‍💻 🔜 📨 🇺🇸🔍 👔 📟 2️⃣0️⃣0️⃣, & 🎻 📨: - -```JSON -{ - "item": "The Foo Wrestlers" -} -``` - -✋️ 🚥 👩‍💻 📨 `http://example.com/items/bar` (🚫-🚫 `item_id` `"bar"`), 👈 👩‍💻 🔜 📨 🇺🇸🔍 👔 📟 4️⃣0️⃣4️⃣ ("🚫 🔎" ❌), & 🎻 📨: - -```JSON -{ - "detail": "Item not found" -} -``` - -/// tip - -🕐❔ 🙋‍♀ `HTTPException`, 👆 💪 🚶‍♀️ 🙆 💲 👈 💪 🗜 🎻 🔢 `detail`, 🚫 🕴 `str`. - -👆 💪 🚶‍♀️ `dict`, `list`, ♒️. - -👫 🍵 🔁 **FastAPI** & 🗜 🎻. - -/// - -## 🚮 🛃 🎚 - -📤 ⚠ 🌐❔ ⚫️ ⚠ 💪 🚮 🛃 🎚 🇺🇸🔍 ❌. 🖼, 🆎 💂‍♂. - -👆 🎲 🏆 🚫 💪 ⚙️ ⚫️ 🔗 👆 📟. - -✋️ 💼 👆 💪 ⚫️ 🏧 😐, 👆 💪 🚮 🛃 🎚: - -{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} - -## ❎ 🛃 ⚠ 🐕‍🦺 - -👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ 🎏 ⚠ 🚙 ⚪️➡️ 💃. - -➡️ 💬 👆 ✔️ 🛃 ⚠ `UnicornException` 👈 👆 (⚖️ 🗃 👆 ⚙️) 💪 `raise`. - -& 👆 💚 🍵 👉 ⚠ 🌐 ⏮️ FastAPI. - -👆 💪 🚮 🛃 ⚠ 🐕‍🦺 ⏮️ `@app.exception_handler()`: - -{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} - -📥, 🚥 👆 📨 `/unicorns/yolo`, *➡ 🛠️* 🔜 `raise` `UnicornException`. - -✋️ ⚫️ 🔜 🍵 `unicorn_exception_handler`. - -, 👆 🔜 📨 🧹 ❌, ⏮️ 🇺🇸🔍 👔 📟 `418` & 🎻 🎚: - -```JSON -{"message": "Oops! yolo did something. There goes a rainbow..."} -``` - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.requests import Request` & `from starlette.responses import JSONResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. 🎏 ⏮️ `Request`. - -/// - -## 🔐 🔢 ⚠ 🐕‍🦺 - -**FastAPI** ✔️ 🔢 ⚠ 🐕‍🦺. - -👫 🐕‍🦺 🈚 🛬 🔢 🎻 📨 🕐❔ 👆 `raise` `HTTPException` & 🕐❔ 📨 ✔️ ❌ 💽. - -👆 💪 🔐 👫 ⚠ 🐕‍🦺 ⏮️ 👆 👍. - -### 🔐 📨 🔬 ⚠ - -🕐❔ 📨 🔌 ❌ 📊, **FastAPI** 🔘 🤚 `RequestValidationError`. - -& ⚫️ 🔌 🔢 ⚠ 🐕‍🦺 ⚫️. - -🔐 ⚫️, 🗄 `RequestValidationError` & ⚙️ ⚫️ ⏮️ `@app.exception_handler(RequestValidationError)` 🎀 ⚠ 🐕‍🦺. - -⚠ 🐕‍🦺 🔜 📨 `Request` & ⚠. - -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} - -🔜, 🚥 👆 🚶 `/items/foo`, ↩️ 💆‍♂ 🔢 🎻 ❌ ⏮️: - -```JSON -{ - "detail": [ - { - "loc": [ - "path", - "item_id" - ], - "msg": "value is not a valid integer", - "type": "type_error.integer" - } - ] -} -``` - -👆 🔜 🤚 ✍ ⏬, ⏮️: - -``` -1 validation error -path -> item_id - value is not a valid integer (type=type_error.integer) -``` - -#### `RequestValidationError` 🆚 `ValidationError` - -/// warning - -👫 📡 ℹ 👈 👆 💪 🚶 🚥 ⚫️ 🚫 ⚠ 👆 🔜. - -/// - -`RequestValidationError` 🎧-🎓 Pydantic `ValidationError`. - -**FastAPI** ⚙️ ⚫️ 👈, 🚥 👆 ⚙️ Pydantic 🏷 `response_model`, & 👆 💽 ✔️ ❌, 👆 🔜 👀 ❌ 👆 🕹. - -✋️ 👩‍💻/👩‍💻 🔜 🚫 👀 ⚫️. ↩️, 👩‍💻 🔜 📨 "🔗 💽 ❌" ⏮️ 🇺🇸🔍 👔 📟 `500`. - -⚫️ 🔜 👉 🌌 ↩️ 🚥 👆 ✔️ Pydantic `ValidationError` 👆 *📨* ⚖️ 🙆 👆 📟 (🚫 👩‍💻 *📨*), ⚫️ 🤙 🐛 👆 📟. - -& ⏪ 👆 🔧 ⚫️, 👆 👩‍💻/👩‍💻 🚫🔜 🚫 ✔️ 🔐 🔗 ℹ 🔃 ❌, 👈 💪 🎦 💂‍♂ ⚠. - -### 🔐 `HTTPException` ❌ 🐕‍🦺 - -🎏 🌌, 👆 💪 🔐 `HTTPException` 🐕‍🦺. - -🖼, 👆 💪 💚 📨 ✅ ✍ 📨 ↩️ 🎻 👫 ❌: - -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import PlainTextResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. - -/// - -### ⚙️ `RequestValidationError` 💪 - -`RequestValidationError` 🔌 `body` ⚫️ 📨 ⏮️ ❌ 💽. - -👆 💪 ⚙️ ⚫️ ⏪ 🛠️ 👆 📱 🕹 💪 & ℹ ⚫️, 📨 ⚫️ 👩‍💻, ♒️. - -{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} - -🔜 🔄 📨 ❌ 🏬 💖: - -```JSON -{ - "title": "towel", - "size": "XL" -} -``` - -👆 🔜 📨 📨 💬 👆 👈 💽 ❌ ⚗ 📨 💪: - -```JSON hl_lines="12-15" -{ - "detail": [ - { - "loc": [ - "body", - "size" - ], - "msg": "value is not a valid integer", - "type": "type_error.integer" - } - ], - "body": { - "title": "towel", - "size": "XL" - } -} -``` - -#### FastAPI `HTTPException` 🆚 💃 `HTTPException` - -**FastAPI** ✔️ 🚮 👍 `HTTPException`. - -& **FastAPI**'Ⓜ `HTTPException` ❌ 🎓 😖 ⚪️➡️ 💃 `HTTPException` ❌ 🎓. - -🕴 🔺, 👈 **FastAPI**'Ⓜ `HTTPException` ✔ 👆 🚮 🎚 🔌 📨. - -👉 💪/⚙️ 🔘 ✳ 2️⃣.0️⃣ & 💂‍♂ 🚙. - -, 👆 💪 🚧 🙋‍♀ **FastAPI**'Ⓜ `HTTPException` 🛎 👆 📟. - -✋️ 🕐❔ 👆 ® ⚠ 🐕‍🦺, 👆 🔜 ® ⚫️ 💃 `HTTPException`. - -👉 🌌, 🚥 🙆 🍕 💃 🔗 📟, ⚖️ 💃 ↔ ⚖️ 🔌 -, 🤚 💃 `HTTPException`, 👆 🐕‍🦺 🔜 💪 ✊ & 🍵 ⚫️. - -👉 🖼, 💪 ✔️ 👯‍♂️ `HTTPException`Ⓜ 🎏 📟, 💃 ⚠ 📁 `StarletteHTTPException`: - -```Python -from starlette.exceptions import HTTPException as StarletteHTTPException -``` - -### 🏤-⚙️ **FastAPI**'Ⓜ ⚠ 🐕‍🦺 - -🚥 👆 💚 ⚙️ ⚠ ⤴️ ⏮️ 🎏 🔢 ⚠ 🐕‍🦺 ⚪️➡️ **FastAPI**, 👆 💪 🗄 & 🏤-⚙️ 🔢 ⚠ 🐕‍🦺 ⚪️➡️ `fastapi.exception_handlers`: - -{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} - -👉 🖼 👆 `print`😅 ❌ ⏮️ 📶 🎨 📧, ✋️ 👆 🤚 💭. 👆 💪 ⚙️ ⚠ & ⤴️ 🏤-⚙️ 🔢 ⚠ 🐕‍🦺. diff --git a/docs/em/docs/tutorial/header-params.md b/docs/em/docs/tutorial/header-params.md deleted file mode 100644 index fa5e3a22b4..0000000000 --- a/docs/em/docs/tutorial/header-params.md +++ /dev/null @@ -1,91 +0,0 @@ -# 🎚 🔢 - -👆 💪 🔬 🎚 🔢 🎏 🌌 👆 🔬 `Query`, `Path` & `Cookie` 🔢. - -## 🗄 `Header` - -🥇 🗄 `Header`: - -{* ../../docs_src/header_params/tutorial001.py hl[3] *} - -## 📣 `Header` 🔢 - -⤴️ 📣 🎚 🔢 ⚙️ 🎏 📊 ⏮️ `Path`, `Query` & `Cookie`. - -🥇 💲 🔢 💲, 👆 💪 🚶‍♀️ 🌐 ➕ 🔬 ⚖️ ✍ 🔢: - -{* ../../docs_src/header_params/tutorial001.py hl[9] *} - -/// note | 📡 ℹ - -`Header` "👭" 🎓 `Path`, `Query` & `Cookie`. ⚫️ 😖 ⚪️➡️ 🎏 ⚠ `Param` 🎓. - -✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `Header`, & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. - -/// - -/// info - -📣 🎚, 👆 💪 ⚙️ `Header`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢. - -/// - -## 🏧 🛠️ - -`Header` ✔️ 🐥 ➕ 🛠️ 🔛 🔝 ⚫️❔ `Path`, `Query` & `Cookie` 🚚. - -🌅 🐩 🎚 🎏 "🔠" 🦹, 💭 "➖ 🔣" (`-`). - -✋️ 🔢 💖 `user-agent` ❌ 🐍. - -, 🔢, `Header` 🔜 🗜 🔢 📛 🦹 ⚪️➡️ 🎦 (`_`) 🔠 (`-`) ⚗ & 📄 🎚. - -, 🇺🇸🔍 🎚 💼-😛,, 👆 💪 📣 👫 ⏮️ 🐩 🐍 👗 (💭 "🔡"). - -, 👆 💪 ⚙️ `user_agent` 👆 🛎 🔜 🐍 📟, ↩️ 💆‍♂ 🎯 🥇 🔤 `User_Agent` ⚖️ 🕳 🎏. - -🚥 🤔 👆 💪 ❎ 🏧 🛠️ 🎦 🔠, ⚒ 🔢 `convert_underscores` `Header` `False`: - -{* ../../docs_src/header_params/tutorial002.py hl[10] *} - -/// warning - -⏭ ⚒ `convert_underscores` `False`, 🐻 🤯 👈 🇺🇸🔍 🗳 & 💽 / ⚙️ 🎚 ⏮️ 🎦. - -/// - -## ❎ 🎚 - -⚫️ 💪 📨 ❎ 🎚. 👈 ⛓, 🎏 🎚 ⏮️ 💗 💲. - -👆 💪 🔬 👈 💼 ⚙️ 📇 🆎 📄. - -👆 🔜 📨 🌐 💲 ⚪️➡️ ❎ 🎚 🐍 `list`. - -🖼, 📣 🎚 `X-Token` 👈 💪 😑 🌅 🌘 🕐, 👆 💪 ✍: - -{* ../../docs_src/header_params/tutorial003.py hl[9] *} - -🚥 👆 🔗 ⏮️ 👈 *➡ 🛠️* 📨 2️⃣ 🇺🇸🔍 🎚 💖: - -``` -X-Token: foo -X-Token: bar -``` - -📨 🔜 💖: - -```JSON -{ - "X-Token values": [ - "bar", - "foo" - ] -} -``` - -## 🌃 - -📣 🎚 ⏮️ `Header`, ⚙️ 🎏 ⚠ ⚓ `Query`, `Path` & `Cookie`. - -& 🚫 😟 🔃 🎦 👆 🔢, **FastAPI** 🔜 ✊ 💅 🏭 👫. diff --git a/docs/em/docs/tutorial/index.md b/docs/em/docs/tutorial/index.md deleted file mode 100644 index 5f7532341e..0000000000 --- a/docs/em/docs/tutorial/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# 🔰 - 👩‍💻 🦮 - -👉 🔰 🎦 👆 ❔ ⚙️ **FastAPI** ⏮️ 🌅 🚮 ⚒, 🔁 🔁. - -🔠 📄 📉 🏗 🔛 ⏮️ 🕐, ✋️ ⚫️ 🏗 🎏 ❔, 👈 👆 💪 🚶 🔗 🙆 🎯 1️⃣ ❎ 👆 🎯 🛠️ 💪. - -⚫️ 🏗 👷 🔮 🔗. - -👆 💪 👟 🔙 & 👀 ⚫️❔ ⚫️❔ 👆 💪. - -## 🏃 📟 - -🌐 📟 🍫 💪 📁 & ⚙️ 🔗 (👫 🤙 💯 🐍 📁). - -🏃 🙆 🖼, 📁 📟 📁 `main.py`, & ▶️ `uvicorn` ⏮️: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -⚫️ **🏆 💡** 👈 👆 ✍ ⚖️ 📁 📟, ✍ ⚫️ & 🏃 ⚫️ 🌐. - -⚙️ ⚫️ 👆 👨‍🎨 ⚫️❔ 🤙 🎦 👆 💰 FastAPI, 👀 ❔ 🐥 📟 👆 ✔️ ✍, 🌐 🆎 ✅, ✍, ♒️. - ---- - -## ❎ FastAPI - -🥇 🔁 ❎ FastAPI. - -🔰, 👆 💪 💚 ❎ ⚫️ ⏮️ 🌐 📦 🔗 & ⚒: - -
- -```console -$ pip install "fastapi[all]" - ----> 100% -``` - -
- -...👈 🔌 `uvicorn`, 👈 👆 💪 ⚙️ 💽 👈 🏃 👆 📟. - -/// note - -👆 💪 ❎ ⚫️ 🍕 🍕. - -👉 ⚫️❔ 👆 🔜 🎲 🕐 👆 💚 🛠️ 👆 🈸 🏭: - -``` -pip install "fastapi[standard]" -``` - -❎ `uvicorn` 👷 💽: - -``` -pip install "uvicorn[standard]" -``` - - & 🎏 🔠 📦 🔗 👈 👆 💚 ⚙️. - -/// - -## 🏧 👩‍💻 🦮 - -📤 **🏧 👩‍💻 🦮** 👈 👆 💪 ✍ ⏪ ⏮️ 👉 **🔰 - 👩‍💻 🦮**. - -**🏧 👩‍💻 🦮**, 🏗 🔛 👉, ⚙️ 🎏 🔧, & 💡 👆 ➕ ⚒. - -✋️ 👆 🔜 🥇 ✍ **🔰 - 👩‍💻 🦮** (⚫️❔ 👆 👂 ▶️️ 🔜). - -⚫️ 🔧 👈 👆 💪 🏗 🏁 🈸 ⏮️ **🔰 - 👩‍💻 🦮**, & ⤴️ ↔ ⚫️ 🎏 🌌, ⚓️ 🔛 👆 💪, ⚙️ 🌖 💭 ⚪️➡️ **🏧 👩‍💻 🦮**. diff --git a/docs/em/docs/tutorial/metadata.md b/docs/em/docs/tutorial/metadata.md deleted file mode 100644 index eaf605de1e..0000000000 --- a/docs/em/docs/tutorial/metadata.md +++ /dev/null @@ -1,111 +0,0 @@ -# 🗃 & 🩺 📛 - -👆 💪 🛃 📚 🗃 📳 👆 **FastAPI** 🈸. - -## 🗃 🛠️ - -👆 💪 ⚒ 📄 🏑 👈 ⚙️ 🗄 🔧 & 🏧 🛠️ 🩺 ⚜: - -| 🔢 | 🆎 | 📛 | -|------------|------|-------------| -| `title` | `str` | 📛 🛠️. | -| `description` | `str` | 📏 📛 🛠️. ⚫️ 💪 ⚙️ ✍. | -| `version` | `string` | ⏬ 🛠️. 👉 ⏬ 👆 👍 🈸, 🚫 🗄. 🖼 `2.5.0`. | -| `terms_of_service` | `str` | 📛 ⚖ 🐕‍🦺 🛠️. 🚥 🚚, 👉 ✔️ 📛. | -| `contact` | `dict` | 📧 ℹ 🎦 🛠️. ⚫️ 💪 🔌 📚 🏑.
contact 🏑
🔢🆎📛
namestr⚖ 📛 📧 👨‍💼/🏢.
urlstr📛 ☝ 📧 ℹ. 🔜 📁 📛.
emailstr📧 📢 📧 👨‍💼/🏢. 🔜 📁 📧 📢.
| -| `license_info` | `dict` | 🛂 ℹ 🎦 🛠️. ⚫️ 💪 🔌 📚 🏑.
license_info 🏑
🔢🆎📛
namestr🚚 (🚥 license_info ⚒). 🛂 📛 ⚙️ 🛠️.
urlstr📛 🛂 ⚙️ 🛠️. 🔜 📁 📛.
| - -👆 💪 ⚒ 👫 ⏩: - -{* ../../docs_src/metadata/tutorial001.py hl[3:16,19:31] *} - -/// tip - -👆 💪 ✍ ✍ `description` 🏑 & ⚫️ 🔜 ✍ 🔢. - -/// - -⏮️ 👉 📳, 🏧 🛠️ 🩺 🔜 👀 💖: - - - -## 🗃 🔖 - -👆 💪 🚮 🌖 🗃 🎏 🔖 ⚙️ 👪 👆 ➡ 🛠️ ⏮️ 🔢 `openapi_tags`. - -⚫️ ✊ 📇 ⚗ 1️⃣ 📖 🔠 🔖. - -🔠 📖 💪 🔌: - -* `name` (**✔**): `str` ⏮️ 🎏 📛 👆 ⚙️ `tags` 🔢 👆 *➡ 🛠️* & `APIRouter`Ⓜ. -* `description`: `str` ⏮️ 📏 📛 🔖. ⚫️ 💪 ✔️ ✍ & 🔜 🎦 🩺 🎚. -* `externalDocs`: `dict` 🔬 🔢 🧾 ⏮️: - * `description`: `str` ⏮️ 📏 📛 🔢 🩺. - * `url` (**✔**): `str` ⏮️ 📛 🔢 🧾. - -### ✍ 🗃 🔖 - -➡️ 🔄 👈 🖼 ⏮️ 🔖 `users` & `items`. - -✍ 🗃 👆 🔖 & 🚶‍♀️ ⚫️ `openapi_tags` 🔢: - -{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} - -👀 👈 👆 💪 ⚙️ ✍ 🔘 📛, 🖼 "💳" 🔜 🎦 🦁 (**💳**) & "🎀" 🔜 🎦 ❕ (_🎀_). - -/// tip - -👆 🚫 ✔️ 🚮 🗃 🌐 🔖 👈 👆 ⚙️. - -/// - -### ⚙️ 👆 🔖 - -⚙️ `tags` 🔢 ⏮️ 👆 *➡ 🛠️* (& `APIRouter`Ⓜ) 🛠️ 👫 🎏 🔖: - -{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} - -/// info - -✍ 🌅 🔃 🔖 [➡ 🛠️ 📳](path-operation-configuration.md#_3){.internal-link target=_blank}. - -/// - -### ✅ 🩺 - -🔜, 🚥 👆 ✅ 🩺, 👫 🔜 🎦 🌐 🌖 🗃: - - - -### ✔ 🔖 - -✔ 🔠 🔖 🗃 📖 🔬 ✔ 🎦 🩺 🎚. - -🖼, ✋️ `users` 🔜 🚶 ⏮️ `items` 🔤 ✔, ⚫️ 🎦 ⏭ 👫, ↩️ 👥 🚮 👫 🗃 🥇 📖 📇. - -## 🗄 📛 - -🔢, 🗄 🔗 🍦 `/openapi.json`. - -✋️ 👆 💪 🔗 ⚫️ ⏮️ 🔢 `openapi_url`. - -🖼, ⚒ ⚫️ 🍦 `/api/v1/openapi.json`: - -{* ../../docs_src/metadata/tutorial002.py hl[3] *} - -🚥 👆 💚 ❎ 🗄 🔗 🍕 👆 💪 ⚒ `openapi_url=None`, 👈 🔜 ❎ 🧾 👩‍💻 🔢 👈 ⚙️ ⚫️. - -## 🩺 📛 - -👆 💪 🔗 2️⃣ 🧾 👩‍💻 🔢 🔌: - -* **🦁 🎚**: 🍦 `/docs`. - * 👆 💪 ⚒ 🚮 📛 ⏮️ 🔢 `docs_url`. - * 👆 💪 ❎ ⚫️ ⚒ `docs_url=None`. -* **📄**: 🍦 `/redoc`. - * 👆 💪 ⚒ 🚮 📛 ⏮️ 🔢 `redoc_url`. - * 👆 💪 ❎ ⚫️ ⚒ `redoc_url=None`. - -🖼, ⚒ 🦁 🎚 🍦 `/documentation` & ❎ 📄: - -{* ../../docs_src/metadata/tutorial003.py hl[3] *} diff --git a/docs/em/docs/tutorial/middleware.md b/docs/em/docs/tutorial/middleware.md deleted file mode 100644 index c77b105544..0000000000 --- a/docs/em/docs/tutorial/middleware.md +++ /dev/null @@ -1,66 +0,0 @@ -# 🛠️ - -👆 💪 🚮 🛠️ **FastAPI** 🈸. - -"🛠️" 🔢 👈 👷 ⏮️ 🔠 **📨** ⏭ ⚫️ 🛠️ 🙆 🎯 *➡ 🛠️*. & ⏮️ 🔠 **📨** ⏭ 🛬 ⚫️. - -* ⚫️ ✊ 🔠 **📨** 👈 👟 👆 🈸. -* ⚫️ 💪 ⤴️ 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟. -* ⤴️ ⚫️ 🚶‍♀️ **📨** 🛠️ 🎂 🈸 ( *➡ 🛠️*). -* ⚫️ ⤴️ ✊ **📨** 🏗 🈸 ( *➡ 🛠️*). -* ⚫️ 💪 🕳 👈 **📨** ⚖️ 🏃 🙆 💪 📟. -* ⤴️ ⚫️ 📨 **📨**. - -/// note | 📡 ℹ - -🚥 👆 ✔️ 🔗 ⏮️ `yield`, 🚪 📟 🔜 🏃 *⏮️* 🛠️. - -🚥 📤 🙆 🖥 📋 (📄 ⏪), 👫 🔜 🏃 *⏮️* 🌐 🛠️. - -/// - -## ✍ 🛠️ - -✍ 🛠️ 👆 ⚙️ 👨‍🎨 `@app.middleware("http")` 🔛 🔝 🔢. - -🛠️ 🔢 📨: - -* `request`. -* 🔢 `call_next` 👈 🔜 📨 `request` 🔢. - * 👉 🔢 🔜 🚶‍♀️ `request` 🔗 *➡ 🛠️*. - * ⤴️ ⚫️ 📨 `response` 🏗 🔗 *➡ 🛠️*. -* 👆 💪 ⤴️ 🔀 🌅 `response` ⏭ 🛬 ⚫️. - -{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} - -/// tip - -✔️ 🤯 👈 🛃 © 🎚 💪 🚮 ⚙️ '✖-' 🔡. - -✋️ 🚥 👆 ✔️ 🛃 🎚 👈 👆 💚 👩‍💻 🖥 💪 👀, 👆 💪 🚮 👫 👆 ⚜ 📳 ([⚜ (✖️-🇨🇳 ℹ 🤝)](cors.md){.internal-link target=_blank}) ⚙️ 🔢 `expose_headers` 📄 💃 ⚜ 🩺. - -/// - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.requests import Request`. - -**FastAPI** 🚚 ⚫️ 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. - -/// - -### ⏭ & ⏮️ `response` - -👆 💪 🚮 📟 🏃 ⏮️ `request`, ⏭ 🙆 *➡ 🛠️* 📨 ⚫️. - -& ⏮️ `response` 🏗, ⏭ 🛬 ⚫️. - -🖼, 👆 💪 🚮 🛃 🎚 `X-Process-Time` ⚗ 🕰 🥈 👈 ⚫️ ✊ 🛠️ 📨 & 🏗 📨: - -{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} - -## 🎏 🛠️ - -👆 💪 ⏪ ✍ 🌖 🔃 🎏 🛠️ [🏧 👩‍💻 🦮: 🏧 🛠️](../advanced/middleware.md){.internal-link target=_blank}. - -👆 🔜 ✍ 🔃 ❔ 🍵 ⏮️ 🛠️ ⏭ 📄. diff --git a/docs/em/docs/tutorial/path-operation-configuration.md b/docs/em/docs/tutorial/path-operation-configuration.md deleted file mode 100644 index c6030c0890..0000000000 --- a/docs/em/docs/tutorial/path-operation-configuration.md +++ /dev/null @@ -1,107 +0,0 @@ -# ➡ 🛠️ 📳 - -📤 📚 🔢 👈 👆 💪 🚶‍♀️ 👆 *➡ 🛠️ 👨‍🎨* 🔗 ⚫️. - -/// warning - -👀 👈 👫 🔢 🚶‍♀️ 🔗 *➡ 🛠️ 👨‍🎨*, 🚫 👆 *➡ 🛠️ 🔢*. - -/// - -## 📨 👔 📟 - -👆 💪 🔬 (🇺🇸🔍) `status_code` ⚙️ 📨 👆 *➡ 🛠️*. - -👆 💪 🚶‍♀️ 🔗 `int` 📟, 💖 `404`. - -✋️ 🚥 👆 🚫 💭 ⚫️❔ 🔠 🔢 📟, 👆 💪 ⚙️ ⌨ 📉 `status`: - -{* ../../docs_src/path_operation_configuration/tutorial001.py hl[3,17] *} - -👈 👔 📟 🔜 ⚙️ 📨 & 🔜 🚮 🗄 🔗. - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette import status`. - -**FastAPI** 🚚 🎏 `starlette.status` `fastapi.status` 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. - -/// - -## 🔖 - -👆 💪 🚮 🔖 👆 *➡ 🛠️*, 🚶‍♀️ 🔢 `tags` ⏮️ `list` `str` (🛎 1️⃣ `str`): - -{* ../../docs_src/path_operation_configuration/tutorial002.py hl[17,22,27] *} - -👫 🔜 🚮 🗄 🔗 & ⚙️ 🏧 🧾 🔢: - - - -### 🔖 ⏮️ 🔢 - -🚥 👆 ✔️ 🦏 🈸, 👆 5️⃣📆 🔚 🆙 📈 **📚 🔖**, & 👆 🔜 💚 ⚒ 💭 👆 🕧 ⚙️ **🎏 🔖** 🔗 *➡ 🛠️*. - -👫 💼, ⚫️ 💪 ⚒ 🔑 🏪 🔖 `Enum`. - -**FastAPI** 🐕‍🦺 👈 🎏 🌌 ⏮️ ✅ 🎻: - -{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *} - -## 📄 & 📛 - -👆 💪 🚮 `summary` & `description`: - -{* ../../docs_src/path_operation_configuration/tutorial003.py hl[20:21] *} - -## 📛 ⚪️➡️ #️⃣ - -📛 😑 📏 & 📔 💗 ⏸, 👆 💪 📣 *➡ 🛠️* 📛 🔢 #️⃣ & **FastAPI** 🔜 ✍ ⚫️ ⚪️➡️ 📤. - -👆 💪 ✍ #️⃣ , ⚫️ 🔜 🔬 & 🖥 ☑ (✊ 🔘 🏧 #️⃣ 📐). - -{* ../../docs_src/path_operation_configuration/tutorial004.py hl[19:27] *} - -⚫️ 🔜 ⚙️ 🎓 🩺: - - - -## 📨 📛 - -👆 💪 ✔ 📨 📛 ⏮️ 🔢 `response_description`: - -{* ../../docs_src/path_operation_configuration/tutorial005.py hl[21] *} - -/// info - -👀 👈 `response_description` 🔗 🎯 📨, `description` 🔗 *➡ 🛠️* 🏢. - -/// - -/// check - -🗄 ✔ 👈 🔠 *➡ 🛠️* 🚚 📨 📛. - -, 🚥 👆 🚫 🚚 1️⃣, **FastAPI** 🔜 🔁 🏗 1️⃣ "🏆 📨". - -/// - - - -## 😢 *➡ 🛠️* - -🚥 👆 💪 ™ *➡ 🛠️* 😢, ✋️ 🍵 ❎ ⚫️, 🚶‍♀️ 🔢 `deprecated`: - -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} - -⚫️ 🔜 🎯 ™ 😢 🎓 🩺: - - - -✅ ❔ 😢 & 🚫-😢 *➡ 🛠️* 👀 💖: - - - -## 🌃 - -👆 💪 🔗 & 🚮 🗃 👆 *➡ 🛠️* 💪 🚶‍♀️ 🔢 *➡ 🛠️ 👨‍🎨*. diff --git a/docs/em/docs/tutorial/path-params-numeric-validations.md b/docs/em/docs/tutorial/path-params-numeric-validations.md deleted file mode 100644 index b45e0557b6..0000000000 --- a/docs/em/docs/tutorial/path-params-numeric-validations.md +++ /dev/null @@ -1,117 +0,0 @@ -# ➡ 🔢 & 🔢 🔬 - -🎏 🌌 👈 👆 💪 📣 🌅 🔬 & 🗃 🔢 🔢 ⏮️ `Query`, 👆 💪 📣 🎏 🆎 🔬 & 🗃 ➡ 🔢 ⏮️ `Path`. - -## 🗄 ➡ - -🥇, 🗄 `Path` ⚪️➡️ `fastapi`: - -{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[3] *} - -## 📣 🗃 - -👆 💪 📣 🌐 🎏 🔢 `Query`. - -🖼, 📣 `title` 🗃 💲 ➡ 🔢 `item_id` 👆 💪 🆎: - -{* ../../docs_src/path_params_numeric_validations/tutorial001.py hl[10] *} - -/// note - -➡ 🔢 🕧 ✔ ⚫️ ✔️ 🍕 ➡. - -, 👆 🔜 📣 ⚫️ ⏮️ `...` ™ ⚫️ ✔. - -👐, 🚥 👆 📣 ⚫️ ⏮️ `None` ⚖️ ⚒ 🔢 💲, ⚫️ 🔜 🚫 📉 🕳, ⚫️ 🔜 🕧 🚚. - -/// - -## ✔ 🔢 👆 💪 - -➡️ 💬 👈 👆 💚 📣 🔢 🔢 `q` ✔ `str`. - -& 👆 🚫 💪 📣 🕳 🙆 👈 🔢, 👆 🚫 🤙 💪 ⚙️ `Query`. - -✋️ 👆 💪 ⚙️ `Path` `item_id` ➡ 🔢. - -🐍 🔜 😭 🚥 👆 🚮 💲 ⏮️ "🔢" ⏭ 💲 👈 🚫 ✔️ "🔢". - -✋️ 👆 💪 🏤-✔ 👫, & ✔️ 💲 🍵 🔢 (🔢 🔢 `q`) 🥇. - -⚫️ 🚫 🤔 **FastAPI**. ⚫️ 🔜 🔍 🔢 👫 📛, 🆎 & 🔢 📄 (`Query`, `Path`, ♒️), ⚫️ 🚫 💅 🔃 ✔. - -, 👆 💪 📣 👆 🔢: - -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} - -## ✔ 🔢 👆 💪, 🎱 - -🚥 👆 💚 📣 `q` 🔢 🔢 🍵 `Query` 🚫 🙆 🔢 💲, & ➡ 🔢 `item_id` ⚙️ `Path`, & ✔️ 👫 🎏 ✔, 🐍 ✔️ 🐥 🎁 ❕ 👈. - -🚶‍♀️ `*`, 🥇 🔢 🔢. - -🐍 🏆 🚫 🕳 ⏮️ 👈 `*`, ✋️ ⚫️ 🔜 💭 👈 🌐 📄 🔢 🔜 🤙 🇨🇻 ❌ (🔑-💲 👫), 💭 kwargs. 🚥 👫 🚫 ✔️ 🔢 💲. - -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} - -## 🔢 🔬: 👑 🌘 ⚖️ 🌓 - -⏮️ `Query` & `Path` (& 🎏 👆 🔜 👀 ⏪) 👆 💪 📣 🔢 ⚛. - -📥, ⏮️ `ge=1`, `item_id` 🔜 💪 🔢 🔢 "`g`🅾 🌘 ⚖️ `e`🅾" `1`. - -{* ../../docs_src/path_params_numeric_validations/tutorial004.py hl[8] *} - -## 🔢 🔬: 🌘 🌘 & 🌘 🌘 ⚖️ 🌓 - -🎏 ✔: - -* `gt`: `g`🅾 `t`👲 -* `le`: `l`👭 🌘 ⚖️ `e`🅾 - -{* ../../docs_src/path_params_numeric_validations/tutorial005.py hl[9] *} - -## 🔢 🔬: 🎈, 🌘 🌘 & 🌘 🌘 - -🔢 🔬 👷 `float` 💲. - -📥 🌐❔ ⚫️ ▶️️ ⚠ 💪 📣 gt & 🚫 ge. ⏮️ ⚫️ 👆 💪 🚚, 🖼, 👈 💲 🔜 👑 🌘 `0`, 🚥 ⚫️ 🌘 🌘 `1`. - -, `0.5` 🔜 ☑ 💲. ✋️ `0.0` ⚖️ `0` 🔜 🚫. - -& 🎏 lt. - -{* ../../docs_src/path_params_numeric_validations/tutorial006.py hl[11] *} - -## 🌃 - -⏮️ `Query`, `Path` (& 🎏 👆 🚫 👀) 👆 💪 📣 🗃 & 🎻 🔬 🎏 🌌 ⏮️ [🔢 🔢 & 🎻 🔬](query-params-str-validations.md){.internal-link target=_blank}. - -& 👆 💪 📣 🔢 🔬: - -* `gt`: `g`🅾 `t`👲 -* `ge`: `g`🅾 🌘 ⚖️ `e`🅾 -* `lt`: `l`👭 `t`👲 -* `le`: `l`👭 🌘 ⚖️ `e`🅾 - -/// info - -`Query`, `Path`, & 🎏 🎓 👆 🔜 👀 ⏪ 🏿 ⚠ `Param` 🎓. - -🌐 👫 💰 🎏 🔢 🌖 🔬 & 🗃 👆 ✔️ 👀. - -/// - -/// note | 📡 ℹ - -🕐❔ 👆 🗄 `Query`, `Path` & 🎏 ⚪️➡️ `fastapi`, 👫 🤙 🔢. - -👈 🕐❔ 🤙, 📨 👐 🎓 🎏 📛. - -, 👆 🗄 `Query`, ❔ 🔢. & 🕐❔ 👆 🤙 ⚫️, ⚫️ 📨 👐 🎓 🌟 `Query`. - -👫 🔢 📤 (↩️ ⚙️ 🎓 🔗) 👈 👆 👨‍🎨 🚫 ™ ❌ 🔃 👫 🆎. - -👈 🌌 👆 💪 ⚙️ 👆 😐 👨‍🎨 & 🛠️ 🧰 🍵 ✔️ 🚮 🛃 📳 🤷‍♂ 📚 ❌. - -/// diff --git a/docs/em/docs/tutorial/path-params.md b/docs/em/docs/tutorial/path-params.md deleted file mode 100644 index a914dc9050..0000000000 --- a/docs/em/docs/tutorial/path-params.md +++ /dev/null @@ -1,256 +0,0 @@ -# ➡ 🔢 - -👆 💪 📣 ➡ "🔢" ⚖️ "🔢" ⏮️ 🎏 ❕ ⚙️ 🐍 📁 🎻: - -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} - -💲 ➡ 🔢 `item_id` 🔜 🚶‍♀️ 👆 🔢 ❌ `item_id`. - -, 🚥 👆 🏃 👉 🖼 & 🚶 http://127.0.0.1:8000/items/foo, 👆 🔜 👀 📨: - -```JSON -{"item_id":"foo"} -``` - -## ➡ 🔢 ⏮️ 🆎 - -👆 💪 📣 🆎 ➡ 🔢 🔢, ⚙️ 🐩 🐍 🆎 ✍: - -{* ../../docs_src/path_params/tutorial002.py hl[7] *} - -👉 💼, `item_id` 📣 `int`. - -/// check - -👉 🔜 🤝 👆 👨‍🎨 🐕‍🦺 🔘 👆 🔢, ⏮️ ❌ ✅, 🛠️, ♒️. - -/// - -## 💽 🛠️ - -🚥 👆 🏃 👉 🖼 & 📂 👆 🖥 http://127.0.0.1:8000/items/3, 👆 🔜 👀 📨: - -```JSON -{"item_id":3} -``` - -/// check - -👀 👈 💲 👆 🔢 📨 (& 📨) `3`, 🐍 `int`, 🚫 🎻 `"3"`. - -, ⏮️ 👈 🆎 📄, **FastAPI** 🤝 👆 🏧 📨 "✍". - -/// - -## 💽 🔬 - -✋️ 🚥 👆 🚶 🖥 http://127.0.0.1:8000/items/foo, 👆 🔜 👀 👌 🇺🇸🔍 ❌: - -```JSON -{ - "detail": [ - { - "loc": [ - "path", - "item_id" - ], - "msg": "value is not a valid integer", - "type": "type_error.integer" - } - ] -} -``` - -↩️ ➡ 🔢 `item_id` ✔️ 💲 `"foo"`, ❔ 🚫 `int`. - -🎏 ❌ 🔜 😑 🚥 👆 🚚 `float` ↩️ `int`,: http://127.0.0.1:8000/items/4.2 - -/// check - -, ⏮️ 🎏 🐍 🆎 📄, **FastAPI** 🤝 👆 💽 🔬. - -👀 👈 ❌ 🎯 🇵🇸 ⚫️❔ ☝ 🌐❔ 🔬 🚫 🚶‍♀️. - -👉 🙃 👍 ⏪ 🛠️ & 🛠️ 📟 👈 🔗 ⏮️ 👆 🛠️. - -/// - -## 🧾 - -& 🕐❔ 👆 📂 👆 🖥 http://127.0.0.1:8000/docs, 👆 🔜 👀 🏧, 🎓, 🛠️ 🧾 💖: - - - -/// check - -🔄, ⏮️ 👈 🎏 🐍 🆎 📄, **FastAPI** 🤝 👆 🏧, 🎓 🧾 (🛠️ 🦁 🎚). - -👀 👈 ➡ 🔢 📣 🔢. - -/// - -## 🐩-⚓️ 💰, 🎛 🧾 - -& ↩️ 🏗 🔗 ⚪️➡️ 🗄 🐩, 📤 📚 🔗 🧰. - -↩️ 👉, **FastAPI** ⚫️ 🚚 🎛 🛠️ 🧾 (⚙️ 📄), ❔ 👆 💪 🔐 http://127.0.0.1:8000/redoc: - - - -🎏 🌌, 📤 📚 🔗 🧰. ✅ 📟 ⚡ 🧰 📚 🇪🇸. - -## Pydantic - -🌐 💽 🔬 🎭 🔽 🚘 Pydantic, 👆 🤚 🌐 💰 ⚪️➡️ ⚫️. & 👆 💭 👆 👍 ✋. - -👆 💪 ⚙️ 🎏 🆎 📄 ⏮️ `str`, `float`, `bool` & 📚 🎏 🏗 📊 🆎. - -📚 👫 🔬 ⏭ 📃 🔰. - -## ✔ 🤔 - -🕐❔ 🏗 *➡ 🛠️*, 👆 💪 🔎 ⚠ 🌐❔ 👆 ✔️ 🔧 ➡. - -💖 `/users/me`, ➡️ 💬 👈 ⚫️ 🤚 📊 🔃 ⏮️ 👩‍💻. - -& ⤴️ 👆 💪 ✔️ ➡ `/users/{user_id}` 🤚 💽 🔃 🎯 👩‍💻 👩‍💻 🆔. - -↩️ *➡ 🛠️* 🔬 ✔, 👆 💪 ⚒ 💭 👈 ➡ `/users/me` 📣 ⏭ 1️⃣ `/users/{user_id}`: - -{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} - -⏪, ➡ `/users/{user_id}` 🔜 🏏 `/users/me`, "💭" 👈 ⚫️ 📨 🔢 `user_id` ⏮️ 💲 `"me"`. - -➡, 👆 🚫🔜 ↔ ➡ 🛠️: - -{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *} - -🥇 🕐 🔜 🕧 ⚙️ ↩️ ➡ 🏏 🥇. - -## 🔁 💲 - -🚥 👆 ✔️ *➡ 🛠️* 👈 📨 *➡ 🔢*, ✋️ 👆 💚 💪 ☑ *➡ 🔢* 💲 🔁, 👆 💪 ⚙️ 🐩 🐍 `Enum`. - -### ✍ `Enum` 🎓 - -🗄 `Enum` & ✍ 🎧-🎓 👈 😖 ⚪️➡️ `str` & ⚪️➡️ `Enum`. - -😖 ⚪️➡️ `str` 🛠️ 🩺 🔜 💪 💭 👈 💲 🔜 🆎 `string` & 🔜 💪 ✍ ☑. - -⤴️ ✍ 🎓 🔢 ⏮️ 🔧 💲, ❔ 🔜 💪 ☑ 💲: - -{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} - -/// info - -🔢 (⚖️ 🔢) 💪 🐍 ↩️ ⏬ 3️⃣.4️⃣. - -/// - -/// tip - -🚥 👆 💭, "📊", "🎓", & "🍏" 📛 🎰 🏫 🏷. - -/// - -### 📣 *➡ 🔢* - -⤴️ ✍ *➡ 🔢* ⏮️ 🆎 ✍ ⚙️ 🔢 🎓 👆 ✍ (`ModelName`): - -{* ../../docs_src/path_params/tutorial005.py hl[16] *} - -### ✅ 🩺 - -↩️ 💪 💲 *➡ 🔢* 🔢, 🎓 🩺 💪 🎦 👫 🎆: - - - -### 👷 ⏮️ 🐍 *🔢* - -💲 *➡ 🔢* 🔜 *🔢 👨‍🎓*. - -#### 🔬 *🔢 👨‍🎓* - -👆 💪 🔬 ⚫️ ⏮️ *🔢 👨‍🎓* 👆 ✍ 🔢 `ModelName`: - -{* ../../docs_src/path_params/tutorial005.py hl[17] *} - -#### 🤚 *🔢 💲* - -👆 💪 🤚 ☑ 💲 ( `str` 👉 💼) ⚙️ `model_name.value`, ⚖️ 🏢, `your_enum_member.value`: - -{* ../../docs_src/path_params/tutorial005.py hl[20] *} - -/// tip - -👆 💪 🔐 💲 `"lenet"` ⏮️ `ModelName.lenet.value`. - -/// - -#### 📨 *🔢 👨‍🎓* - -👆 💪 📨 *🔢 👨‍🎓* ⚪️➡️ 👆 *➡ 🛠️*, 🐦 🎻 💪 (✅ `dict`). - -👫 🔜 🗜 👫 🔗 💲 (🎻 👉 💼) ⏭ 🛬 👫 👩‍💻: - -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} - -👆 👩‍💻 👆 🔜 🤚 🎻 📨 💖: - -```JSON -{ - "model_name": "alexnet", - "message": "Deep Learning FTW!" -} -``` - -## ➡ 🔢 ⚗ ➡ - -➡️ 💬 👆 ✔️ *➡ 🛠️* ⏮️ ➡ `/files/{file_path}`. - -✋️ 👆 💪 `file_path` ⚫️ 🔌 *➡*, 💖 `home/johndoe/myfile.txt`. - -, 📛 👈 📁 🔜 🕳 💖: `/files/home/johndoe/myfile.txt`. - -### 🗄 🐕‍🦺 - -🗄 🚫 🐕‍🦺 🌌 📣 *➡ 🔢* 🔌 *➡* 🔘, 👈 💪 ↘️ 😐 👈 ⚠ 💯 & 🔬. - -👐, 👆 💪 ⚫️ **FastAPI**, ⚙️ 1️⃣ 🔗 🧰 ⚪️➡️ 💃. - -& 🩺 🔜 👷, 👐 🚫 ❎ 🙆 🧾 💬 👈 🔢 🔜 🔌 ➡. - -### ➡ 🔌 - -⚙️ 🎛 🔗 ⚪️➡️ 💃 👆 💪 📣 *➡ 🔢* ⚗ *➡* ⚙️ 📛 💖: - -``` -/files/{file_path:path} -``` - -👉 💼, 📛 🔢 `file_path`, & 🏁 🍕, `:path`, 💬 ⚫️ 👈 🔢 🔜 🏏 🙆 *➡*. - -, 👆 💪 ⚙️ ⚫️ ⏮️: - -{* ../../docs_src/path_params/tutorial004.py hl[6] *} - -/// tip - -👆 💪 💪 🔢 🔌 `/home/johndoe/myfile.txt`, ⏮️ 🏁 🔪 (`/`). - -👈 💼, 📛 🔜: `/files//home/johndoe/myfile.txt`, ⏮️ 2️⃣✖️ 🔪 (`//`) 🖖 `files` & `home`. - -/// - -## 🌃 - -⏮️ **FastAPI**, ⚙️ 📏, 🏋️ & 🐩 🐍 🆎 📄, 👆 🤚: - -* 👨‍🎨 🐕‍🦺: ❌ ✅, ✍, ♒️. -* 💽 "" -* 💽 🔬 -* 🛠️ ✍ & 🏧 🧾 - -& 👆 🕴 ✔️ 📣 👫 🕐. - -👈 🎲 👑 ⭐ 📈 **FastAPI** 🔬 🎛 🛠️ (↖️ ⚪️➡️ 🍣 🎭). diff --git a/docs/em/docs/tutorial/query-params-str-validations.md b/docs/em/docs/tutorial/query-params-str-validations.md deleted file mode 100644 index fd077bf8f7..0000000000 --- a/docs/em/docs/tutorial/query-params-str-validations.md +++ /dev/null @@ -1,320 +0,0 @@ -# 🔢 🔢 & 🎻 🔬 - -**FastAPI** ✔ 👆 📣 🌖 ℹ & 🔬 👆 🔢. - -➡️ ✊ 👉 🈸 🖼: - -{* ../../docs_src/query_params_str_validations/tutorial001.py hl[9] *} - -🔢 🔢 `q` 🆎 `Union[str, None]` (⚖️ `str | None` 🐍 3️⃣.1️⃣0️⃣), 👈 ⛓ 👈 ⚫️ 🆎 `str` ✋️ 💪 `None`, & 👐, 🔢 💲 `None`, FastAPI 🔜 💭 ⚫️ 🚫 ✔. - -/// note - -FastAPI 🔜 💭 👈 💲 `q` 🚫 ✔ ↩️ 🔢 💲 `= None`. - - `Union` `Union[str, None]` 🔜 ✔ 👆 👨‍🎨 🤝 👆 👍 🐕‍🦺 & 🔍 ❌. - -/// - -## 🌖 🔬 - -👥 🔜 🛠️ 👈 ✋️ `q` 📦, 🕐❔ ⚫️ 🚚, **🚮 📐 🚫 📉 5️⃣0️⃣ 🦹**. - -### 🗄 `Query` - -🏆 👈, 🥇 🗄 `Query` ⚪️➡️ `fastapi`: - -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[3] *} - -## ⚙️ `Query` 🔢 💲 - -& 🔜 ⚙️ ⚫️ 🔢 💲 👆 🔢, ⚒ 🔢 `max_length` 5️⃣0️⃣: - -{* ../../docs_src/query_params_str_validations/tutorial002.py hl[9] *} - -👥 ✔️ ❎ 🔢 💲 `None` 🔢 ⏮️ `Query()`, 👥 💪 🔜 ⚒ 🔢 💲 ⏮️ 🔢 `Query(default=None)`, ⚫️ 🍦 🎏 🎯 ⚖ 👈 🔢 💲. - -: - -```Python -q: Union[str, None] = Query(default=None) -``` - -...⚒ 🔢 📦, 🎏: - -```Python -q: Union[str, None] = None -``` - -& 🐍 3️⃣.1️⃣0️⃣ & 🔛: - -```Python -q: str | None = Query(default=None) -``` - -...⚒ 🔢 📦, 🎏: - -```Python -q: str | None = None -``` - -✋️ ⚫️ 📣 ⚫️ 🎯 💆‍♂ 🔢 🔢. - -/// info - -✔️ 🤯 👈 🌅 ⚠ 🍕 ⚒ 🔢 📦 🍕: - -```Python -= None -``` - -⚖️: - -```Python -= Query(default=None) -``` - -⚫️ 🔜 ⚙️ 👈 `None` 🔢 💲, & 👈 🌌 ⚒ 🔢 **🚫 ✔**. - - `Union[str, None]` 🍕 ✔ 👆 👨‍🎨 🚚 👻 🐕‍🦺, ✋️ ⚫️ 🚫 ⚫️❔ 💬 FastAPI 👈 👉 🔢 🚫 ✔. - -/// - -⤴️, 👥 💪 🚶‍♀️ 🌅 🔢 `Query`. 👉 💼, `max_length` 🔢 👈 ✔ 🎻: - -```Python -q: Union[str, None] = Query(default=None, max_length=50) -``` - -👉 🔜 ✔ 📊, 🎦 🆑 ❌ 🕐❔ 📊 🚫 ☑, & 📄 🔢 🗄 🔗 *➡ 🛠️*. - -## 🚮 🌅 🔬 - -👆 💪 🚮 🔢 `min_length`: - -{* ../../docs_src/query_params_str_validations/tutorial003.py hl[10] *} - -## 🚮 🥔 🧬 - -👆 💪 🔬 🥔 🧬 👈 🔢 🔜 🏏: - -{* ../../docs_src/query_params_str_validations/tutorial004.py hl[11] *} - -👉 🎯 🥔 🧬 ✅ 👈 📨 🔢 💲: - -* `^`: ▶️ ⏮️ 📄 🦹, 🚫 ✔️ 🦹 ⏭. -* `fixedquery`: ✔️ ☑ 💲 `fixedquery`. -* `$`: 🔚 📤, 🚫 ✔️ 🙆 🌖 🦹 ⏮️ `fixedquery`. - -🚥 👆 💭 💸 ⏮️ 🌐 👉 **"🥔 🧬"** 💭, 🚫 😟. 👫 🏋️ ❔ 📚 👫👫. 👆 💪 📚 💩 🍵 💆‍♂ 🥔 🧬. - -✋️ 🕐❔ 👆 💪 👫 & 🚶 & 💡 👫, 💭 👈 👆 💪 ⏪ ⚙️ 👫 🔗 **FastAPI**. - -## 🔢 💲 - -🎏 🌌 👈 👆 💪 🚶‍♀️ `None` 💲 `default` 🔢, 👆 💪 🚶‍♀️ 🎏 💲. - -➡️ 💬 👈 👆 💚 📣 `q` 🔢 🔢 ✔️ `min_length` `3`, & ✔️ 🔢 💲 `"fixedquery"`: - -{* ../../docs_src/query_params_str_validations/tutorial005.py hl[7] *} - -/// note - -✔️ 🔢 💲 ⚒ 🔢 📦. - -/// - -## ⚒ ⚫️ ✔ - -🕐❔ 👥 🚫 💪 📣 🌅 🔬 ⚖️ 🗃, 👥 💪 ⚒ `q` 🔢 🔢 ✔ 🚫 📣 🔢 💲, 💖: - -```Python -q: str -``` - -↩️: - -```Python -q: Union[str, None] = None -``` - -✋️ 👥 🔜 📣 ⚫️ ⏮️ `Query`, 🖼 💖: - -```Python -q: Union[str, None] = Query(default=None, min_length=3) -``` - -, 🕐❔ 👆 💪 📣 💲 ✔ ⏪ ⚙️ `Query`, 👆 💪 🎯 🚫 📣 🔢 💲: - -{* ../../docs_src/query_params_str_validations/tutorial006.py hl[7] *} - -### ✔ ⏮️ `None` - -👆 💪 📣 👈 🔢 💪 🚫 `None`, ✋️ 👈 ⚫️ ✔. 👉 🔜 ⚡ 👩‍💻 📨 💲, 🚥 💲 `None`. - -👈, 👆 💪 📣 👈 `None` ☑ 🆎 ✋️ ⚙️ `default=...`: - -{* ../../docs_src/query_params_str_validations/tutorial006c.py hl[9] *} - -/// tip - -Pydantic, ❔ ⚫️❔ 🏋️ 🌐 💽 🔬 & 🛠️ FastAPI, ✔️ 🎁 🎭 🕐❔ 👆 ⚙️ `Optional` ⚖️ `Union[Something, None]` 🍵 🔢 💲, 👆 💪 ✍ 🌅 🔃 ⚫️ Pydantic 🩺 🔃 ✔ 📦 🏑. - -/// - -## 🔢 🔢 📇 / 💗 💲 - -🕐❔ 👆 🔬 🔢 🔢 🎯 ⏮️ `Query` 👆 💪 📣 ⚫️ 📨 📇 💲, ⚖️ 🙆‍♀ 🎏 🌌, 📨 💗 💲. - -🖼, 📣 🔢 🔢 `q` 👈 💪 😑 💗 🕰 📛, 👆 💪 ✍: - -{* ../../docs_src/query_params_str_validations/tutorial011.py hl[9] *} - -⤴️, ⏮️ 📛 💖: - -``` -http://localhost:8000/items/?q=foo&q=bar -``` - -👆 🔜 📨 💗 `q` *🔢 🔢'* 💲 (`foo` & `bar`) 🐍 `list` 🔘 👆 *➡ 🛠️ 🔢*, *🔢 🔢* `q`. - -, 📨 👈 📛 🔜: - -```JSON -{ - "q": [ - "foo", - "bar" - ] -} -``` - -/// tip - -📣 🔢 🔢 ⏮️ 🆎 `list`, 💖 🖼 🔛, 👆 💪 🎯 ⚙️ `Query`, ⏪ ⚫️ 🔜 🔬 📨 💪. - -/// - -🎓 🛠️ 🩺 🔜 ℹ ➡️, ✔ 💗 💲: - - - -### 🔢 🔢 📇 / 💗 💲 ⏮️ 🔢 - -& 👆 💪 🔬 🔢 `list` 💲 🚥 👌 🚚: - -{* ../../docs_src/query_params_str_validations/tutorial012.py hl[9] *} - -🚥 👆 🚶: - -``` -http://localhost:8000/items/ -``` - -🔢 `q` 🔜: `["foo", "bar"]` & 👆 📨 🔜: - -```JSON -{ - "q": [ - "foo", - "bar" - ] -} -``` - -#### ⚙️ `list` - -👆 💪 ⚙️ `list` 🔗 ↩️ `List[str]` (⚖️ `list[str]` 🐍 3️⃣.9️⃣ ➕): - -{* ../../docs_src/query_params_str_validations/tutorial013.py hl[7] *} - -/// note - -✔️ 🤯 👈 👉 💼, FastAPI 🏆 🚫 ✅ 🎚 📇. - -🖼, `List[int]` 🔜 ✅ (& 📄) 👈 🎚 📇 🔢. ✋️ `list` 😞 🚫🔜. - -/// - -## 📣 🌅 🗃 - -👆 💪 🚮 🌅 ℹ 🔃 🔢. - -👈 ℹ 🔜 🔌 🏗 🗄 & ⚙️ 🧾 👩‍💻 🔢 & 🔢 🧰. - -/// note - -✔️ 🤯 👈 🎏 🧰 5️⃣📆 ✔️ 🎏 🎚 🗄 🐕‍🦺. - -👫 💪 🚫 🎦 🌐 ➕ ℹ 📣, 👐 🌅 💼, ❌ ⚒ ⏪ 📄 🛠️. - -/// - -👆 💪 🚮 `title`: - -{* ../../docs_src/query_params_str_validations/tutorial007.py hl[10] *} - -& `description`: - -{* ../../docs_src/query_params_str_validations/tutorial008.py hl[13] *} - -## 📛 🔢 - -🌈 👈 👆 💚 🔢 `item-query`. - -💖: - -``` -http://127.0.0.1:8000/items/?item-query=foobaritems -``` - -✋️ `item-query` 🚫 ☑ 🐍 🔢 📛. - -🔐 🔜 `item_query`. - -✋️ 👆 💪 ⚫️ ⚫️❔ `item-query`... - -⤴️ 👆 💪 📣 `alias`, & 👈 📛 ⚫️❔ 🔜 ⚙️ 🔎 🔢 💲: - -{* ../../docs_src/query_params_str_validations/tutorial009.py hl[9] *} - -## 😛 🔢 - -🔜 ➡️ 💬 👆 🚫 💖 👉 🔢 🚫🔜. - -👆 ✔️ 👈 ⚫️ 📤 ⏪ ↩️ 📤 👩‍💻 ⚙️ ⚫️, ✋️ 👆 💚 🩺 🎯 🎦 ⚫️ 😢. - -⤴️ 🚶‍♀️ 🔢 `deprecated=True` `Query`: - -{* ../../docs_src/query_params_str_validations/tutorial010.py hl[18] *} - -🩺 🔜 🎦 ⚫️ 💖 👉: - - - -## 🚫 ⚪️➡️ 🗄 - -🚫 🔢 🔢 ⚪️➡️ 🏗 🗄 🔗 (& ➡️, ⚪️➡️ 🏧 🧾 ⚙️), ⚒ 🔢 `include_in_schema` `Query` `False`: - -{* ../../docs_src/query_params_str_validations/tutorial014.py hl[10] *} - -## 🌃 - -👆 💪 📣 🌖 🔬 & 🗃 👆 🔢. - -💊 🔬 & 🗃: - -* `alias` -* `title` -* `description` -* `deprecated` - -🔬 🎯 🎻: - -* `min_length` -* `max_length` -* `regex` - -👫 🖼 👆 👀 ❔ 📣 🔬 `str` 💲. - -👀 ⏭ 📃 👀 ❔ 📣 🔬 🎏 🆎, 💖 🔢. diff --git a/docs/em/docs/tutorial/query-params.md b/docs/em/docs/tutorial/query-params.md deleted file mode 100644 index 5c8d868a9a..0000000000 --- a/docs/em/docs/tutorial/query-params.md +++ /dev/null @@ -1,187 +0,0 @@ -# 🔢 🔢 - -🕐❔ 👆 📣 🎏 🔢 🔢 👈 🚫 🍕 ➡ 🔢, 👫 🔁 🔬 "🔢" 🔢. - -{* ../../docs_src/query_params/tutorial001.py hl[9] *} - -🔢 ⚒ 🔑-💲 👫 👈 🚶 ⏮️ `?` 📛, 🎏 `&` 🦹. - -🖼, 📛: - -``` -http://127.0.0.1:8000/items/?skip=0&limit=10 -``` - -...🔢 🔢: - -* `skip`: ⏮️ 💲 `0` -* `limit`: ⏮️ 💲 `10` - -👫 🍕 📛, 👫 "🛎" 🎻. - -✋️ 🕐❔ 👆 📣 👫 ⏮️ 🐍 🆎 (🖼 🔛, `int`), 👫 🗜 👈 🆎 & ✔ 🛡 ⚫️. - -🌐 🎏 🛠️ 👈 ⚖ ➡ 🔢 ✔ 🔢 🔢: - -* 👨‍🎨 🐕‍🦺 (🎲) -* 💽 "✍" -* 💽 🔬 -* 🏧 🧾 - -## 🔢 - -🔢 🔢 🚫 🔧 🍕 ➡, 👫 💪 📦 & 💪 ✔️ 🔢 💲. - -🖼 🔛 👫 ✔️ 🔢 💲 `skip=0` & `limit=10`. - -, 🔜 📛: - -``` -http://127.0.0.1:8000/items/ -``` - -🔜 🎏 🔜: - -``` -http://127.0.0.1:8000/items/?skip=0&limit=10 -``` - -✋️ 🚥 👆 🚶, 🖼: - -``` -http://127.0.0.1:8000/items/?skip=20 -``` - -🔢 💲 👆 🔢 🔜: - -* `skip=20`: ↩️ 👆 ⚒ ⚫️ 📛 -* `limit=10`: ↩️ 👈 🔢 💲 - -## 📦 🔢 - -🎏 🌌, 👆 💪 📣 📦 🔢 🔢, ⚒ 👫 🔢 `None`: - -{* ../../docs_src/query_params/tutorial002.py hl[9] *} - -👉 💼, 🔢 🔢 `q` 🔜 📦, & 🔜 `None` 🔢. - -/// check - -👀 👈 **FastAPI** 🙃 🥃 👀 👈 ➡ 🔢 `item_id` ➡ 🔢 & `q` 🚫,, ⚫️ 🔢 🔢. - -/// - -## 🔢 🔢 🆎 🛠️ - -👆 💪 📣 `bool` 🆎, & 👫 🔜 🗜: - -{* ../../docs_src/query_params/tutorial003.py hl[9] *} - -👉 💼, 🚥 👆 🚶: - -``` -http://127.0.0.1:8000/items/foo?short=1 -``` - -⚖️ - -``` -http://127.0.0.1:8000/items/foo?short=True -``` - -⚖️ - -``` -http://127.0.0.1:8000/items/foo?short=true -``` - -⚖️ - -``` -http://127.0.0.1:8000/items/foo?short=on -``` - -⚖️ - -``` -http://127.0.0.1:8000/items/foo?short=yes -``` - -⚖️ 🙆 🎏 💼 📈 (🔠, 🥇 🔤 🔠, ♒️), 👆 🔢 🔜 👀 🔢 `short` ⏮️ `bool` 💲 `True`. ⏪ `False`. - - -## 💗 ➡ & 🔢 🔢 - -👆 💪 📣 💗 ➡ 🔢 & 🔢 🔢 🎏 🕰, **FastAPI** 💭 ❔ ❔. - -& 👆 🚫 ✔️ 📣 👫 🙆 🎯 ✔. - -👫 🔜 🔬 📛: - -{* ../../docs_src/query_params/tutorial004.py hl[8,10] *} - -## ✔ 🔢 🔢 - -🕐❔ 👆 📣 🔢 💲 🚫-➡ 🔢 (🔜, 👥 ✔️ 🕴 👀 🔢 🔢), ⤴️ ⚫️ 🚫 ✔. - -🚥 👆 🚫 💚 🚮 🎯 💲 ✋️ ⚒ ⚫️ 📦, ⚒ 🔢 `None`. - -✋️ 🕐❔ 👆 💚 ⚒ 🔢 🔢 ✔, 👆 💪 🚫 📣 🙆 🔢 💲: - -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} - -📥 🔢 🔢 `needy` ✔ 🔢 🔢 🆎 `str`. - -🚥 👆 📂 👆 🖥 📛 💖: - -``` -http://127.0.0.1:8000/items/foo-item -``` - -...🍵 ❎ ✔ 🔢 `needy`, 👆 🔜 👀 ❌ 💖: - -```JSON -{ - "detail": [ - { - "loc": [ - "query", - "needy" - ], - "msg": "field required", - "type": "value_error.missing" - } - ] -} -``` - -`needy` 🚚 🔢, 👆 🔜 💪 ⚒ ⚫️ 📛: - -``` -http://127.0.0.1:8000/items/foo-item?needy=sooooneedy -``` - -...👉 🔜 👷: - -```JSON -{ - "item_id": "foo-item", - "needy": "sooooneedy" -} -``` - -& ↗️, 👆 💪 🔬 🔢 ✔, ✔️ 🔢 💲, & 🍕 📦: - -{* ../../docs_src/query_params/tutorial006.py hl[10] *} - -👉 💼, 📤 3️⃣ 🔢 🔢: - -* `needy`, ✔ `str`. -* `skip`, `int` ⏮️ 🔢 💲 `0`. -* `limit`, 📦 `int`. - -/// tip - -👆 💪 ⚙️ `Enum`Ⓜ 🎏 🌌 ⏮️ [➡ 🔢](path-params.md#_7){.internal-link target=_blank}. - -/// diff --git a/docs/em/docs/tutorial/request-files.md b/docs/em/docs/tutorial/request-files.md deleted file mode 100644 index c3bdeafd48..0000000000 --- a/docs/em/docs/tutorial/request-files.md +++ /dev/null @@ -1,172 +0,0 @@ -# 📨 📁 - -👆 💪 🔬 📁 📂 👩‍💻 ⚙️ `File`. - -/// info - -📨 📂 📁, 🥇 ❎ `python-multipart`. - -🤶 Ⓜ. `pip install python-multipart`. - -👉 ↩️ 📂 📁 📨 "📨 💽". - -/// - -## 🗄 `File` - -🗄 `File` & `UploadFile` ⚪️➡️ `fastapi`: - -{* ../../docs_src/request_files/tutorial001.py hl[1] *} - -## 🔬 `File` 🔢 - -✍ 📁 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Form`: - -{* ../../docs_src/request_files/tutorial001.py hl[7] *} - -/// info - -`File` 🎓 👈 😖 🔗 ⚪️➡️ `Form`. - -✋️ 💭 👈 🕐❔ 👆 🗄 `Query`, `Path`, `File` & 🎏 ⚪️➡️ `fastapi`, 👈 🤙 🔢 👈 📨 🎁 🎓. - -/// - -/// tip - -📣 📁 💪, 👆 💪 ⚙️ `File`, ↩️ ⏪ 🔢 🔜 🔬 🔢 🔢 ⚖️ 💪 (🎻) 🔢. - -/// - -📁 🔜 📂 "📨 💽". - -🚥 👆 📣 🆎 👆 *➡ 🛠️ 🔢* 🔢 `bytes`, **FastAPI** 🔜 ✍ 📁 👆 & 👆 🔜 📨 🎚 `bytes`. - -✔️ 🤯 👈 👉 ⛓ 👈 🎂 🎚 🔜 🏪 💾. 👉 🔜 👷 👍 🤪 📁. - -✋️ 📤 📚 💼 ❔ 👆 💪 💰 ⚪️➡️ ⚙️ `UploadFile`. - -## 📁 🔢 ⏮️ `UploadFile` - -🔬 📁 🔢 ⏮️ 🆎 `UploadFile`: - -{* ../../docs_src/request_files/tutorial001.py hl[12] *} - -⚙️ `UploadFile` ✔️ 📚 📈 🤭 `bytes`: - -* 👆 🚫 ✔️ ⚙️ `File()` 🔢 💲 🔢. -* ⚫️ ⚙️ "🧵" 📁: - * 📁 🏪 💾 🆙 🔆 📐 📉, & ⏮️ 🚶‍♀️ 👉 📉 ⚫️ 🔜 🏪 💾. -* 👉 ⛓ 👈 ⚫️ 🔜 👷 👍 ⭕ 📁 💖 🖼, 📹, ⭕ 💱, ♒️. 🍵 😩 🌐 💾. -* 👆 💪 🤚 🗃 ⚪️➡️ 📂 📁. -* ⚫️ ✔️ 📁-💖 `async` 🔢. -* ⚫️ 🎦 ☑ 🐍 `SpooledTemporaryFile` 🎚 👈 👆 💪 🚶‍♀️ 🔗 🎏 🗃 👈 ⌛ 📁-💖 🎚. - -### `UploadFile` - -`UploadFile` ✔️ 📄 🔢: - -* `filename`: `str` ⏮️ ⏮️ 📁 📛 👈 📂 (✅ `myimage.jpg`). -* `content_type`: `str` ⏮️ 🎚 🆎 (📁 🆎 / 📻 🆎) (✅ `image/jpeg`). -* `file`: `SpooledTemporaryFile` ( 📁-💖 🎚). 👉 ☑ 🐍 📁 👈 👆 💪 🚶‍♀️ 🔗 🎏 🔢 ⚖️ 🗃 👈 ⌛ "📁-💖" 🎚. - -`UploadFile` ✔️ 📄 `async` 👩‍🔬. 👫 🌐 🤙 🔗 📁 👩‍🔬 🔘 (⚙️ 🔗 `SpooledTemporaryFile`). - -* `write(data)`: ✍ `data` (`str` ⚖️ `bytes`) 📁. -* `read(size)`: ✍ `size` (`int`) 🔢/🦹 📁. -* `seek(offset)`: 🚶 🔢 🧘 `offset` (`int`) 📁. - * 🤶 Ⓜ., `await myfile.seek(0)` 🔜 🚶 ▶️ 📁. - * 👉 ✴️ ⚠ 🚥 👆 🏃 `await myfile.read()` 🕐 & ⤴️ 💪 ✍ 🎚 🔄. -* `close()`: 🔐 📁. - -🌐 👫 👩‍🔬 `async` 👩‍🔬, 👆 💪 "⌛" 👫. - -🖼, 🔘 `async` *➡ 🛠️ 🔢* 👆 💪 🤚 🎚 ⏮️: - -```Python -contents = await myfile.read() -``` - -🚥 👆 🔘 😐 `def` *➡ 🛠️ 🔢*, 👆 💪 🔐 `UploadFile.file` 🔗, 🖼: - -```Python -contents = myfile.file.read() -``` - -/// note | `async` 📡 ℹ - -🕐❔ 👆 ⚙️ `async` 👩‍🔬, **FastAPI** 🏃 📁 👩‍🔬 🧵 & ⌛ 👫. - -/// - -/// note | 💃 📡 ℹ - -**FastAPI**'Ⓜ `UploadFile` 😖 🔗 ⚪️➡️ **💃**'Ⓜ `UploadFile`, ✋️ 🚮 💪 🍕 ⚒ ⚫️ 🔗 ⏮️ **Pydantic** & 🎏 🍕 FastAPI. - -/// - -## ⚫️❔ "📨 💽" - -🌌 🕸 📨 (`
`) 📨 💽 💽 🛎 ⚙️ "🎁" 🔢 👈 📊, ⚫️ 🎏 ⚪️➡️ 🎻. - -**FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻. - -/// note | 📡 ℹ - -📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded` 🕐❔ ⚫️ 🚫 🔌 📁. - -✋️ 🕐❔ 📨 🔌 📁, ⚫️ 🗜 `multipart/form-data`. 🚥 👆 ⚙️ `File`, **FastAPI** 🔜 💭 ⚫️ ✔️ 🤚 📁 ⚪️➡️ ☑ 🍕 💪. - -🚥 👆 💚 ✍ 🌖 🔃 👉 🔢 & 📨 🏑, 👳 🏇 🕸 🩺 POST. - -/// - -/// warning - -👆 💪 📣 💗 `File` & `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `multipart/form-data` ↩️ `application/json`. - -👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️. - -/// - -## 📦 📁 📂 - -👆 💪 ⚒ 📁 📦 ⚙️ 🐩 🆎 ✍ & ⚒ 🔢 💲 `None`: - -{* ../../docs_src/request_files/tutorial001_02.py hl[9,17] *} - -## `UploadFile` ⏮️ 🌖 🗃 - -👆 💪 ⚙️ `File()` ⏮️ `UploadFile`, 🖼, ⚒ 🌖 🗃: - -{* ../../docs_src/request_files/tutorial001_03.py hl[13] *} - -## 💗 📁 📂 - -⚫️ 💪 📂 📚 📁 🎏 🕰. - -👫 🔜 👨‍💼 🎏 "📨 🏑" 📨 ⚙️ "📨 💽". - -⚙️ 👈, 📣 📇 `bytes` ⚖️ `UploadFile`: - -{* ../../docs_src/request_files/tutorial002.py hl[10,15] *} - -👆 🔜 📨, 📣, `list` `bytes` ⚖️ `UploadFile`Ⓜ. - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.responses import HTMLResponse`. - -**FastAPI** 🚚 🎏 `starlette.responses` `fastapi.responses` 🏪 👆, 👩‍💻. ✋️ 🌅 💪 📨 👟 🔗 ⚪️➡️ 💃. - -/// - -### 💗 📁 📂 ⏮️ 🌖 🗃 - -& 🎏 🌌 ⏭, 👆 💪 ⚙️ `File()` ⚒ 🌖 🔢, `UploadFile`: - -{* ../../docs_src/request_files/tutorial003.py hl[18] *} - -## 🌃 - -⚙️ `File`, `bytes`, & `UploadFile` 📣 📁 📂 📨, 📨 📨 💽. diff --git a/docs/em/docs/tutorial/request-forms-and-files.md b/docs/em/docs/tutorial/request-forms-and-files.md deleted file mode 100644 index 680b1a96a5..0000000000 --- a/docs/em/docs/tutorial/request-forms-and-files.md +++ /dev/null @@ -1,37 +0,0 @@ -# 📨 📨 & 📁 - -👆 💪 🔬 📁 & 📨 🏑 🎏 🕰 ⚙️ `File` & `Form`. - -/// info - -📨 📂 📁 & /⚖️ 📨 📊, 🥇 ❎ `python-multipart`. - -🤶 Ⓜ. `pip install python-multipart`. - -/// - -## 🗄 `File` & `Form` - -{* ../../docs_src/request_forms_and_files/tutorial001.py hl[1] *} - -## 🔬 `File` & `Form` 🔢 - -✍ 📁 & 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`: - -{* ../../docs_src/request_forms_and_files/tutorial001.py hl[8] *} - -📁 & 📨 🏑 🔜 📂 📨 📊 & 👆 🔜 📨 📁 & 📨 🏑. - -& 👆 💪 📣 📁 `bytes` & `UploadFile`. - -/// warning - -👆 💪 📣 💗 `File` & `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `multipart/form-data` ↩️ `application/json`. - -👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️. - -/// - -## 🌃 - -⚙️ `File` & `Form` 👯‍♂️ 🕐❔ 👆 💪 📨 💽 & 📁 🎏 📨. diff --git a/docs/em/docs/tutorial/request-forms.md b/docs/em/docs/tutorial/request-forms.md deleted file mode 100644 index 1cc1ea5dcb..0000000000 --- a/docs/em/docs/tutorial/request-forms.md +++ /dev/null @@ -1,69 +0,0 @@ -# 📨 💽 - -🕐❔ 👆 💪 📨 📨 🏑 ↩️ 🎻, 👆 💪 ⚙️ `Form`. - -/// info - -⚙️ 📨, 🥇 ❎ `python-multipart`. - -🤶 Ⓜ. `pip install python-multipart`. - -/// - -## 🗄 `Form` - -🗄 `Form` ⚪️➡️ `fastapi`: - -{* ../../docs_src/request_forms/tutorial001.py hl[1] *} - -## 🔬 `Form` 🔢 - -✍ 📨 🔢 🎏 🌌 👆 🔜 `Body` ⚖️ `Query`: - -{* ../../docs_src/request_forms/tutorial001.py hl[7] *} - -🖼, 1️⃣ 🌌 Oauth2️⃣ 🔧 💪 ⚙️ (🤙 "🔐 💧") ⚫️ ✔ 📨 `username` & `password` 📨 🏑. - -🔌 🚚 🏑 ⚫️❔ 📛 `username` & `password`, & 📨 📨 🏑, 🚫 🎻. - -⏮️ `Form` 👆 💪 📣 🎏 📳 ⏮️ `Body` (& `Query`, `Path`, `Cookie`), 🔌 🔬, 🖼, 📛 (✅ `user-name` ↩️ `username`), ♒️. - -/// info - -`Form` 🎓 👈 😖 🔗 ⚪️➡️ `Body`. - -/// - -/// tip - -📣 📨 💪, 👆 💪 ⚙️ `Form` 🎯, ↩️ 🍵 ⚫️ 🔢 🔜 🔬 🔢 🔢 ⚖️ 💪 (🎻) 🔢. - -/// - -## 🔃 "📨 🏑" - -🌌 🕸 📨 (`
`) 📨 💽 💽 🛎 ⚙️ "🎁" 🔢 👈 📊, ⚫️ 🎏 ⚪️➡️ 🎻. - -**FastAPI** 🔜 ⚒ 💭 ✍ 👈 📊 ⚪️➡️ ▶️️ 🥉 ↩️ 🎻. - -/// note | 📡 ℹ - -📊 ⚪️➡️ 📨 🛎 🗜 ⚙️ "📻 🆎" `application/x-www-form-urlencoded`. - -✋️ 🕐❔ 📨 🔌 📁, ⚫️ 🗜 `multipart/form-data`. 👆 🔜 ✍ 🔃 🚚 📁 ⏭ 📃. - -🚥 👆 💚 ✍ 🌖 🔃 👉 🔢 & 📨 🏑, 👳 🏇 🕸 🩺 POST. - -/// - -/// warning - -👆 💪 📣 💗 `Form` 🔢 *➡ 🛠️*, ✋️ 👆 💪 🚫 📣 `Body` 🏑 👈 👆 ⌛ 📨 🎻, 📨 🔜 ✔️ 💪 🗜 ⚙️ `application/x-www-form-urlencoded` ↩️ `application/json`. - -👉 🚫 🚫 **FastAPI**, ⚫️ 🍕 🇺🇸🔍 🛠️. - -/// - -## 🌃 - -⚙️ `Form` 📣 📨 💽 🔢 🔢. diff --git a/docs/em/docs/tutorial/response-model.md b/docs/em/docs/tutorial/response-model.md deleted file mode 100644 index 477376458d..0000000000 --- a/docs/em/docs/tutorial/response-model.md +++ /dev/null @@ -1,340 +0,0 @@ -# 📨 🏷 - 📨 🆎 - -👆 💪 📣 🆎 ⚙️ 📨 ✍ *➡ 🛠️ 🔢* **📨 🆎**. - -👆 💪 ⚙️ **🆎 ✍** 🎏 🌌 👆 🔜 🔢 💽 🔢 **🔢**, 👆 💪 ⚙️ Pydantic 🏷, 📇, 📖, 📊 💲 💖 🔢, 🎻, ♒️. - -{* ../../docs_src/response_model/tutorial001_01.py hl[18,23] *} - -FastAPI 🔜 ⚙️ 👉 📨 🆎: - -* **✔** 📨 💽. - * 🚥 💽 ❌ (✅ 👆 ❌ 🏑), ⚫️ ⛓ 👈 *👆* 📱 📟 💔, 🚫 🛬 ⚫️❔ ⚫️ 🔜, & ⚫️ 🔜 📨 💽 ❌ ↩️ 🛬 ❌ 💽. 👉 🌌 👆 & 👆 👩‍💻 💪 🎯 👈 👫 🔜 📨 💽 & 💽 💠 📈. -* 🚮 **🎻 🔗** 📨, 🗄 *➡ 🛠️*. - * 👉 🔜 ⚙️ **🏧 🩺**. - * ⚫️ 🔜 ⚙️ 🏧 👩‍💻 📟 ⚡ 🧰. - -✋️ 🏆 🥈: - -* ⚫️ 🔜 **📉 & ⛽** 🔢 📊 ⚫️❔ 🔬 📨 🆎. - * 👉 ✴️ ⚠ **💂‍♂**, 👥 🔜 👀 🌅 👈 🔛. - -## `response_model` 🔢 - -📤 💼 🌐❔ 👆 💪 ⚖️ 💚 📨 💽 👈 🚫 ⚫️❔ ⚫️❔ 🆎 📣. - -🖼, 👆 💪 💚 **📨 📖** ⚖️ 💽 🎚, ✋️ **📣 ⚫️ Pydantic 🏷**. 👉 🌌 Pydantic 🏷 🔜 🌐 💽 🧾, 🔬, ♒️. 🎚 👈 👆 📨 (✅ 📖 ⚖️ 💽 🎚). - -🚥 👆 🚮 📨 🆎 ✍, 🧰 & 👨‍🎨 🔜 😭 ⏮️ (☑) ❌ 💬 👆 👈 👆 🔢 🛬 🆎 (✅#️⃣) 👈 🎏 ⚪️➡️ ⚫️❔ 👆 📣 (✅ Pydantic 🏷). - -📚 💼, 👆 💪 ⚙️ *➡ 🛠️ 👨‍🎨* 🔢 `response_model` ↩️ 📨 🆎. - -👆 💪 ⚙️ `response_model` 🔢 🙆 *➡ 🛠️*: - -* `@app.get()` -* `@app.post()` -* `@app.put()` -* `@app.delete()` -* ♒️. - -{* ../../docs_src/response_model/tutorial001.py hl[17,22,24:27] *} - -/// note - -👀 👈 `response_model` 🔢 "👨‍🎨" 👩‍🔬 (`get`, `post`, ♒️). 🚫 👆 *➡ 🛠️ 🔢*, 💖 🌐 🔢 & 💪. - -/// - -`response_model` 📨 🎏 🆎 👆 🔜 📣 Pydantic 🏷 🏑,, ⚫️ 💪 Pydantic 🏷, ✋️ ⚫️ 💪, ✅ `list` Pydantic 🏷, 💖 `List[Item]`. - -FastAPI 🔜 ⚙️ 👉 `response_model` 🌐 💽 🧾, 🔬, ♒️. & **🗜 & ⛽ 🔢 📊** 🚮 🆎 📄. - -/// tip - -🚥 👆 ✔️ ⚠ 🆎 ✅ 👆 👨‍🎨, ✍, ♒️, 👆 💪 📣 🔢 📨 🆎 `Any`. - -👈 🌌 👆 💬 👨‍🎨 👈 👆 😫 🛬 🕳. ✋️ FastAPI 🔜 💽 🧾, 🔬, 🖥, ♒️. ⏮️ `response_model`. - -/// - -### `response_model` 📫 - -🚥 👆 📣 👯‍♂️ 📨 🆎 & `response_model`, `response_model` 🔜 ✊ 📫 & ⚙️ FastAPI. - -👉 🌌 👆 💪 🚮 ☑ 🆎 ✍ 👆 🔢 🕐❔ 👆 🛬 🆎 🎏 🌘 📨 🏷, ⚙️ 👨‍🎨 & 🧰 💖 ✍. & 👆 💪 ✔️ FastAPI 💽 🔬, 🧾, ♒️. ⚙️ `response_model`. - -👆 💪 ⚙️ `response_model=None` ❎ 🏗 📨 🏷 👈 *➡ 🛠️*, 👆 5️⃣📆 💪 ⚫️ 🚥 👆 ❎ 🆎 ✍ 👜 👈 🚫 ☑ Pydantic 🏑, 👆 🔜 👀 🖼 👈 1️⃣ 📄 🔛. - -## 📨 🎏 🔢 💽 - -📥 👥 📣 `UserIn` 🏷, ⚫️ 🔜 🔌 🔢 🔐: - -{* ../../docs_src/response_model/tutorial002.py hl[9,11] *} - -/// info - -⚙️ `EmailStr`, 🥇 ❎ `email-validator`. - -🤶 Ⓜ. `pip install email-validator` -⚖️ `pip install pydantic[email]`. - -/// - -& 👥 ⚙️ 👉 🏷 📣 👆 🔢 & 🎏 🏷 📣 👆 🔢: - -{* ../../docs_src/response_model/tutorial002.py hl[18] *} - -🔜, 🕐❔ 🖥 🏗 👩‍💻 ⏮️ 🔐, 🛠️ 🔜 📨 🎏 🔐 📨. - -👉 💼, ⚫️ 💪 🚫 ⚠, ↩️ ⚫️ 🎏 👩‍💻 📨 🔐. - -✋️ 🚥 👥 ⚙️ 🎏 🏷 ➕1️⃣ *➡ 🛠️*, 👥 💪 📨 👆 👩‍💻 🔐 🔠 👩‍💻. - -/// danger - -🙅 🏪 ✅ 🔐 👩‍💻 ⚖️ 📨 ⚫️ 📨 💖 👉, 🚥 👆 💭 🌐 ⚠ & 👆 💭 ⚫️❔ 👆 🔨. - -/// - -## 🚮 🔢 🏷 - -👥 💪 ↩️ ✍ 🔢 🏷 ⏮️ 🔢 🔐 & 🔢 🏷 🍵 ⚫️: - -{* ../../docs_src/response_model/tutorial003.py hl[9,11,16] *} - -📥, ✋️ 👆 *➡ 🛠️ 🔢* 🛬 🎏 🔢 👩‍💻 👈 🔌 🔐: - -{* ../../docs_src/response_model/tutorial003.py hl[24] *} - -...👥 📣 `response_model` 👆 🏷 `UserOut`, 👈 🚫 🔌 🔐: - -{* ../../docs_src/response_model/tutorial003.py hl[22] *} - -, **FastAPI** 🔜 ✊ 💅 🖥 👅 🌐 💽 👈 🚫 📣 🔢 🏷 (⚙️ Pydantic). - -### `response_model` ⚖️ 📨 🆎 - -👉 💼, ↩️ 2️⃣ 🏷 🎏, 🚥 👥 ✍ 🔢 📨 🆎 `UserOut`, 👨‍🎨 & 🧰 🔜 😭 👈 👥 🛬 ❌ 🆎, 📚 🎏 🎓. - -👈 ⚫️❔ 👉 🖼 👥 ✔️ 📣 ⚫️ `response_model` 🔢. - -...✋️ 😣 👂 🔛 👀 ❔ ❎ 👈. - -## 📨 🆎 & 💽 🖥 - -➡️ 😣 ⚪️➡️ ⏮️ 🖼. 👥 💚 **✍ 🔢 ⏮️ 1️⃣ 🆎** ✋️ 📨 🕳 👈 🔌 **🌅 💽**. - -👥 💚 FastAPI 🚧 **🖥** 📊 ⚙️ 📨 🏷. - -⏮️ 🖼, ↩️ 🎓 🎏, 👥 ✔️ ⚙️ `response_model` 🔢. ✋️ 👈 ⛓ 👈 👥 🚫 🤚 🐕‍🦺 ⚪️➡️ 👨‍🎨 & 🧰 ✅ 🔢 📨 🆎. - -✋️ 🌅 💼 🌐❔ 👥 💪 🕳 💖 👉, 👥 💚 🏷 **⛽/❎** 📊 👉 🖼. - -& 👈 💼, 👥 💪 ⚙️ 🎓 & 🧬 ✊ 📈 🔢 **🆎 ✍** 🤚 👍 🐕‍🦺 👨‍🎨 & 🧰, & 🤚 FastAPI **💽 🖥**. - -{* ../../docs_src/response_model/tutorial003_01.py hl[9:13,15:16,20] *} - -⏮️ 👉, 👥 🤚 🏭 🐕‍🦺, ⚪️➡️ 👨‍🎨 & ✍ 👉 📟 ☑ ⚖ 🆎, ✋️ 👥 🤚 💽 🖥 ⚪️➡️ FastAPI. - -❔ 🔨 👉 👷 ❓ ➡️ ✅ 👈 👅. 👶 - -### 🆎 ✍ & 🏭 - -🥇 ➡️ 👀 ❔ 👨‍🎨, ✍ & 🎏 🧰 🔜 👀 👉. - -`BaseUser` ✔️ 🧢 🏑. ⤴️ `UserIn` 😖 ⚪️➡️ `BaseUser` & 🚮 `password` 🏑,, ⚫️ 🔜 🔌 🌐 🏑 ⚪️➡️ 👯‍♂️ 🏷. - -👥 ✍ 🔢 📨 🆎 `BaseUser`, ✋️ 👥 🤙 🛬 `UserIn` 👐. - -👨‍🎨, ✍, & 🎏 🧰 🏆 🚫 😭 🔃 👉 ↩️, ⌨ ⚖, `UserIn` 🏿 `BaseUser`, ❔ ⛓ ⚫️ *☑* 🆎 🕐❔ ⚫️❔ ⌛ 🕳 👈 `BaseUser`. - -### FastAPI 💽 🖥 - -🔜, FastAPI, ⚫️ 🔜 👀 📨 🆎 & ⚒ 💭 👈 ⚫️❔ 👆 📨 🔌 **🕴** 🏑 👈 📣 🆎. - -FastAPI 🔨 📚 👜 🔘 ⏮️ Pydantic ⚒ 💭 👈 📚 🎏 🚫 🎓 🧬 🚫 ⚙️ 📨 💽 🖥, ⏪ 👆 💪 🔚 🆙 🛬 🌅 🌅 💽 🌘 ⚫️❔ 👆 📈. - -👉 🌌, 👆 💪 🤚 🏆 👯‍♂️ 🌏: 🆎 ✍ ⏮️ **🏭 🐕‍🦺** & **💽 🖥**. - -## 👀 ⚫️ 🩺 - -🕐❔ 👆 👀 🏧 🩺, 👆 💪 ✅ 👈 🔢 🏷 & 🔢 🏷 🔜 👯‍♂️ ✔️ 👫 👍 🎻 🔗: - - - -& 👯‍♂️ 🏷 🔜 ⚙️ 🎓 🛠️ 🧾: - - - -## 🎏 📨 🆎 ✍ - -📤 5️⃣📆 💼 🌐❔ 👆 📨 🕳 👈 🚫 ☑ Pydantic 🏑 & 👆 ✍ ⚫️ 🔢, 🕴 🤚 🐕‍🦺 🚚 🏭 (👨‍🎨, ✍, ♒️). - -### 📨 📨 🔗 - -🏆 ⚠ 💼 🔜 [🛬 📨 🔗 🔬 ⏪ 🏧 🩺](../advanced/response-directly.md){.internal-link target=_blank}. - -{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} - -👉 🙅 💼 🍵 🔁 FastAPI ↩️ 📨 🆎 ✍ 🎓 (⚖️ 🏿) `Response`. - -& 🧰 🔜 😄 ↩️ 👯‍♂️ `RedirectResponse` & `JSONResponse` 🏿 `Response`, 🆎 ✍ ☑. - -### ✍ 📨 🏿 - -👆 💪 ⚙️ 🏿 `Response` 🆎 ✍: - -{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} - -👉 🔜 👷 ↩️ `RedirectResponse` 🏿 `Response`, & FastAPI 🔜 🔁 🍵 👉 🙅 💼. - -### ❌ 📨 🆎 ✍ - -✋️ 🕐❔ 👆 📨 🎏 ❌ 🎚 👈 🚫 ☑ Pydantic 🆎 (✅ 💽 🎚) & 👆 ✍ ⚫️ 💖 👈 🔢, FastAPI 🔜 🔄 ✍ Pydantic 📨 🏷 ⚪️➡️ 👈 🆎 ✍, & 🔜 ❌. - -🎏 🔜 🔨 🚥 👆 ✔️ 🕳 💖 🇪🇺 🖖 🎏 🆎 🌐❔ 1️⃣ ⚖️ 🌅 👫 🚫 ☑ Pydantic 🆎, 🖼 👉 🔜 ❌ 👶: - -{* ../../docs_src/response_model/tutorial003_04.py hl[10] *} - -...👉 ❌ ↩️ 🆎 ✍ 🚫 Pydantic 🆎 & 🚫 👁 `Response` 🎓 ⚖️ 🏿, ⚫️ 🇪🇺 (🙆 2️⃣) 🖖 `Response` & `dict`. - -### ❎ 📨 🏷 - -▶️ ⚪️➡️ 🖼 🔛, 👆 5️⃣📆 🚫 💚 ✔️ 🔢 💽 🔬, 🧾, 🖥, ♒️. 👈 🎭 FastAPI. - -✋️ 👆 💪 💚 🚧 📨 🆎 ✍ 🔢 🤚 🐕‍🦺 ⚪️➡️ 🧰 💖 👨‍🎨 & 🆎 ☑ (✅ ✍). - -👉 💼, 👆 💪 ❎ 📨 🏷 ⚡ ⚒ `response_model=None`: - -{* ../../docs_src/response_model/tutorial003_05.py hl[9] *} - -👉 🔜 ⚒ FastAPI 🚶 📨 🏷 ⚡ & 👈 🌌 👆 💪 ✔️ 🙆 📨 🆎 ✍ 👆 💪 🍵 ⚫️ 🤕 👆 FastAPI 🈸. 👶 - -## 📨 🏷 🔢 🔢 - -👆 📨 🏷 💪 ✔️ 🔢 💲, 💖: - -{* ../../docs_src/response_model/tutorial004.py hl[11,13:14] *} - -* `description: Union[str, None] = None` (⚖️ `str | None = None` 🐍 3️⃣.1️⃣0️⃣) ✔️ 🔢 `None`. -* `tax: float = 10.5` ✔️ 🔢 `10.5`. -* `tags: List[str] = []` 🔢 🛁 📇: `[]`. - -✋️ 👆 💪 💚 🚫 👫 ⚪️➡️ 🏁 🚥 👫 🚫 🤙 🏪. - -🖼, 🚥 👆 ✔️ 🏷 ⏮️ 📚 📦 🔢 ☁ 💽, ✋️ 👆 🚫 💚 📨 📶 📏 🎻 📨 🌕 🔢 💲. - -### ⚙️ `response_model_exclude_unset` 🔢 - -👆 💪 ⚒ *➡ 🛠️ 👨‍🎨* 🔢 `response_model_exclude_unset=True`: - -{* ../../docs_src/response_model/tutorial004.py hl[24] *} - -& 👈 🔢 💲 🏆 🚫 🔌 📨, 🕴 💲 🤙 ⚒. - -, 🚥 👆 📨 📨 👈 *➡ 🛠️* 🏬 ⏮️ 🆔 `foo`, 📨 (🚫 ✅ 🔢 💲) 🔜: - -```JSON -{ - "name": "Foo", - "price": 50.2 -} -``` - -/// info - -FastAPI ⚙️ Pydantic 🏷 `.dict()` ⏮️ 🚮 `exclude_unset` 🔢 🏆 👉. - -/// - -/// info - -👆 💪 ⚙️: - -* `response_model_exclude_defaults=True` -* `response_model_exclude_none=True` - -🔬 Pydantic 🩺 `exclude_defaults` & `exclude_none`. - -/// - -#### 📊 ⏮️ 💲 🏑 ⏮️ 🔢 - -✋️ 🚥 👆 📊 ✔️ 💲 🏷 🏑 ⏮️ 🔢 💲, 💖 🏬 ⏮️ 🆔 `bar`: - -```Python hl_lines="3 5" -{ - "name": "Bar", - "description": "The bartenders", - "price": 62, - "tax": 20.2 -} -``` - -👫 🔜 🔌 📨. - -#### 📊 ⏮️ 🎏 💲 🔢 - -🚥 📊 ✔️ 🎏 💲 🔢 🕐, 💖 🏬 ⏮️ 🆔 `baz`: - -```Python hl_lines="3 5-6" -{ - "name": "Baz", - "description": None, - "price": 50.2, - "tax": 10.5, - "tags": [] -} -``` - -FastAPI 🙃 🥃 (🤙, Pydantic 🙃 🥃) 🤔 👈, ✋️ `description`, `tax`, & `tags` ✔️ 🎏 💲 🔢, 👫 ⚒ 🎯 (↩️ ✊ ⚪️➡️ 🔢). - -, 👫 🔜 🔌 🎻 📨. - -/// tip - -👀 👈 🔢 💲 💪 🕳, 🚫 🕴 `None`. - -👫 💪 📇 (`[]`), `float` `10.5`, ♒️. - -/// - -### `response_model_include` & `response_model_exclude` - -👆 💪 ⚙️ *➡ 🛠️ 👨‍🎨* 🔢 `response_model_include` & `response_model_exclude`. - -👫 ✊ `set` `str` ⏮️ 📛 🔢 🔌 (❎ 🎂) ⚖️ 🚫 (✅ 🎂). - -👉 💪 ⚙️ ⏩ ⌨ 🚥 👆 ✔️ 🕴 1️⃣ Pydantic 🏷 & 💚 ❎ 💽 ⚪️➡️ 🔢. - -/// tip - -✋️ ⚫️ 👍 ⚙️ 💭 🔛, ⚙️ 💗 🎓, ↩️ 👫 🔢. - -👉 ↩️ 🎻 🔗 🏗 👆 📱 🗄 (& 🩺) 🔜 1️⃣ 🏁 🏷, 🚥 👆 ⚙️ `response_model_include` ⚖️ `response_model_exclude` 🚫 🔢. - -👉 ✔ `response_model_by_alias` 👈 👷 ➡. - -/// - -{* ../../docs_src/response_model/tutorial005.py hl[31,37] *} - -/// tip - -❕ `{"name", "description"}` ✍ `set` ⏮️ 📚 2️⃣ 💲. - -⚫️ 🌓 `set(["name", "description"])`. - -/// - -#### ⚙️ `list`Ⓜ ↩️ `set`Ⓜ - -🚥 👆 💭 ⚙️ `set` & ⚙️ `list` ⚖️ `tuple` ↩️, FastAPI 🔜 🗜 ⚫️ `set` & ⚫️ 🔜 👷 ☑: - -{* ../../docs_src/response_model/tutorial006.py hl[31,37] *} - -## 🌃 - -⚙️ *➡ 🛠️ 👨‍🎨* 🔢 `response_model` 🔬 📨 🏷 & ✴️ 🚚 📢 💽 ⛽ 👅. - -⚙️ `response_model_exclude_unset` 📨 🕴 💲 🎯 ⚒. diff --git a/docs/em/docs/tutorial/response-status-code.md b/docs/em/docs/tutorial/response-status-code.md deleted file mode 100644 index 413ceb9169..0000000000 --- a/docs/em/docs/tutorial/response-status-code.md +++ /dev/null @@ -1,101 +0,0 @@ -# 📨 👔 📟 - -🎏 🌌 👆 💪 ✔ 📨 🏷, 👆 💪 📣 🇺🇸🔍 👔 📟 ⚙️ 📨 ⏮️ 🔢 `status_code` 🙆 *➡ 🛠️*: - -* `@app.get()` -* `@app.post()` -* `@app.put()` -* `@app.delete()` -* ♒️. - -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} - -/// note - -👀 👈 `status_code` 🔢 "👨‍🎨" 👩‍🔬 (`get`, `post`, ♒️). 🚫 👆 *➡ 🛠️ 🔢*, 💖 🌐 🔢 & 💪. - -/// - -`status_code` 🔢 📨 🔢 ⏮️ 🇺🇸🔍 👔 📟. - -/// info - -`status_code` 💪 👐 📨 `IntEnum`, ✅ 🐍 `http.HTTPStatus`. - -/// - -⚫️ 🔜: - -* 📨 👈 👔 📟 📨. -* 📄 ⚫️ ✅ 🗄 🔗 ( & , 👩‍💻 🔢): - - - -/// note - -📨 📟 (👀 ⏭ 📄) 🎦 👈 📨 🔨 🚫 ✔️ 💪. - -FastAPI 💭 👉, & 🔜 🏭 🗄 🩺 👈 🇵🇸 📤 🙅‍♂ 📨 💪. - -/// - -## 🔃 🇺🇸🔍 👔 📟 - -/// note - -🚥 👆 ⏪ 💭 ⚫️❔ 🇺🇸🔍 👔 📟, 🚶 ⏭ 📄. - -/// - -🇺🇸🔍, 👆 📨 🔢 👔 📟 3️⃣ 9️⃣ 🍕 📨. - -👫 👔 📟 ✔️ 📛 🔗 🤔 👫, ✋️ ⚠ 🍕 🔢. - -📏: - -* `100` & 🔛 "ℹ". 👆 🛎 ⚙️ 👫 🔗. 📨 ⏮️ 👫 👔 📟 🚫🔜 ✔️ 💪. -* **`200`** & 🔛 "🏆" 📨. 👫 🕐 👆 🔜 ⚙️ 🏆. - * `200` 🔢 👔 📟, ❔ ⛓ 🌐 "👌". - * ➕1️⃣ 🖼 🔜 `201`, "✍". ⚫️ 🛎 ⚙️ ⏮️ 🏗 🆕 ⏺ 💽. - * 🎁 💼 `204`, "🙅‍♂ 🎚". 👉 📨 ⚙️ 🕐❔ 📤 🙅‍♂ 🎚 📨 👩‍💻, & 📨 🔜 🚫 ✔️ 💪. -* **`300`** & 🔛 "❎". 📨 ⏮️ 👫 👔 📟 5️⃣📆 ⚖️ 5️⃣📆 🚫 ✔️ 💪, 🌖 `304`, "🚫 🔀", ❔ 🔜 🚫 ✔️ 1️⃣. -* **`400`** & 🔛 "👩‍💻 ❌" 📨. 👫 🥈 🆎 👆 🔜 🎲 ⚙️ 🏆. - * 🖼 `404`, "🚫 🔎" 📨. - * 💊 ❌ ⚪️➡️ 👩‍💻, 👆 💪 ⚙️ `400`. -* `500` & 🔛 💽 ❌. 👆 🌖 🙅 ⚙️ 👫 🔗. 🕐❔ 🕳 🚶 ❌ 🍕 👆 🈸 📟, ⚖️ 💽, ⚫️ 🔜 🔁 📨 1️⃣ 👫 👔 📟. - -/// tip - -💭 🌅 🔃 🔠 👔 📟 & ❔ 📟 ⚫️❔, ✅ 🏇 🧾 🔃 🇺🇸🔍 👔 📟. - -/// - -## ⌨ 💭 📛 - -➡️ 👀 ⏮️ 🖼 🔄: - -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} - -`201` 👔 📟 "✍". - -✋️ 👆 🚫 ✔️ ✍ ⚫️❔ 🔠 👉 📟 ⛓. - -👆 💪 ⚙️ 🏪 🔢 ⚪️➡️ `fastapi.status`. - -{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} - -👫 🏪, 👫 🧑‍🤝‍🧑 🎏 🔢, ✋️ 👈 🌌 👆 💪 ⚙️ 👨‍🎨 📋 🔎 👫: - - - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette import status`. - -**FastAPI** 🚚 🎏 `starlette.status` `fastapi.status` 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. - -/// - -## 🔀 🔢 - -⏪, [🏧 👩‍💻 🦮](../advanced/response-change-status-code.md){.internal-link target=_blank}, 👆 🔜 👀 ❔ 📨 🎏 👔 📟 🌘 🔢 👆 📣 📥. diff --git a/docs/em/docs/tutorial/schema-extra-example.md b/docs/em/docs/tutorial/schema-extra-example.md deleted file mode 100644 index 1bd314c513..0000000000 --- a/docs/em/docs/tutorial/schema-extra-example.md +++ /dev/null @@ -1,110 +0,0 @@ -# 📣 📨 🖼 💽 - -👆 💪 📣 🖼 💽 👆 📱 💪 📨. - -📥 📚 🌌 ⚫️. - -## Pydantic `schema_extra` - -👆 💪 📣 `example` Pydantic 🏷 ⚙️ `Config` & `schema_extra`, 🔬 Pydantic 🩺: 🔗 🛃: - -{* ../../docs_src/schema_extra_example/tutorial001.py hl[15:23] *} - -👈 ➕ ℹ 🔜 🚮-🔢 **🎻 🔗** 👈 🏷, & ⚫️ 🔜 ⚙️ 🛠️ 🩺. - -/// tip - -👆 💪 ⚙️ 🎏 ⚒ ↔ 🎻 🔗 & 🚮 👆 👍 🛃 ➕ ℹ. - -🖼 👆 💪 ⚙️ ⚫️ 🚮 🗃 🕸 👩‍💻 🔢, ♒️. - -/// - -## `Field` 🌖 ❌ - -🕐❔ ⚙️ `Field()` ⏮️ Pydantic 🏷, 👆 💪 📣 ➕ ℹ **🎻 🔗** 🚶‍♀️ 🙆 🎏 ❌ ❌ 🔢. - -👆 💪 ⚙️ 👉 🚮 `example` 🔠 🏑: - -{* ../../docs_src/schema_extra_example/tutorial002.py hl[4,10:13] *} - -/// warning - -🚧 🤯 👈 📚 ➕ ❌ 🚶‍♀️ 🏆 🚫 🚮 🙆 🔬, 🕴 ➕ ℹ, 🧾 🎯. - -/// - -## `example` & `examples` 🗄 - -🕐❔ ⚙️ 🙆: - -* `Path()` -* `Query()` -* `Header()` -* `Cookie()` -* `Body()` -* `Form()` -* `File()` - -👆 💪 📣 💽 `example` ⚖️ 👪 `examples` ⏮️ 🌖 ℹ 👈 🔜 🚮 **🗄**. - -### `Body` ⏮️ `example` - -📥 👥 🚶‍♀️ `example` 📊 ⌛ `Body()`: - -{* ../../docs_src/schema_extra_example/tutorial003.py hl[20:25] *} - -### 🖼 🩺 🎚 - -⏮️ 🙆 👩‍🔬 🔛 ⚫️ 🔜 👀 💖 👉 `/docs`: - - - -### `Body` ⏮️ 💗 `examples` - -👐 👁 `example`, 👆 💪 🚶‍♀️ `examples` ⚙️ `dict` ⏮️ **💗 🖼**, 🔠 ⏮️ ➕ ℹ 👈 🔜 🚮 **🗄** 💁‍♂️. - -🔑 `dict` 🔬 🔠 🖼, & 🔠 💲 ➕1️⃣ `dict`. - -🔠 🎯 🖼 `dict` `examples` 💪 🔌: - -* `summary`: 📏 📛 🖼. -* `description`: 📏 📛 👈 💪 🔌 ✍ ✍. -* `value`: 👉 ☑ 🖼 🎦, ✅ `dict`. -* `externalValue`: 🎛 `value`, 📛 ☝ 🖼. 👐 👉 5️⃣📆 🚫 🐕‍🦺 📚 🧰 `value`. - -{* ../../docs_src/schema_extra_example/tutorial004.py hl[21:47] *} - -### 🖼 🩺 🎚 - -⏮️ `examples` 🚮 `Body()` `/docs` 🔜 👀 💖: - - - -## 📡 ℹ - -/// warning - -👉 📶 📡 ℹ 🔃 🐩 **🎻 🔗** & **🗄**. - -🚥 💭 🔛 ⏪ 👷 👆, 👈 💪 🥃, & 👆 🎲 🚫 💪 👉 ℹ, 💭 🆓 🚶 👫. - -/// - -🕐❔ 👆 🚮 🖼 🔘 Pydantic 🏷, ⚙️ `schema_extra` ⚖️ `Field(example="something")` 👈 🖼 🚮 **🎻 🔗** 👈 Pydantic 🏷. - -& 👈 **🎻 🔗** Pydantic 🏷 🔌 **🗄** 👆 🛠️, & ⤴️ ⚫️ ⚙️ 🩺 🎚. - -**🎻 🔗** 🚫 🤙 ✔️ 🏑 `example` 🐩. ⏮️ ⏬ 🎻 🔗 🔬 🏑 `examples`, ✋️ 🗄 3️⃣.0️⃣.3️⃣ ⚓️ 🔛 🗝 ⏬ 🎻 🔗 👈 🚫 ✔️ `examples`. - -, 🗄 3️⃣.0️⃣.3️⃣ 🔬 🚮 👍 `example` 🔀 ⏬ **🎻 🔗** ⚫️ ⚙️, 🎏 🎯 (✋️ ⚫️ 👁 `example`, 🚫 `examples`), & 👈 ⚫️❔ ⚙️ 🛠️ 🩺 🎚 (⚙️ 🦁 🎚). - -, 👐 `example` 🚫 🍕 🎻 🔗, ⚫️ 🍕 🗄 🛃 ⏬ 🎻 🔗, & 👈 ⚫️❔ 🔜 ⚙️ 🩺 🎚. - -✋️ 🕐❔ 👆 ⚙️ `example` ⚖️ `examples` ⏮️ 🙆 🎏 🚙 (`Query()`, `Body()`, ♒️.) 📚 🖼 🚫 🚮 🎻 🔗 👈 🔬 👈 💽 (🚫 🗄 👍 ⏬ 🎻 🔗), 👫 🚮 🔗 *➡ 🛠️* 📄 🗄 (🏞 🍕 🗄 👈 ⚙️ 🎻 🔗). - -`Path()`, `Query()`, `Header()`, & `Cookie()`, `example` ⚖️ `examples` 🚮 🗄 🔑, `Parameter Object` (🔧). - -& `Body()`, `File()`, & `Form()`, `example` ⚖️ `examples` 📊 🚮 🗄 🔑, `Request Body Object`, 🏑 `content`, 🔛 `Media Type Object` (🔧). - -🔛 🎏 ✋, 📤 🆕 ⏬ 🗄: **3️⃣.1️⃣.0️⃣**, ⏳ 🚀. ⚫️ ⚓️ 🔛 ⏪ 🎻 🔗 & 🏆 🛠️ ⚪️➡️ 🗄 🛃 ⏬ 🎻 🔗 ❎, 💱 ⚒ ⚪️➡️ ⏮️ ⏬ 🎻 🔗, 🌐 👫 🤪 🔺 📉. 👐, 🦁 🎚 ⏳ 🚫 🐕‍🦺 🗄 3️⃣.1️⃣.0️⃣,, 🔜, ⚫️ 👍 😣 ⚙️ 💭 🔛. diff --git a/docs/em/docs/tutorial/security/first-steps.md b/docs/em/docs/tutorial/security/first-steps.md deleted file mode 100644 index 8fb459a651..0000000000 --- a/docs/em/docs/tutorial/security/first-steps.md +++ /dev/null @@ -1,197 +0,0 @@ -# 💂‍♂ - 🥇 🔁 - -➡️ 🌈 👈 👆 ✔️ 👆 **👩‍💻** 🛠️ 🆔. - -& 👆 ✔️ **🕸** ➕1️⃣ 🆔 ⚖️ 🎏 ➡ 🎏 🆔 (⚖️ 📱 🈸). - -& 👆 💚 ✔️ 🌌 🕸 🔓 ⏮️ 👩‍💻, ⚙️ **🆔** & **🔐**. - -👥 💪 ⚙️ **Oauth2️⃣** 🏗 👈 ⏮️ **FastAPI**. - -✋️ ➡️ 🖊 👆 🕰 👂 🌕 📏 🔧 🔎 👈 🐥 🍖 ℹ 👆 💪. - -➡️ ⚙️ 🧰 🚚 **FastAPI** 🍵 💂‍♂. - -## ❔ ⚫️ 👀 - -➡️ 🥇 ⚙️ 📟 & 👀 ❔ ⚫️ 👷, & ⤴️ 👥 🔜 👟 🔙 🤔 ⚫️❔ 😥. - -## ✍ `main.py` - -📁 🖼 📁 `main.py`: - -{* ../../docs_src/security/tutorial001.py *} - -## 🏃 ⚫️ - -/// info - -🥇 ❎ `python-multipart`. - -🤶 Ⓜ. `pip install python-multipart`. - -👉 ↩️ **Oauth2️⃣** ⚙️ "📨 📊" 📨 `username` & `password`. - -/// - -🏃 🖼 ⏮️: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -## ✅ ⚫️ - -🚶 🎓 🩺: http://127.0.0.1:8000/docs. - -👆 🔜 👀 🕳 💖 👉: - - - -/// check | ✔ 🔼 ❗ - -👆 ⏪ ✔️ ✨ 🆕 "✔" 🔼. - - & 👆 *➡ 🛠️* ✔️ 🐥 🔒 🔝-▶️️ ↩ 👈 👆 💪 🖊. - -/// - -& 🚥 👆 🖊 ⚫️, 👆 ✔️ 🐥 ✔ 📨 🆎 `username` & `password` (& 🎏 📦 🏑): - - - -/// note - -⚫️ 🚫 🤔 ⚫️❔ 👆 🆎 📨, ⚫️ 🏆 🚫 👷. ✋️ 👥 🔜 🤚 📤. - -/// - -👉 ↗️ 🚫 🕸 🏁 👩‍💻, ✋️ ⚫️ 👑 🏧 🧰 📄 🖥 🌐 👆 🛠️. - -⚫️ 💪 ⚙️ 🕸 🏉 (👈 💪 👆). - -⚫️ 💪 ⚙️ 🥉 🥳 🈸 & ⚙️. - -& ⚫️ 💪 ⚙️ 👆, ℹ, ✅ & 💯 🎏 🈸. - -## `password` 💧 - -🔜 ➡️ 🚶 🔙 👄 & 🤔 ⚫️❔ 🌐 👈. - -`password` "💧" 1️⃣ 🌌 ("💧") 🔬 Oauth2️⃣, 🍵 💂‍♂ & 🤝. - -Oauth2️⃣ 🔧 👈 👩‍💻 ⚖️ 🛠️ 💪 🔬 💽 👈 🔓 👩‍💻. - -✋️ 👉 💼, 🎏 **FastAPI** 🈸 🔜 🍵 🛠️ & 🤝. - -, ➡️ 📄 ⚫️ ⚪️➡️ 👈 📉 ☝ 🎑: - -* 👩‍💻 🆎 `username` & `password` 🕸, & 🎯 `Enter`. -* 🕸 (🏃‍♂ 👩‍💻 🖥) 📨 👈 `username` & `password` 🎯 📛 👆 🛠️ (📣 ⏮️ `tokenUrl="token"`). -* 🛠️ ✅ 👈 `username` & `password`, & 📨 ⏮️ "🤝" (👥 🚫 🛠️ 🙆 👉). - * "🤝" 🎻 ⏮️ 🎚 👈 👥 💪 ⚙️ ⏪ ✔ 👉 👩‍💻. - * 🛎, 🤝 ⚒ 🕛 ⏮️ 🕰. - * , 👩‍💻 🔜 ✔️ 🕹 🔄 ☝ ⏪. - * & 🚥 🤝 📎, ⚠ 🌘. ⚫️ 🚫 💖 🧲 🔑 👈 🔜 👷 ♾ (🏆 💼). -* 🕸 🏪 👈 🤝 🍕 👱. -* 👩‍💻 🖊 🕸 🚶 ➕1️⃣ 📄 🕸 🕸 📱. -* 🕸 💪 ☕ 🌅 💽 ⚪️➡️ 🛠️. - * ✋️ ⚫️ 💪 🤝 👈 🎯 🔗. - * , 🔓 ⏮️ 👆 🛠️, ⚫️ 📨 🎚 `Authorization` ⏮️ 💲 `Bearer ` ➕ 🤝. - * 🚥 🤝 🔌 `foobar`, 🎚 `Authorization` 🎚 🔜: `Bearer foobar`. - -## **FastAPI**'Ⓜ `OAuth2PasswordBearer` - -**FastAPI** 🚚 📚 🧰, 🎏 🎚 ⚛, 🛠️ 👫 💂‍♂ ⚒. - -👉 🖼 👥 🔜 ⚙️ **Oauth2️⃣**, ⏮️ **🔐** 💧, ⚙️ **📨** 🤝. 👥 👈 ⚙️ `OAuth2PasswordBearer` 🎓. - -/// info - -"📨" 🤝 🚫 🕴 🎛. - -✋️ ⚫️ 🏆 1️⃣ 👆 ⚙️ 💼. - - & ⚫️ 💪 🏆 🏆 ⚙️ 💼, 🚥 👆 Oauth2️⃣ 🕴 & 💭 ⚫️❔ ⚫️❔ 📤 ➕1️⃣ 🎛 👈 ♣ 👻 👆 💪. - -👈 💼, **FastAPI** 🚚 👆 ⏮️ 🧰 🏗 ⚫️. - -/// - -🕐❔ 👥 ✍ 👐 `OAuth2PasswordBearer` 🎓 👥 🚶‍♀️ `tokenUrl` 🔢. 👉 🔢 🔌 📛 👈 👩‍💻 (🕸 🏃 👩‍💻 🖥) 🔜 ⚙️ 📨 `username` & `password` ✔ 🤚 🤝. - -{* ../../docs_src/security/tutorial001.py hl[6] *} - -/// tip - -📥 `tokenUrl="token"` 🔗 ⚖ 📛 `token` 👈 👥 🚫 ✍. ⚫️ ⚖ 📛, ⚫️ 🌓 `./token`. - -↩️ 👥 ⚙️ ⚖ 📛, 🚥 👆 🛠️ 🔎 `https://example.com/`, ⤴️ ⚫️ 🔜 🔗 `https://example.com/token`. ✋️ 🚥 👆 🛠️ 🔎 `https://example.com/api/v1/`, ⤴️ ⚫️ 🔜 🔗 `https://example.com/api/v1/token`. - -⚙️ ⚖ 📛 ⚠ ⚒ 💭 👆 🈸 🚧 👷 🏧 ⚙️ 💼 💖 [⛅ 🗳](../../advanced/behind-a-proxy.md){.internal-link target=_blank}. - -/// - -👉 🔢 🚫 ✍ 👈 🔗 / *➡ 🛠️*, ✋️ 📣 👈 📛 `/token` 🔜 1️⃣ 👈 👩‍💻 🔜 ⚙️ 🤚 🤝. 👈 ℹ ⚙️ 🗄, & ⤴️ 🎓 🛠️ 🧾 ⚙️. - -👥 🔜 🔜 ✍ ☑ ➡ 🛠️. - -/// info - -🚥 👆 📶 ⚠ "✍" 👆 💪 👎 👗 🔢 📛 `tokenUrl` ↩️ `token_url`. - -👈 ↩️ ⚫️ ⚙️ 🎏 📛 🗄 🔌. 👈 🚥 👆 💪 🔬 🌅 🔃 🙆 👫 💂‍♂ ⚖ 👆 💪 📁 & 📋 ⚫️ 🔎 🌖 ℹ 🔃 ⚫️. - -/// - -`oauth2_scheme` 🔢 👐 `OAuth2PasswordBearer`, ✋️ ⚫️ "🇧🇲". - -⚫️ 💪 🤙: - -```Python -oauth2_scheme(some, parameters) -``` - -, ⚫️ 💪 ⚙️ ⏮️ `Depends`. - -### ⚙️ ⚫️ - -🔜 👆 💪 🚶‍♀️ 👈 `oauth2_scheme` 🔗 ⏮️ `Depends`. - -{* ../../docs_src/security/tutorial001.py hl[10] *} - -👉 🔗 🔜 🚚 `str` 👈 🛠️ 🔢 `token` *➡ 🛠️ 🔢*. - -**FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 👉 🔗 🔬 "💂‍♂ ⚖" 🗄 🔗 (& 🏧 🛠️ 🩺). - -/// info | 📡 ℹ - -**FastAPI** 🔜 💭 👈 ⚫️ 💪 ⚙️ 🎓 `OAuth2PasswordBearer` (📣 🔗) 🔬 💂‍♂ ⚖ 🗄 ↩️ ⚫️ 😖 ⚪️➡️ `fastapi.security.oauth2.OAuth2`, ❔ 🔄 😖 ⚪️➡️ `fastapi.security.base.SecurityBase`. - -🌐 💂‍♂ 🚙 👈 🛠️ ⏮️ 🗄 (& 🏧 🛠️ 🩺) 😖 ⚪️➡️ `SecurityBase`, 👈 ❔ **FastAPI** 💪 💭 ❔ 🛠️ 👫 🗄. - -/// - -## ⚫️❔ ⚫️ 🔨 - -⚫️ 🔜 🚶 & 👀 📨 👈 `Authorization` 🎚, ✅ 🚥 💲 `Bearer ` ➕ 🤝, & 🔜 📨 🤝 `str`. - -🚥 ⚫️ 🚫 👀 `Authorization` 🎚, ⚖️ 💲 🚫 ✔️ `Bearer ` 🤝, ⚫️ 🔜 📨 ⏮️ 4️⃣0️⃣1️⃣ 👔 📟 ❌ (`UNAUTHORIZED`) 🔗. - -👆 🚫 ✔️ ✅ 🚥 🤝 🔀 📨 ❌. 👆 💪 💭 👈 🚥 👆 🔢 🛠️, ⚫️ 🔜 ✔️ `str` 👈 🤝. - -👆 💪 🔄 ⚫️ ⏪ 🎓 🩺: - - - -👥 🚫 ✔ 🔬 🤝, ✋️ 👈 ▶️ ⏪. - -## 🌃 - -, 3️⃣ ⚖️ 4️⃣ ➕ ⏸, 👆 ⏪ ✔️ 🐒 📨 💂‍♂. diff --git a/docs/em/docs/tutorial/security/get-current-user.md b/docs/em/docs/tutorial/security/get-current-user.md deleted file mode 100644 index 2f4a26f352..0000000000 --- a/docs/em/docs/tutorial/security/get-current-user.md +++ /dev/null @@ -1,105 +0,0 @@ -# 🤚 ⏮️ 👩‍💻 - -⏮️ 📃 💂‍♂ ⚙️ (❔ 🧢 🔛 🔗 💉 ⚙️) 🤝 *➡ 🛠️ 🔢* `token` `str`: - -{* ../../docs_src/security/tutorial001.py hl[10] *} - -✋️ 👈 🚫 👈 ⚠. - -➡️ ⚒ ⚫️ 🤝 👥 ⏮️ 👩‍💻. - -## ✍ 👩‍💻 🏷 - -🥇, ➡️ ✍ Pydantic 👩‍💻 🏷. - -🎏 🌌 👥 ⚙️ Pydantic 📣 💪, 👥 💪 ⚙️ ⚫️ 🙆 🙆: - -{* ../../docs_src/security/tutorial002.py hl[5,12:16] *} - -## ✍ `get_current_user` 🔗 - -➡️ ✍ 🔗 `get_current_user`. - -💭 👈 🔗 💪 ✔️ 🎧-🔗 ❓ - -`get_current_user` 🔜 ✔️ 🔗 ⏮️ 🎏 `oauth2_scheme` 👥 ✍ ⏭. - -🎏 👥 🔨 ⏭ *➡ 🛠️* 🔗, 👆 🆕 🔗 `get_current_user` 🔜 📨 `token` `str` ⚪️➡️ 🎧-🔗 `oauth2_scheme`: - -{* ../../docs_src/security/tutorial002.py hl[25] *} - -## 🤚 👩‍💻 - -`get_current_user` 🔜 ⚙️ (❌) 🚙 🔢 👥 ✍, 👈 ✊ 🤝 `str` & 📨 👆 Pydantic `User` 🏷: - -{* ../../docs_src/security/tutorial002.py hl[19:22,26:27] *} - -## 💉 ⏮️ 👩‍💻 - -🔜 👥 💪 ⚙️ 🎏 `Depends` ⏮️ 👆 `get_current_user` *➡ 🛠️*: - -{* ../../docs_src/security/tutorial002.py hl[31] *} - -👀 👈 👥 📣 🆎 `current_user` Pydantic 🏷 `User`. - -👉 🔜 ℹ 🇺🇲 🔘 🔢 ⏮️ 🌐 🛠️ & 🆎 ✅. - -/// tip - -👆 5️⃣📆 💭 👈 📨 💪 📣 ⏮️ Pydantic 🏷. - -📥 **FastAPI** 🏆 🚫 🤚 😨 ↩️ 👆 ⚙️ `Depends`. - -/// - -/// check - -🌌 👉 🔗 ⚙️ 🏗 ✔ 👥 ✔️ 🎏 🔗 (🎏 "☑") 👈 🌐 📨 `User` 🏷. - -👥 🚫 🚫 ✔️ 🕴 1️⃣ 🔗 👈 💪 📨 👈 🆎 💽. - -/// - -## 🎏 🏷 - -👆 💪 🔜 🤚 ⏮️ 👩‍💻 🔗 *➡ 🛠️ 🔢* & 🙅 ⏮️ 💂‍♂ 🛠️ **🔗 💉** 🎚, ⚙️ `Depends`. - -& 👆 💪 ⚙️ 🙆 🏷 ⚖️ 💽 💂‍♂ 📄 (👉 💼, Pydantic 🏷 `User`). - -✋️ 👆 🚫 🚫 ⚙️ 🎯 💽 🏷, 🎓 ⚖️ 🆎. - -👆 💚 ✔️ `id` & `email` & 🚫 ✔️ 🙆 `username` 👆 🏷 ❓ 💭. 👆 💪 ⚙️ 👉 🎏 🧰. - -👆 💚 ✔️ `str`❓ ⚖️ `dict`❓ ⚖️ 💽 🎓 🏷 👐 🔗 ❓ ⚫️ 🌐 👷 🎏 🌌. - -👆 🤙 🚫 ✔️ 👩‍💻 👈 🕹 👆 🈸 ✋️ 🤖, 🤖, ⚖️ 🎏 ⚙️, 👈 ✔️ 🔐 🤝 ❓ 🔄, ⚫️ 🌐 👷 🎏. - -⚙️ 🙆 😇 🏷, 🙆 😇 🎓, 🙆 😇 💽 👈 👆 💪 👆 🈸. **FastAPI** ✔️ 👆 📔 ⏮️ 🔗 💉 ⚙️. - -## 📟 📐 - -👉 🖼 5️⃣📆 😑 🔁. ✔️ 🤯 👈 👥 🌀 💂‍♂, 📊 🏷, 🚙 🔢 & *➡ 🛠️* 🎏 📁. - -✋️ 📥 🔑 ☝. - -💂‍♂ & 🔗 💉 💩 ✍ 🕐. - -& 👆 💪 ⚒ ⚫️ 🏗 👆 💚. & , ✔️ ⚫️ ✍ 🕴 🕐, 👁 🥉. ⏮️ 🌐 💪. - -✋️ 👆 💪 ✔️ 💯 🔗 (*➡ 🛠️*) ⚙️ 🎏 💂‍♂ ⚙️. - -& 🌐 👫 (⚖️ 🙆 ↔ 👫 👈 👆 💚) 💪 ✊ 📈 🏤-⚙️ 👫 🔗 ⚖️ 🙆 🎏 🔗 👆 ✍. - -& 🌐 👉 💯 *➡ 🛠️* 💪 🤪 3️⃣ ⏸: - -{* ../../docs_src/security/tutorial002.py hl[30:32] *} - -## 🌃 - -👆 💪 🔜 🤚 ⏮️ 👩‍💻 🔗 👆 *➡ 🛠️ 🔢*. - -👥 ⏪ 😬 📤. - -👥 💪 🚮 *➡ 🛠️* 👩‍💻/👩‍💻 🤙 📨 `username` & `password`. - -👈 👟 ⏭. diff --git a/docs/em/docs/tutorial/security/index.md b/docs/em/docs/tutorial/security/index.md deleted file mode 100644 index 1a47e55102..0000000000 --- a/docs/em/docs/tutorial/security/index.md +++ /dev/null @@ -1,106 +0,0 @@ -# 💂‍♂ - -📤 📚 🌌 🍵 💂‍♂, 🤝 & ✔. - -& ⚫️ 🛎 🏗 & "⚠" ❔. - -📚 🛠️ & ⚙️ 🍵 💂‍♂ & 🤝 ✊ 🦏 💸 🎯 & 📟 (📚 💼 ⚫️ 💪 5️⃣0️⃣ 💯 ⚖️ 🌅 🌐 📟 ✍). - -**FastAPI** 🚚 📚 🧰 ℹ 👆 🙅 ⏮️ **💂‍♂** 💪, 📉, 🐩 🌌, 🍵 ✔️ 🔬 & 💡 🌐 💂‍♂ 🔧. - -✋️ 🥇, ➡️ ✅ 🤪 🔧. - -## 🏃 ❓ - -🚥 👆 🚫 💅 🔃 🙆 👉 ⚖ & 👆 💪 🚮 💂‍♂ ⏮️ 🤝 ⚓️ 🔛 🆔 & 🔐 *▶️️ 🔜*, 🚶 ⏭ 📃. - -## Oauth2️⃣ - -Oauth2️⃣ 🔧 👈 🔬 📚 🌌 🍵 🤝 & ✔. - -⚫️ 🔬 🔧 & 📔 📚 🏗 ⚙️ 💼. - -⚫️ 🔌 🌌 🔓 ⚙️ "🥉 🥳". - -👈 ⚫️❔ 🌐 ⚙️ ⏮️ "💳 ⏮️ 👱📔, 🇺🇸🔍, 👱📔, 📂" ⚙️ 🔘. - -### ✳ 1️⃣ - -📤 ✳ 1️⃣, ❔ 📶 🎏 ⚪️➡️ Oauth2️⃣, & 🌖 🏗, ⚫️ 🔌 🔗 🔧 🔛 ❔ 🗜 📻. - -⚫️ 🚫 📶 🌟 ⚖️ ⚙️ 🛎. - -Oauth2️⃣ 🚫 ✔ ❔ 🗜 📻, ⚫️ ⌛ 👆 ✔️ 👆 🈸 🍦 ⏮️ 🇺🇸🔍. - -/// tip - -📄 🔃 **🛠️** 👆 🔜 👀 ❔ ⚒ 🆙 🇺🇸🔍 🆓, ⚙️ Traefik & ➡️ 🗜. - -/// - -## 👩‍💻 🔗 - -👩‍💻 🔗 ➕1️⃣ 🔧, 🧢 🔛 **Oauth2️⃣**. - -⚫️ ↔ Oauth2️⃣ ✔ 👜 👈 📶 🌌 Oauth2️⃣, 🔄 ⚒ ⚫️ 🌅 🛠️. - -🖼, 🇺🇸🔍 💳 ⚙️ 👩‍💻 🔗 (❔ 🔘 ⚙️ Oauth2️⃣). - -✋️ 👱📔 💳 🚫 🐕‍🦺 👩‍💻 🔗. ⚫️ ✔️ 🚮 👍 🍛 Oauth2️⃣. - -### 👩‍💻 (🚫 "👩‍💻 🔗") - -📤 "👩‍💻" 🔧. 👈 🔄 ❎ 🎏 👜 **👩‍💻 🔗**, ✋️ 🚫 ⚓️ 🔛 Oauth2️⃣. - -, ⚫️ 🏁 🌖 ⚙️. - -⚫️ 🚫 📶 🌟 ⚖️ ⚙️ 🛎. - -## 🗄 - -🗄 (⏪ 💭 🦁) 📂 🔧 🏗 🔗 (🔜 🍕 💾 🏛). - -**FastAPI** ⚓️ 🔛 **🗄**. - -👈 ⚫️❔ ⚒ ⚫️ 💪 ✔️ 💗 🏧 🎓 🧾 🔢, 📟 ⚡, ♒️. - -🗄 ✔️ 🌌 🔬 💗 💂‍♂ "⚖". - -⚙️ 👫, 👆 💪 ✊ 📈 🌐 👫 🐩-⚓️ 🧰, 🔌 👉 🎓 🧾 ⚙️. - -🗄 🔬 📄 💂‍♂ ⚖: - -* `apiKey`: 🈸 🎯 🔑 👈 💪 👟 ⚪️➡️: - * 🔢 🔢. - * 🎚. - * 🍪. -* `http`: 🐩 🇺🇸🔍 🤝 ⚙️, 🔌: - * `bearer`: 🎚 `Authorization` ⏮️ 💲 `Bearer ` ➕ 🤝. 👉 😖 ⚪️➡️ Oauth2️⃣. - * 🇺🇸🔍 🔰 🤝. - * 🇺🇸🔍 📰, ♒️. -* `oauth2`: 🌐 Oauth2️⃣ 🌌 🍵 💂‍♂ (🤙 "💧"). - * 📚 👫 💧 ☑ 🏗 ✳ 2️⃣.0️⃣ 🤝 🐕‍🦺 (💖 🇺🇸🔍, 👱📔, 👱📔, 📂, ♒️): - * `implicit` - * `clientCredentials` - * `authorizationCode` - * ✋️ 📤 1️⃣ 🎯 "💧" 👈 💪 👌 ⚙️ 🚚 🤝 🎏 🈸 🔗: - * `password`: ⏭ 📃 🔜 📔 🖼 👉. -* `openIdConnect`: ✔️ 🌌 🔬 ❔ 🔎 Oauth2️⃣ 🤝 📊 🔁. - * 👉 🏧 🔍 ⚫️❔ 🔬 👩‍💻 🔗 🔧. - - -/// tip - -🛠️ 🎏 🤝/✔ 🐕‍🦺 💖 🇺🇸🔍, 👱📔, 👱📔, 📂, ♒️. 💪 & 📶 ⏩. - -🌅 🏗 ⚠ 🏗 🤝/✔ 🐕‍🦺 💖 👈, ✋️ **FastAPI** 🤝 👆 🧰 ⚫️ 💪, ⏪ 🔨 🏋️ 🏋‍♂ 👆. - -/// - -## **FastAPI** 🚙 - -FastAPI 🚚 📚 🧰 🔠 👉 💂‍♂ ⚖ `fastapi.security` 🕹 👈 📉 ⚙️ 👉 💂‍♂ 🛠️. - -⏭ 📃 👆 🔜 👀 ❔ 🚮 💂‍♂ 👆 🛠️ ⚙️ 📚 🧰 🚚 **FastAPI**. - -& 👆 🔜 👀 ❔ ⚫️ 🤚 🔁 🛠️ 🔘 🎓 🧾 ⚙️. diff --git a/docs/em/docs/tutorial/security/oauth2-jwt.md b/docs/em/docs/tutorial/security/oauth2-jwt.md deleted file mode 100644 index ee7bc2d28a..0000000000 --- a/docs/em/docs/tutorial/security/oauth2-jwt.md +++ /dev/null @@ -1,275 +0,0 @@ -# Oauth2️⃣ ⏮️ 🔐 (& 🔁), 📨 ⏮️ 🥙 🤝 - -🔜 👈 👥 ✔️ 🌐 💂‍♂ 💧, ➡️ ⚒ 🈸 🤙 🔐, ⚙️ 🥙 🤝 & 🔐 🔐 🔁. - -👉 📟 🕳 👆 💪 🤙 ⚙️ 👆 🈸, 🖊 🔐 #️⃣ 👆 💽, ♒️. - -👥 🔜 ▶️ ⚪️➡️ 🌐❔ 👥 ◀️ ⏮️ 📃 & 📈 ⚫️. - -## 🔃 🥙 - -🥙 ⛓ "🎻 🕸 🤝". - -⚫️ 🐩 🚫 🎻 🎚 📏 💧 🎻 🍵 🚀. ⚫️ 👀 💖 👉: - -``` -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c -``` - -⚫️ 🚫 🗜,, 🙆 💪 🛡 ℹ ⚪️➡️ 🎚. - -✋️ ⚫️ 🛑. , 🕐❔ 👆 📨 🤝 👈 👆 ♨, 👆 💪 ✔ 👈 👆 🤙 ♨ ⚫️. - -👈 🌌, 👆 💪 ✍ 🤝 ⏮️ 👔, ➡️ 💬, 1️⃣ 🗓️. & ⤴️ 🕐❔ 👩‍💻 👟 🔙 ⏭ 📆 ⏮️ 🤝, 👆 💭 👈 👩‍💻 🕹 👆 ⚙️. - -⏮️ 🗓️, 🤝 🔜 🕛 & 👩‍💻 🔜 🚫 ✔ & 🔜 ✔️ 🛑 🔄 🤚 🆕 🤝. & 🚥 👩‍💻 (⚖️ 🥉 🥳) 🔄 🔀 🤝 🔀 👔, 👆 🔜 💪 🔎 ⚫️, ↩️ 💳 🔜 🚫 🏏. - -🚥 👆 💚 🤾 ⏮️ 🥙 🤝 & 👀 ❔ 👫 👷, ✅ https://jwt.io. - -## ❎ `python-jose` - -👥 💪 ❎ `python-jose` 🏗 & ✔ 🥙 🤝 🐍: - -
- -```console -$ pip install "python-jose[cryptography]" - ----> 100% -``` - -
- -🐍-🇩🇬 🚚 🔐 👩‍💻 ➕. - -📥 👥 ⚙️ 👍 1️⃣: )/⚛. - -/// tip - -👉 🔰 ⏪ ⚙️ PyJWT. - -✋️ ⚫️ ℹ ⚙️ 🐍-🇩🇬 ↩️ ⚫️ 🚚 🌐 ⚒ ⚪️➡️ PyJWT ➕ ➕ 👈 👆 💪 💪 ⏪ 🕐❔ 🏗 🛠️ ⏮️ 🎏 🧰. - -/// - -## 🔐 🔁 - -"🔁" ⛓ 🏭 🎚 (🔐 👉 💼) 🔘 🔁 🔢 (🎻) 👈 👀 💖 🙃. - -🕐❔ 👆 🚶‍♀️ ⚫️❔ 🎏 🎚 (⚫️❔ 🎏 🔐) 👆 🤚 ⚫️❔ 🎏 🙃. - -✋️ 👆 🚫🔜 🗜 ⚪️➡️ 🙃 🔙 🔐. - -### ⚫️❔ ⚙️ 🔐 🔁 - -🚥 👆 💽 📎, 🧙‍♀ 🏆 🚫 ✔️ 👆 👩‍💻' 🔢 🔐, 🕴#️⃣. - -, 🧙‍♀ 🏆 🚫 💪 🔄 ⚙️ 👈 🔐 ➕1️⃣ ⚙️ (📚 👩‍💻 ⚙️ 🎏 🔐 🌐, 👉 🔜 ⚠). - -## ❎ `passlib` - -🇸🇲 👑 🐍 📦 🍵 🔐#️⃣. - -⚫️ 🐕‍🦺 📚 🔐 🔁 📊 & 🚙 👷 ⏮️ 👫. - -👍 📊 "🐡". - -, ❎ 🇸🇲 ⏮️ 🐡: - -
- -```console -$ pip install "passlib[bcrypt]" - ----> 100% -``` - -
- -/// tip - -⏮️ `passlib`, 👆 💪 🔗 ⚫️ 💪 ✍ 🔐 ✍ **✳**, **🏺** 💂‍♂ 🔌-⚖️ 📚 🎏. - -, 👆 🔜 💪, 🖼, 💰 🎏 📊 ⚪️➡️ ✳ 🈸 💽 ⏮️ FastAPI 🈸. ⚖️ 📉 ↔ ✳ 🈸 ⚙️ 🎏 💽. - - & 👆 👩‍💻 🔜 💪 💳 ⚪️➡️ 👆 ✳ 📱 ⚖️ ⚪️➡️ 👆 **FastAPI** 📱, 🎏 🕰. - -/// - -## #️⃣ & ✔ 🔐 - -🗄 🧰 👥 💪 ⚪️➡️ `passlib`. - -✍ 🇸🇲 "🔑". 👉 ⚫️❔ 🔜 ⚙️ #️⃣ & ✔ 🔐. - -/// tip - -🇸🇲 🔑 ✔️ 🛠️ ⚙️ 🎏 🔁 📊, 🔌 😢 🗝 🕐 🕴 ✔ ✔ 👫, ♒️. - -🖼, 👆 💪 ⚙️ ⚫️ ✍ & ✔ 🔐 🏗 ➕1️⃣ ⚙️ (💖 ✳) ✋️ #️⃣ 🙆 🆕 🔐 ⏮️ 🎏 📊 💖 🐡. - - & 🔗 ⏮️ 🌐 👫 🎏 🕰. - -/// - -✍ 🚙 🔢 #️⃣ 🔐 👟 ⚪️➡️ 👩‍💻. - -& ➕1️⃣ 🚙 ✔ 🚥 📨 🔐 🏏 #️⃣ 🏪. - -& ➕1️⃣ 1️⃣ 🔓 & 📨 👩‍💻. - -{* ../../docs_src/security/tutorial004.py hl[7,48,55:56,59:60,69:75] *} - -/// note - -🚥 👆 ✅ 🆕 (❌) 💽 `fake_users_db`, 👆 🔜 👀 ❔ #️⃣ 🔐 👀 💖 🔜: `"$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW"`. - -/// - -## 🍵 🥙 🤝 - -🗄 🕹 ❎. - -✍ 🎲 ㊙ 🔑 👈 🔜 ⚙️ 🛑 🥙 🤝. - -🏗 🔐 🎲 ㊙ 🔑 ⚙️ 📋: - -
- -```console -$ openssl rand -hex 32 - -09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7 -``` - -
- -& 📁 🔢 🔢 `SECRET_KEY` (🚫 ⚙️ 1️⃣ 🖼). - -✍ 🔢 `ALGORITHM` ⏮️ 📊 ⚙️ 🛑 🥙 🤝 & ⚒ ⚫️ `"HS256"`. - -✍ 🔢 👔 🤝. - -🔬 Pydantic 🏷 👈 🔜 ⚙️ 🤝 🔗 📨. - -✍ 🚙 🔢 🏗 🆕 🔐 🤝. - -{* ../../docs_src/security/tutorial004.py hl[6,12:14,28:30,78:86] *} - -## ℹ 🔗 - -ℹ `get_current_user` 📨 🎏 🤝 ⏭, ✋️ 👉 🕰, ⚙️ 🥙 🤝. - -🔣 📨 🤝, ✔ ⚫️, & 📨 ⏮️ 👩‍💻. - -🚥 🤝 ❌, 📨 🇺🇸🔍 ❌ ▶️️ ↖️. - -{* ../../docs_src/security/tutorial004.py hl[89:106] *} - -## ℹ `/token` *➡ 🛠️* - -✍ `timedelta` ⏮️ 👔 🕰 🤝. - -✍ 🎰 🥙 🔐 🤝 & 📨 ⚫️. - -{* ../../docs_src/security/tutorial004.py hl[115:130] *} - -### 📡 ℹ 🔃 🥙 "📄" `sub` - -🥙 🔧 💬 👈 📤 🔑 `sub`, ⏮️ 📄 🤝. - -⚫️ 📦 ⚙️ ⚫️, ✋️ 👈 🌐❔ 👆 🔜 🚮 👩‍💻 🆔, 👥 ⚙️ ⚫️ 📥. - -🥙 5️⃣📆 ⚙️ 🎏 👜 ↖️ ⚪️➡️ ⚖ 👩‍💻 & 🤝 👫 🎭 🛠️ 🔗 🔛 👆 🛠️. - -🖼, 👆 💪 🔬 "🚘" ⚖️ "📰 🏤". - -⤴️ 👆 💪 🚮 ✔ 🔃 👈 👨‍💼, 💖 "💾" (🚘) ⚖️ "✍" (📰). - -& ⤴️, 👆 💪 🤝 👈 🥙 🤝 👩‍💻 (⚖️ 🤖), & 👫 💪 ⚙️ ⚫️ 🎭 👈 🎯 (💾 🚘, ⚖️ ✍ 📰 🏤) 🍵 💆‍♂ ✔️ 🏧, ⏮️ 🥙 🤝 👆 🛠️ 🏗 👈. - -⚙️ 👫 💭, 🥙 💪 ⚙️ 🌌 🌖 🤓 😐. - -📚 💼, 📚 👈 👨‍💼 💪 ✔️ 🎏 🆔, ➡️ 💬 `foo` (👩‍💻 `foo`, 🚘 `foo`, & 📰 🏤 `foo`). - -, ❎ 🆔 💥, 🕐❔ 🏗 🥙 🤝 👩‍💻, 👆 💪 🔡 💲 `sub` 🔑, ✅ ⏮️ `username:`. , 👉 🖼, 💲 `sub` 💪 ✔️: `username:johndoe`. - -⚠ 👜 ✔️ 🤯 👈 `sub` 🔑 🔜 ✔️ 😍 🆔 🤭 🎂 🈸, & ⚫️ 🔜 🎻. - -## ✅ ⚫️ - -🏃 💽 & 🚶 🩺: http://127.0.0.1:8000/docs. - -👆 🔜 👀 👩‍💻 🔢 💖: - - - -✔ 🈸 🎏 🌌 ⏭. - -⚙️ 🎓: - -🆔: `johndoe` -🔐: `secret` - -/// check - -👀 👈 🕳 📟 🔢 🔐 "`secret`", 👥 🕴 ✔️ #️⃣ ⏬. - -/// - - - -🤙 🔗 `/users/me/`, 👆 🔜 🤚 📨: - -```JSON -{ - "username": "johndoe", - "email": "johndoe@example.com", - "full_name": "John Doe", - "disabled": false -} -``` - - - -🚥 👆 📂 👩‍💻 🧰, 👆 💪 👀 ❔ 📊 📨 🕴 🔌 🤝, 🔐 🕴 📨 🥇 📨 🔓 👩‍💻 & 🤚 👈 🔐 🤝, ✋️ 🚫 ⏮️: - - - -/// note - -👀 🎚 `Authorization`, ⏮️ 💲 👈 ▶️ ⏮️ `Bearer `. - -/// - -## 🏧 ⚙️ ⏮️ `scopes` - -Oauth2️⃣ ✔️ 🔑 "↔". - -👆 💪 ⚙️ 👫 🚮 🎯 ⚒ ✔ 🥙 🤝. - -⤴️ 👆 💪 🤝 👉 🤝 👩‍💻 🔗 ⚖️ 🥉 🥳, 🔗 ⏮️ 👆 🛠️ ⏮️ ⚒ 🚫. - -👆 💪 💡 ❔ ⚙️ 👫 & ❔ 👫 🛠️ 🔘 **FastAPI** ⏪ **🏧 👩‍💻 🦮**. - -## 🌃 - -⏮️ ⚫️❔ 👆 ✔️ 👀 🆙 🔜, 👆 💪 ⚒ 🆙 🔐 **FastAPI** 🈸 ⚙️ 🐩 💖 Oauth2️⃣ & 🥙. - -🌖 🙆 🛠️ 🚚 💂‍♂ ▶️️ 👍 🏗 📄 🔜. - -📚 📦 👈 📉 ⚫️ 📚 ✔️ ⚒ 📚 ⚠ ⏮️ 💽 🏷, 💽, & 💪 ⚒. & 👉 📦 👈 📉 👜 💁‍♂️ 🌅 🤙 ✔️ 💂‍♂ ⚠ 🔘. - ---- - -**FastAPI** 🚫 ⚒ 🙆 ⚠ ⏮️ 🙆 💽, 💽 🏷 ⚖️ 🧰. - -⚫️ 🤝 👆 🌐 💪 ⚒ 🕐 👈 👖 👆 🏗 🏆. - -& 👆 💪 ⚙️ 🔗 📚 👍 🚧 & 🛎 ⚙️ 📦 💖 `passlib` & `python-jose`, ↩️ **FastAPI** 🚫 🚚 🙆 🏗 🛠️ 🛠️ 🔢 📦. - -✋️ ⚫️ 🚚 👆 🧰 📉 🛠️ 🌅 💪 🍵 🎯 💪, ⚖, ⚖️ 💂‍♂. - -& 👆 💪 ⚙️ & 🛠️ 🔐, 🐩 🛠️, 💖 Oauth2️⃣ 📶 🙅 🌌. - -👆 💪 💡 🌅 **🏧 👩‍💻 🦮** 🔃 ❔ ⚙️ Oauth2️⃣ "↔", 🌖 👌-🧽 ✔ ⚙️, 📄 👫 🎏 🐩. Oauth2️⃣ ⏮️ ↔ 🛠️ ⚙️ 📚 🦏 🤝 🐕‍🦺, 💖 👱📔, 🇺🇸🔍, 📂, 🤸‍♂, 👱📔, ♒️. ✔ 🥉 🥳 🈸 🔗 ⏮️ 👫 🔗 🔛 👨‍💼 👫 👩‍💻. diff --git a/docs/em/docs/tutorial/security/simple-oauth2.md b/docs/em/docs/tutorial/security/simple-oauth2.md deleted file mode 100644 index 1fd513d48d..0000000000 --- a/docs/em/docs/tutorial/security/simple-oauth2.md +++ /dev/null @@ -1,289 +0,0 @@ -# 🙅 Oauth2️⃣ ⏮️ 🔐 & 📨 - -🔜 ➡️ 🏗 ⚪️➡️ ⏮️ 📃 & 🚮 ❌ 🍕 ✔️ 🏁 💂‍♂ 💧. - -## 🤚 `username` & `password` - -👥 🔜 ⚙️ **FastAPI** 💂‍♂ 🚙 🤚 `username` & `password`. - -Oauth2️⃣ ✔ 👈 🕐❔ ⚙️ "🔐 💧" (👈 👥 ⚙️) 👩‍💻/👩‍💻 🔜 📨 `username` & `password` 🏑 📨 💽. - -& 🔌 💬 👈 🏑 ✔️ 🌟 💖 👈. `user-name` ⚖️ `email` 🚫🔜 👷. - -✋️ 🚫 😟, 👆 💪 🎦 ⚫️ 👆 🎋 👆 🏁 👩‍💻 🕸. - -& 👆 💽 🏷 💪 ⚙️ 🙆 🎏 📛 👆 💚. - -✋️ 💳 *➡ 🛠️*, 👥 💪 ⚙️ 👉 📛 🔗 ⏮️ 🔌 (& 💪, 🖼, ⚙️ 🛠️ 🛠️ 🧾 ⚙️). - -🔌 🇵🇸 👈 `username` & `password` 🔜 📨 📨 💽 (, 🙅‍♂ 🎻 📥). - -### `scope` - -🔌 💬 👈 👩‍💻 💪 📨 ➕1️⃣ 📨 🏑 "`scope`". - -📨 🏑 📛 `scope` (⭐), ✋️ ⚫️ 🤙 📏 🎻 ⏮️ "↔" 🎏 🚀. - -🔠 "↔" 🎻 (🍵 🚀). - -👫 🛎 ⚙️ 📣 🎯 💂‍♂ ✔, 🖼: - -* `users:read` ⚖️ `users:write` ⚠ 🖼. -* `instagram_basic` ⚙️ 👱📔 / 👱📔. -* `https://www.googleapis.com/auth/drive` ⚙️ 🇺🇸🔍. - -/// info - -Oauth2️⃣ "↔" 🎻 👈 📣 🎯 ✔ ✔. - -⚫️ 🚫 🤔 🚥 ⚫️ ✔️ 🎏 🦹 💖 `:` ⚖️ 🚥 ⚫️ 📛. - -👈 ℹ 🛠️ 🎯. - -Oauth2️⃣ 👫 🎻. - -/// - -## 📟 🤚 `username` & `password` - -🔜 ➡️ ⚙️ 🚙 🚚 **FastAPI** 🍵 👉. - -### `OAuth2PasswordRequestForm` - -🥇, 🗄 `OAuth2PasswordRequestForm`, & ⚙️ ⚫️ 🔗 ⏮️ `Depends` *➡ 🛠️* `/token`: - -{* ../../docs_src/security/tutorial003.py hl[4,76] *} - -`OAuth2PasswordRequestForm` 🎓 🔗 👈 📣 📨 💪 ⏮️: - -* `username`. -* `password`. -* 📦 `scope` 🏑 🦏 🎻, ✍ 🎻 🎏 🚀. -* 📦 `grant_type`. - -/// tip - -Oauth2️⃣ 🔌 🤙 *🚚* 🏑 `grant_type` ⏮️ 🔧 💲 `password`, ✋️ `OAuth2PasswordRequestForm` 🚫 🛠️ ⚫️. - -🚥 👆 💪 🛠️ ⚫️, ⚙️ `OAuth2PasswordRequestFormStrict` ↩️ `OAuth2PasswordRequestForm`. - -/// - -* 📦 `client_id` (👥 🚫 💪 ⚫️ 👆 🖼). -* 📦 `client_secret` (👥 🚫 💪 ⚫️ 👆 🖼). - -/// info - -`OAuth2PasswordRequestForm` 🚫 🎁 🎓 **FastAPI** `OAuth2PasswordBearer`. - -`OAuth2PasswordBearer` ⚒ **FastAPI** 💭 👈 ⚫️ 💂‍♂ ⚖. ⚫️ 🚮 👈 🌌 🗄. - -✋️ `OAuth2PasswordRequestForm` 🎓 🔗 👈 👆 💪 ✔️ ✍ 👆, ⚖️ 👆 💪 ✔️ 📣 `Form` 🔢 🔗. - -✋️ ⚫️ ⚠ ⚙️ 💼, ⚫️ 🚚 **FastAPI** 🔗, ⚒ ⚫️ ⏩. - -/// - -### ⚙️ 📨 💽 - -/// tip - -👐 🔗 🎓 `OAuth2PasswordRequestForm` 🏆 🚫 ✔️ 🔢 `scope` ⏮️ 📏 🎻 👽 🚀, ↩️, ⚫️ 🔜 ✔️ `scopes` 🔢 ⏮️ ☑ 📇 🎻 🔠 ↔ 📨. - -👥 🚫 ⚙️ `scopes` 👉 🖼, ✋️ 🛠️ 📤 🚥 👆 💪 ⚫️. - -/// - -🔜, 🤚 👩‍💻 📊 ⚪️➡️ (❌) 💽, ⚙️ `username` ⚪️➡️ 📨 🏑. - -🚥 📤 🙅‍♂ ✅ 👩‍💻, 👥 📨 ❌ 💬 "❌ 🆔 ⚖️ 🔐". - -❌, 👥 ⚙️ ⚠ `HTTPException`: - -{* ../../docs_src/security/tutorial003.py hl[3,77:79] *} - -### ✅ 🔐 - -👉 ☝ 👥 ✔️ 👩‍💻 📊 ⚪️➡️ 👆 💽, ✋️ 👥 🚫 ✅ 🔐. - -➡️ 🚮 👈 💽 Pydantic `UserInDB` 🏷 🥇. - -👆 🔜 🙅 🖊 🔢 🔐,, 👥 🔜 ⚙️ (❌) 🔐 🔁 ⚙️. - -🚥 🔐 🚫 🏏, 👥 📨 🎏 ❌. - -#### 🔐 🔁 - -"🔁" ⛓: 🏭 🎚 (🔐 👉 💼) 🔘 🔁 🔢 (🎻) 👈 👀 💖 🙃. - -🕐❔ 👆 🚶‍♀️ ⚫️❔ 🎏 🎚 (⚫️❔ 🎏 🔐) 👆 🤚 ⚫️❔ 🎏 🙃. - -✋️ 👆 🚫🔜 🗜 ⚪️➡️ 🙃 🔙 🔐. - -##### ⚫️❔ ⚙️ 🔐 🔁 - -🚥 👆 💽 📎, 🧙‍♀ 🏆 🚫 ✔️ 👆 👩‍💻' 🔢 🔐, 🕴#️⃣. - -, 🧙‍♀ 🏆 🚫 💪 🔄 ⚙️ 👈 🎏 🔐 ➕1️⃣ ⚙️ (📚 👩‍💻 ⚙️ 🎏 🔐 🌐, 👉 🔜 ⚠). - -{* ../../docs_src/security/tutorial003.py hl[80:83] *} - -#### 🔃 `**user_dict` - -`UserInDB(**user_dict)` ⛓: - -*🚶‍♀️ 🔑 & 💲 `user_dict` 🔗 🔑-💲 ❌, 🌓:* - -```Python -UserInDB( - username = user_dict["username"], - email = user_dict["email"], - full_name = user_dict["full_name"], - disabled = user_dict["disabled"], - hashed_password = user_dict["hashed_password"], -) -``` - -/// info - -🌅 🏁 🔑 `**👩‍💻_ #️⃣ ` ✅ 🔙 [🧾 **➕ 🏷**](../extra-models.md#user_indict){.internal-link target=_blank}. - -/// - -## 📨 🤝 - -📨 `token` 🔗 🔜 🎻 🎚. - -⚫️ 🔜 ✔️ `token_type`. 👆 💼, 👥 ⚙️ "📨" 🤝, 🤝 🆎 🔜 "`bearer`". - -& ⚫️ 🔜 ✔️ `access_token`, ⏮️ 🎻 ⚗ 👆 🔐 🤝. - -👉 🙅 🖼, 👥 🔜 🍕 😟 & 📨 🎏 `username` 🤝. - -/// tip - -⏭ 📃, 👆 🔜 👀 🎰 🔐 🛠️, ⏮️ 🔐 #️⃣ & 🥙 🤝. - -✋️ 🔜, ➡️ 🎯 🔛 🎯 ℹ 👥 💪. - -/// - -{* ../../docs_src/security/tutorial003.py hl[85] *} - -/// tip - -🔌, 👆 🔜 📨 🎻 ⏮️ `access_token` & `token_type`, 🎏 👉 🖼. - -👉 🕳 👈 👆 ✔️ 👆 👆 📟, & ⚒ 💭 👆 ⚙️ 📚 🎻 🔑. - -⚫️ 🌖 🕴 👜 👈 👆 ✔️ 💭 ☑ 👆, 🛠️ ⏮️ 🔧. - -🎂, **FastAPI** 🍵 ⚫️ 👆. - -/// - -## ℹ 🔗 - -🔜 👥 🔜 ℹ 👆 🔗. - -👥 💚 🤚 `current_user` *🕴* 🚥 👉 👩‍💻 🦁. - -, 👥 ✍ 🌖 🔗 `get_current_active_user` 👈 🔄 ⚙️ `get_current_user` 🔗. - -👯‍♂️ 👉 🔗 🔜 📨 🇺🇸🔍 ❌ 🚥 👩‍💻 🚫 🔀, ⚖️ 🚥 🔕. - -, 👆 🔗, 👥 🔜 🕴 🤚 👩‍💻 🚥 👩‍💻 🔀, ☑ 🔓, & 🦁: - -{* ../../docs_src/security/tutorial003.py hl[58:66,69:72,90] *} - -/// info - -🌖 🎚 `WWW-Authenticate` ⏮️ 💲 `Bearer` 👥 🛬 📥 🍕 🔌. - -🙆 🇺🇸🔍 (❌) 👔 📟 4️⃣0️⃣1️⃣ "⛔" 🤔 📨 `WWW-Authenticate` 🎚. - -💼 📨 🤝 (👆 💼), 💲 👈 🎚 🔜 `Bearer`. - -👆 💪 🤙 🚶 👈 ➕ 🎚 & ⚫️ 🔜 👷. - -✋️ ⚫️ 🚚 📥 🛠️ ⏮️ 🔧. - -, 📤 5️⃣📆 🧰 👈 ⌛ & ⚙️ ⚫️ (🔜 ⚖️ 🔮) & 👈 💪 ⚠ 👆 ⚖️ 👆 👩‍💻, 🔜 ⚖️ 🔮. - -👈 💰 🐩... - -/// - -## 👀 ⚫️ 🎯 - -📂 🎓 🩺: http://127.0.0.1:8000/docs. - -### 🔓 - -🖊 "✔" 🔼. - -⚙️ 🎓: - -👩‍💻: `johndoe` - -🔐: `secret` - - - -⏮️ 🔗 ⚙️, 👆 🔜 👀 ⚫️ 💖: - - - -### 🤚 👆 👍 👩‍💻 💽 - -🔜 ⚙️ 🛠️ `GET` ⏮️ ➡ `/users/me`. - -👆 🔜 🤚 👆 👩‍💻 📊, 💖: - -```JSON -{ - "username": "johndoe", - "email": "johndoe@example.com", - "full_name": "John Doe", - "disabled": false, - "hashed_password": "fakehashedsecret" -} -``` - - - -🚥 👆 🖊 🔒 ℹ & ⏏, & ⤴️ 🔄 🎏 🛠️ 🔄, 👆 🔜 🤚 🇺🇸🔍 4️⃣0️⃣1️⃣ ❌: - -```JSON -{ - "detail": "Not authenticated" -} -``` - -### 🔕 👩‍💻 - -🔜 🔄 ⏮️ 🔕 👩‍💻, 🔓 ⏮️: - -👩‍💻: `alice` - -🔐: `secret2` - -& 🔄 ⚙️ 🛠️ `GET` ⏮️ ➡ `/users/me`. - -👆 🔜 🤚 "🔕 👩‍💻" ❌, 💖: - -```JSON -{ - "detail": "Inactive user" -} -``` - -## 🌃 - -👆 🔜 ✔️ 🧰 🛠️ 🏁 💂‍♂ ⚙️ ⚓️ 🔛 `username` & `password` 👆 🛠️. - -⚙️ 👫 🧰, 👆 💪 ⚒ 💂‍♂ ⚙️ 🔗 ⏮️ 🙆 💽 & ⏮️ 🙆 👩‍💻 ⚖️ 💽 🏷. - -🕴 ℹ ❌ 👈 ⚫️ 🚫 🤙 "🔐". - -⏭ 📃 👆 🔜 👀 ❔ ⚙️ 🔐 🔐 🔁 🗃 & 🥙 🤝. diff --git a/docs/em/docs/tutorial/static-files.md b/docs/em/docs/tutorial/static-files.md deleted file mode 100644 index 27685c06d9..0000000000 --- a/docs/em/docs/tutorial/static-files.md +++ /dev/null @@ -1,40 +0,0 @@ -# 🎻 📁 - -👆 💪 🍦 🎻 📁 🔁 ⚪️➡️ 📁 ⚙️ `StaticFiles`. - -## ⚙️ `StaticFiles` - -* 🗄 `StaticFiles`. -* "🗻" `StaticFiles()` 👐 🎯 ➡. - -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.staticfiles import StaticFiles`. - -**FastAPI** 🚚 🎏 `starlette.staticfiles` `fastapi.staticfiles` 🏪 👆, 👩‍💻. ✋️ ⚫️ 🤙 👟 🔗 ⚪️➡️ 💃. - -/// - -### ⚫️❔ "🗜" - -"🗜" ⛓ ❎ 🏁 "🔬" 🈸 🎯 ➡, 👈 ⤴️ ✊ 💅 🚚 🌐 🎧-➡. - -👉 🎏 ⚪️➡️ ⚙️ `APIRouter` 🗻 🈸 🍕 🔬. 🗄 & 🩺 ⚪️➡️ 👆 👑 🈸 🏆 🚫 🔌 🕳 ⚪️➡️ 🗻 🈸, ♒️. - -👆 💪 ✍ 🌅 🔃 👉 **🏧 👩‍💻 🦮**. - -## ℹ - -🥇 `"/static"` 🔗 🎧-➡ 👉 "🎧-🈸" 🔜 "🗻" 🔛. , 🙆 ➡ 👈 ▶️ ⏮️ `"/static"` 🔜 🍵 ⚫️. - -`directory="static"` 🔗 📛 📁 👈 🔌 👆 🎻 📁. - -`name="static"` 🤝 ⚫️ 📛 👈 💪 ⚙️ 🔘 **FastAPI**. - -🌐 👫 🔢 💪 🎏 🌘 "`static`", 🔆 👫 ⏮️ 💪 & 🎯 ℹ 👆 👍 🈸. - -## 🌅 ℹ - -🌖 ℹ & 🎛 ✅ 💃 🩺 🔃 🎻 📁. diff --git a/docs/em/docs/tutorial/testing.md b/docs/em/docs/tutorial/testing.md deleted file mode 100644 index 2e4a531f7d..0000000000 --- a/docs/em/docs/tutorial/testing.md +++ /dev/null @@ -1,185 +0,0 @@ -# 🔬 - -👏 💃, 🔬 **FastAPI** 🈸 ⏩ & 😌. - -⚫️ ⚓️ 🔛 🇸🇲, ❔ 🔄 🏗 ⚓️ 🔛 📨, ⚫️ 📶 😰 & 🏋️. - -⏮️ ⚫️, 👆 💪 ⚙️ 🔗 ⏮️ **FastAPI**. - -## ⚙️ `TestClient` - -/// info - -⚙️ `TestClient`, 🥇 ❎ `httpx`. - -🤶 Ⓜ. `pip install httpx`. - -/// - -🗄 `TestClient`. - -✍ `TestClient` 🚶‍♀️ 👆 **FastAPI** 🈸 ⚫️. - -✍ 🔢 ⏮️ 📛 👈 ▶️ ⏮️ `test_` (👉 🐩 `pytest` 🏛). - -⚙️ `TestClient` 🎚 🎏 🌌 👆 ⏮️ `httpx`. - -✍ 🙅 `assert` 📄 ⏮️ 🐩 🐍 🧬 👈 👆 💪 ✅ (🔄, 🐩 `pytest`). - -{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} - -/// tip - -👀 👈 🔬 🔢 😐 `def`, 🚫 `async def`. - - & 🤙 👩‍💻 😐 🤙, 🚫 ⚙️ `await`. - -👉 ✔ 👆 ⚙️ `pytest` 🔗 🍵 🤢. - -/// - -/// note | 📡 ℹ - -👆 💪 ⚙️ `from starlette.testclient import TestClient`. - -**FastAPI** 🚚 🎏 `starlette.testclient` `fastapi.testclient` 🏪 👆, 👩‍💻. ✋️ ⚫️ 👟 🔗 ⚪️➡️ 💃. - -/// - -/// tip - -🚥 👆 💚 🤙 `async` 🔢 👆 💯 ↖️ ⚪️➡️ 📨 📨 👆 FastAPI 🈸 (✅ 🔁 💽 🔢), ✔️ 👀 [🔁 💯](../advanced/async-tests.md){.internal-link target=_blank} 🏧 🔰. - -/// - -## 🎏 💯 - -🎰 🈸, 👆 🎲 🔜 ✔️ 👆 💯 🎏 📁. - -& 👆 **FastAPI** 🈸 5️⃣📆 ✍ 📚 📁/🕹, ♒️. - -### **FastAPI** 📱 📁 - -➡️ 💬 👆 ✔️ 📁 📊 🔬 [🦏 🈸](bigger-applications.md){.internal-link target=_blank}: - -``` -. -├── app -│   ├── __init__.py -│   └── main.py -``` - -📁 `main.py` 👆 ✔️ 👆 **FastAPI** 📱: - - -{* ../../docs_src/app_testing/main.py *} - -### 🔬 📁 - -⤴️ 👆 💪 ✔️ 📁 `test_main.py` ⏮️ 👆 💯. ⚫️ 💪 🖖 🔛 🎏 🐍 📦 (🎏 📁 ⏮️ `__init__.py` 📁): - -``` hl_lines="5" -. -├── app -│   ├── __init__.py -│   ├── main.py -│   └── test_main.py -``` - -↩️ 👉 📁 🎏 📦, 👆 💪 ⚙️ ⚖ 🗄 🗄 🎚 `app` ⚪️➡️ `main` 🕹 (`main.py`): - -{* ../../docs_src/app_testing/test_main.py hl[3] *} - -...& ✔️ 📟 💯 💖 ⏭. - -## 🔬: ↔ 🖼 - -🔜 ➡️ ↔ 👉 🖼 & 🚮 🌖 ℹ 👀 ❔ 💯 🎏 🍕. - -### ↔ **FastAPI** 📱 📁 - -➡️ 😣 ⏮️ 🎏 📁 📊 ⏭: - -``` -. -├── app -│   ├── __init__.py -│   ├── main.py -│   └── test_main.py -``` - -➡️ 💬 👈 🔜 📁 `main.py` ⏮️ 👆 **FastAPI** 📱 ✔️ 🎏 **➡ 🛠️**. - -⚫️ ✔️ `GET` 🛠️ 👈 💪 📨 ❌. - -⚫️ ✔️ `POST` 🛠️ 👈 💪 📨 📚 ❌. - -👯‍♂️ *➡ 🛠️* 🚚 `X-Token` 🎚. - -{* ../../docs_src/app_testing/app_b/main.py *} - -### ↔ 🔬 📁 - -👆 💪 ⤴️ ℹ `test_main.py` ⏮️ ↔ 💯: - -{* ../../docs_src/app_testing/app_b/test_main.py *} - -🕐❔ 👆 💪 👩‍💻 🚶‍♀️ ℹ 📨 & 👆 🚫 💭 ❔, 👆 💪 🔎 (🇺🇸🔍) ❔ ⚫️ `httpx`, ⚖️ ❔ ⚫️ ⏮️ `requests`, 🇸🇲 🔧 ⚓️ 🔛 📨' 🔧. - -⤴️ 👆 🎏 👆 💯. - -🤶 Ⓜ.: - -* 🚶‍♀️ *➡* ⚖️ *🔢* 🔢, 🚮 ⚫️ 📛 ⚫️. -* 🚶‍♀️ 🎻 💪, 🚶‍♀️ 🐍 🎚 (✅ `dict`) 🔢 `json`. -* 🚥 👆 💪 📨 *📨 💽* ↩️ 🎻, ⚙️ `data` 🔢 ↩️. -* 🚶‍♀️ *🎚*, ⚙️ `dict` `headers` 🔢. -* *🍪*, `dict` `cookies` 🔢. - -🌖 ℹ 🔃 ❔ 🚶‍♀️ 💽 👩‍💻 (⚙️ `httpx` ⚖️ `TestClient`) ✅ 🇸🇲 🧾. - -/// info - -🗒 👈 `TestClient` 📨 💽 👈 💪 🗜 🎻, 🚫 Pydantic 🏷. - -🚥 👆 ✔️ Pydantic 🏷 👆 💯 & 👆 💚 📨 🚮 💽 🈸 ⏮️ 🔬, 👆 💪 ⚙️ `jsonable_encoder` 🔬 [🎻 🔗 🔢](encoder.md){.internal-link target=_blank}. - -/// - -## 🏃 ⚫️ - -⏮️ 👈, 👆 💪 ❎ `pytest`: - -
- -```console -$ pip install pytest - ----> 100% -``` - -
- -⚫️ 🔜 🔍 📁 & 💯 🔁, 🛠️ 👫, & 📄 🏁 🔙 👆. - -🏃 💯 ⏮️: - -
- -```console -$ pytest - -================ test session starts ================ -platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -rootdir: /home/user/code/superawesome-cli/app -plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1 -collected 6 items - ----> 100% - -test_main.py ...... [100%] - -================= 1 passed in 0.03s ================= -``` - -
diff --git a/docs/em/mkdocs.yml b/docs/em/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/em/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml From 9be5063396e674df74165e0a990259bf30753746 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 20:44:35 +0000 Subject: [PATCH 087/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index cbf5e9c93a..93d7f51bc9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🔥 Remove translation to emoji to simplify the new setup with LLM autotranslations. PR [#14541](https://github.com/fastapi/fastapi/pull/14541) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for pt (update-outdated). PR [#14537](https://github.com/fastapi/fastapi/pull/14537) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (update-outdated). PR [#14532](https://github.com/fastapi/fastapi/pull/14532) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (add-missing). PR [#14533](https://github.com/fastapi/fastapi/pull/14533) by [@tiangolo](https://github.com/tiangolo). From 58bc4a15ac46abffcb0aea1481996337cd5a58e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 12:58:53 -0800 Subject: [PATCH 088/140] =?UTF-8?q?=F0=9F=94=A5=20Remove=20inactive/scarce?= =?UTF-8?q?=20translations=20to=20Persian=20(#14542)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/mkdocs.yml | 8 +- docs/fa/docs/advanced/sub-applications.md | 66 --- docs/fa/docs/async.md | 444 ----------------- docs/fa/docs/environment-variables.md | 298 ----------- docs/fa/docs/features.md | 209 -------- docs/fa/docs/index.md | 467 ----------------- docs/fa/docs/learn/index.md | 5 - docs/fa/docs/python-types.md | 578 ---------------------- docs/fa/docs/tutorial/middleware.md | 63 --- docs/fa/docs/tutorial/security/index.md | 106 ---- docs/fa/mkdocs.yml | 1 - scripts/docs.py | 1 - 12 files changed, 2 insertions(+), 2244 deletions(-) delete mode 100644 docs/fa/docs/advanced/sub-applications.md delete mode 100644 docs/fa/docs/async.md delete mode 100644 docs/fa/docs/environment-variables.md delete mode 100644 docs/fa/docs/features.md delete mode 100644 docs/fa/docs/index.md delete mode 100644 docs/fa/docs/learn/index.md delete mode 100644 docs/fa/docs/python-types.md delete mode 100644 docs/fa/docs/tutorial/middleware.md delete mode 100644 docs/fa/docs/tutorial/security/index.md delete mode 100644 docs/fa/mkdocs.yml diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 0e0adab9b2..ea7be2deeb 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -55,7 +55,7 @@ plugins: social: cards_layout_options: logo: ../en/docs/img/icon-white.svg - typeset: + typeset: null search: null macros: include_yaml: @@ -260,7 +260,7 @@ markdown_extensions: material.extensions.preview: targets: include: - - "*" + - '*' abbr: null attr_list: null footnotes: null @@ -317,8 +317,6 @@ extra: name: de - Deutsch - link: /es/ name: es - español - - link: /fa/ - name: fa - فارسی - link: /fr/ name: fr - français - link: /ja/ @@ -339,8 +337,6 @@ extra: name: zh - 简体中文 - link: /zh-hant/ name: zh-hant - 繁體中文 - - link: /em/ - name: 😉 extra_css: - css/termynal.css - css/custom.css diff --git a/docs/fa/docs/advanced/sub-applications.md b/docs/fa/docs/advanced/sub-applications.md deleted file mode 100644 index de813b6cf1..0000000000 --- a/docs/fa/docs/advanced/sub-applications.md +++ /dev/null @@ -1,66 +0,0 @@ -# زیر برنامه ها - اتصال - -اگر نیاز دارید که دو برنامه مستقل FastAPI، با OpenAPI مستقل و رابط‌های کاربری اسناد خود داشته باشید، می‌توانید یک برنامه -اصلی داشته باشید و یک (یا چند) زیر برنامه را به آن متصل کنید. - -## اتصال (mount) به یک برنامه **FastAPI** - -کلمه "Mounting" به معنای افزودن یک برنامه کاملاً مستقل در یک مسیر خاص است، که پس از آن مدیریت همه چیز در آن مسیر، با path operations (عملیات های مسیر) اعلام شده در آن زیر برنامه می باشد. - -### برنامه سطح بالا - -ابتدا برنامه اصلی سطح بالا، **FastAPI** و path operations آن را ایجاد کنید: - - -{* ../../docs_src/sub_applications/tutorial001.py hl[3,6:8] *} - -### زیر برنامه - -سپس، زیر برنامه خود و path operations آن را ایجاد کنید. - -این زیر برنامه فقط یکی دیگر از برنامه های استاندارد FastAPI است، اما این برنامه ای است که متصل می شود: - -{* ../../docs_src/sub_applications/tutorial001.py hl[11,14:16] *} - -### اتصال زیر برنامه - -در برنامه سطح بالا `app` اتصال زیر برنامه `subapi` در این نمونه `/subapi` در مسیر قرار میدهد و میشود: - -{* ../../docs_src/sub_applications/tutorial001.py hl[11,19] *} - -### اسناد API خودکار را بررسی کنید - -برنامه را با استفاده از ‘uvicorn‘ اجرا کنید، اگر فایل شما ‘main.py‘ نام دارد، دستور زیر را وارد کنید: -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -صفحه مستندات را در آدرس http://127.0.0.1:8000/docs باز کنید. - -اسناد API خودکار برنامه اصلی را مشاهده خواهید کرد که فقط شامل path operations خود می شود: - - - -و سپس اسناد زیر برنامه را در آدرس http://127.0.0.1:8000/subapi/docs. باز کنید. - -اسناد API خودکار برای زیر برنامه را خواهید دید، که فقط شامل path operations خود می شود، همه در زیر مسیر `/subapi` قرار دارند: - - - -اگر سعی کنید با هر یک از این دو رابط کاربری تعامل داشته باشید، آنها به درستی کار می کنند، زیرا مرورگر می تواند با هر یک از برنامه ها یا زیر برنامه های خاص صحبت کند. - -### جرئیات فنی : `root_path` - -هنگامی که یک زیر برنامه را همانطور که در بالا توضیح داده شد متصل می کنید, FastAPI با استفاده از مکانیزمی از مشخصات ASGI به نام `root_path` ارتباط مسیر mount را برای زیر برنامه انجام می دهد. - -به این ترتیب، زیر برنامه می داند که از آن پیشوند مسیر برای رابط کاربری اسناد (docs UI) استفاده کند. - -و زیر برنامه ها نیز می تواند زیر برنامه های متصل شده خود را داشته باشد و همه چیز به درستی کار کند، زیرا FastAPI تمام این مسیرهای `root_path` را به طور خودکار مدیریت می کند. - -در بخش [پشت پراکسی](behind-a-proxy.md){.internal-link target=_blank}. درباره `root_path` و نحوه استفاده درست از آن بیشتر خواهید آموخت. diff --git a/docs/fa/docs/async.md b/docs/fa/docs/async.md deleted file mode 100644 index b1d77754a5..0000000000 --- a/docs/fa/docs/async.md +++ /dev/null @@ -1,444 +0,0 @@ -# هم‌زمانی و async / await - -جزئیات در مورد سینتکس `async def` برای *توابع عملیات مسیر* و یه کم پیش‌زمینه در مورد کد ناهم‌زمان، هم‌زمانی و موازی‌سازی. - -## عجله داری؟ - -TL;DR: - -اگه از کتابخونه‌های سوم‌شخصی استفاده می‌کنی که بهت می‌گن با `await` صداشون کنی، مثل: - -```Python -results = await some_library() -``` - -اون وقت، *توابع عملیات مسیرت* رو با `async def` تعریف کن، اینجوری: - -```Python hl_lines="2" -@app.get('/') -async def read_results(): - results = await some_library() - return results -``` - -/// note - -فقط توی توابعی که با `async def` ساخته شدن می‌تونی از `await` استفاده کنی. - -/// - ---- - -اگه از یه کتابخونه سوم‌شخص استفاده می‌کنی که با یه چیزی (مثل دیتابیس، API، سیستم فایل و غیره) ارتباط داره و از `await` پشتیبانی نمی‌کنه (که الان برای بیشتر کتابخونه‌های دیتابیس اینجوریه)، اون وقت *توابع عملیات مسیرت* رو عادی، فقط با `def` تعریف کن، اینجوری: - -```Python hl_lines="2" -@app.get('/') -def results(): - results = some_library() - return results -``` - ---- - -اگه برنامه‌ات (به هر دلیلی) لازم نیست با چیز دیگه‌ای ارتباط برقرار کنه و منتظر جوابش بمونه، از `async def` استفاده کن. - ---- - -اگه نمی‌دونی چیکار کنی، از `def` معمولی استفاده کن. - ---- - -**توجه**: می‌تونی توی *توابع عملیات مسیرت* هر چقدر که لازم داری `def` و `async def` رو قاطی کنی و هر کدوم رو با بهترین گزینه برات تعریف کنی. FastAPI خودش کار درست رو باهاشون انجام می‌ده. - -به هر حال، توی هر کدوم از موقعیت‌های بالا، FastAPI هنوز ناهم‌زمان کار می‌کنه و خیلی خیلی سریع هست. - -ولی با دنبال کردن مراحل بالا، می‌تونه یه سری بهینه‌سازی عملکرد هم بکنه. - -## جزئیات فنی - -نسخه‌های مدرن پایتون از **"کد ناهم‌زمان"** با چیزی که بهش **"کروتین"** می‌گن پشتیبانی می‌کنن، با سینتکس **`async` و `await`**. - -بیاید این جمله رو تکه‌تکه توی بخش‌های زیر ببینیم: - -* **کد ناهم‌زمان** -* **`async` و `await`** -* **کروتین‌ها** - -## کد ناهم‌زمان - -کد ناهم‌زمان یعنی زبون 💬 یه راهی داره که به کامپیوتر / برنامه 🤖 بگه توی یه جای کد، باید منتظر بمونه تا *یه چیز دیگه* یه جای دیگه تموم بشه. فرض کن اون *یه چیز دیگه* اسمش "فایل-آروم" 📝 باشه. - -پس، توی اون مدت، کامپیوتر می‌تونه بره یه کار دیگه بکنه، تا وقتی "فایل-آروم" 📝 تموم بشه. - -بعدش کامپیوتر / برنامه 🤖 هر وقت فرصتی داشته باشه برمی‌گرده، چون دوباره منتظره، یا هر وقت همه کاری که اون لحظه داشته تموم کرده. و می‌بینه آیا کارایی که منتظرشون بوده تموم شدن یا نه، و هر کاری که باید بکنه رو انجام می‌ده. - -بعد، اون 🤖 اولین کاری که تموم شده (مثلاً "فایل-آروم" 📝 ما) رو برمی‌داره و هر کاری که باید باهاش بکنه رو ادامه می‌ده. - -این "منتظر یه چیز دیگه بودن" معمولاً به عملیات I/O اشاره داره که نسبتاً "آروم" هستن (نسبت به سرعت پردازنده و حافظه RAM)، مثل منتظر موندن برای: - -* داده‌هایی که از کلاینت از طریق شبکه فرستاده می‌شن -* داده‌هایی که برنامه‌ات فرستاده تا از طریق شبکه به کلاینت برسه -* محتوای یه فایل توی دیسک که سیستم بخوندش و به برنامه‌ات بده -* محتوایی که برنامه‌ات به سیستم داده تا توی دیسک بنویسه -* یه عملیات API از راه دور -* یه عملیات دیتابیس که تموم بشه -* یه کوئری دیتابیس که نتایجش برگرده -* و غیره. - -چون زمان اجرا بیشتر صرف انتظار برای عملیات I/O می‌شه، بهشون می‌گن عملیات "I/O bound". - -بهش "ناهم‌زمان" می‌گن چون کامپیوتر / برنامه لازم نیست با کار آروم "هم‌زمان" باشه، منتظر لحظه دقیق تموم شدن کار بمونه، در حالی که هیچ کاری نمی‌کنه، تا نتیجه رو بگیره و کارش رو ادامه بده. - -به جاش، چون یه سیستم "ناهم‌زمان" هست، وقتی کار تموم شد، می‌تونه یه کم توی صف منتظر بمونه (چند میکروثانیه) تا کامپیوتر / برنامه هر کاری که رفته بکنه رو تموم کنه، و بعد برگرده نتیجه رو بگیره و باهاش کار کنه. - -برای "هم‌زمان" (برخلاف "ناهم‌زمان") معمولاً از اصطلاح "ترتیبی" هم استفاده می‌کنن، چون کامپیوتر / برنامه همه مراحل رو به ترتیب دنبال می‌کنه قبل از اینکه بره سراغ یه کار دیگه، حتی اگه اون مراحل شامل انتظار باشن. - -### هم‌زمانی و برگرها - -این ایده **ناهم‌زمان** که بالا توضیح دادم گاهی بهش **"هم‌زمانی"** هم می‌گن. با **"موازی‌سازی"** فرق داره. - -**هم‌زمانی** و **موازی‌سازی** هر دو به "اتفاق افتادن چیزای مختلف کم‌وبیش همزمان" ربط دارن. - -ولی جزئیات بین *هم‌زمانی* و *موازی‌سازی* خیلی متفاوته. - -برای دیدن فرقش، این داستان در مورد برگرها رو تصور کن: - -### برگرهای هم‌زمان - -با عشقت می‌ری فست‌فود بگیرین، توی صف وایمیستی در حالی که صندوقدار سفارش آدمای جلوی تو رو می‌گیره. 😍 - - - -بعد نوبت تو می‌شه، سفارش دو تا برگر خیلی شیک برای خودت و عشقت می‌دی. 🍔🍔 - - - -صندوقدار یه چیزی به آشپز توی آشپزخونه می‌گه تا بدونن باید برگرهای تو رو آماده کنن (گرچه الان دارن برگرهای مشتریای قبلی رو درست می‌کنن). - - - -پول رو می‌دی. 💸 - -صندوقدار شماره نوبتت رو بهت می‌ده. - - - -وقتی منتظری، با عشقت می‌ری یه میز انتخاب می‌کنی، می‌شینی و کلی با عشقت حرف می‌زنی (چون برگرهات خیلی شیکن و آماده کردنشون یه کم طول می‌کشه). - -وقتی پشت میز با عشقت نشستی، در حالی که منتظر برگرهایی، می‌تونی اون زمان رو صرف تحسین این کنی که عشقت چقدر باحال، ناز و باهوشه ✨😍✨. - - - -وقتی منتظری و با عشقت حرف می‌زنی، هر از گاهی شماره‌ای که رو پیشخون نشون داده می‌شه رو چک می‌کنی که ببینی نوبتت شده یا نه. - -بعد یه جایی بالاخره نوبتت می‌شه. می‌ری پیشخون، برگرهات رو می‌گیری و برمی‌گردی سر میز. - - - -تو و عشقت برگرها رو می‌خورین و یه وقت خوب باهم دارین. ✨ - - - -/// info - -تصاویر قشنگ از کترینا تامپسون. 🎨 - -/// - ---- - -تصور کن تو توی این داستان کامپیوتر / برنامه 🤖 هستی. - -وقتی توی صف هستی، فقط بیکاری 😴، منتظر نوبتت هستی، کار خیلی "مفیدی" نمی‌کنی. ولی صف سریع پیش می‌ره چون صندوقدار فقط سفارش می‌گیره (آمادشون نمی‌کنه)، پس این خوبه. - -بعد، وقتی نوبتت می‌شه، کار "مفید" واقعی می‌کنی، منو رو پردازش می‌کنی، تصمیم می‌گیری چی می‌خوای، انتخاب عشقت رو می‌گیری، پول می‌دی، چک می‌کنی اسکناس یا کارت درست رو دادی، چک می‌کنی درست حساب شده، چک می‌کنی سفارش آیتمای درست رو داره و غیره. - -ولی بعد، گرچه هنوز برگرهات رو نداری، کارت با صندوقدار "موقتاً متوقف" ⏸ می‌شه، چون باید منتظر بمونی 🕙 تا برگرهات آماده بشن. - -ولی وقتی از پیشخون دور می‌شی و با شماره نوبتت سر میز می‌شینی، می‌تونی توجهت رو 🔀 به عشقت بدی و "کار" ⏯ 🤓 رو اون بکنی. بعدش دوباره داری یه چیز خیلی "مفید" انجام می‌دی، مثل لاس زدن با عشقت 😍. - -بعد صندوقدار 💁 با گذاشتن شماره‌ات رو نمایشگر پیشخون می‌گه "من با درست کردن برگرها تموم کردم"، ولی تو مثل دیوونه‌ها وقتی شماره‌ات رو نمایشگر میاد فوری نمی‌پری. می‌دونی کسی برگرهات رو نمی‌دزده چون شماره نوبتت رو داری، و اونا هم مال خودشون رو دارن. - -پس منتظر می‌مونی تا عشقت داستانش رو تموم کنه (کار فعلی ⏯ / وظیفه‌ای که داره پردازش می‌شه 🤓)، آروم لبخند می‌زنی و می‌گی که می‌ری برگرها رو بیاری ⏸. - -بعد می‌ری پیشخون 🔀، به کار اولیه که حالا تموم شده ⏯، برگرها رو می‌گیری، تشکر می‌کنی و می‌برشون سر میز. این مرحله / وظیفه تعامل با پیشخون رو تموم می‌کنه ⏹. این به نوبه خودش یه وظیفه جدید، "خوردن برگرها" 🔀 ⏯، می‌سازه، ولی اون قبلی که "گرفتن برگرها" بود تموم شده ⏹. - -### برگرهای موازی - -حالا فرض کن اینا "برگرهای هم‌زمان" نیستن، بلکه "برگرهای موازی" هستن. - -با عشقت می‌ری فست‌فود موازی بگیری. - -توی صف وایمیستی در حالی که چند تا (مثلاً 8 تا) صندوقدار که همزمان آشپز هم هستن سفارش آدمای جلوی تو رو می‌گیرن. - -همه قبل تو منتظرن برگرهاشون آماده بشه قبل از اینکه پیشخون رو ترک کنن، چون هر کدوم از 8 تا صندوقدار می‌ره و برگر رو همون موقع درست می‌کنه قبل از اینکه سفارش بعدی رو بگیره. - - - -بالاخره نوبت تو می‌شه، سفارش دو تا برگر خیلی شیک برای خودت و عشقت می‌دی. - -پول رو می‌دی 💸. - - - -صندوقدار می‌ره آشپزخونه. - -منتظر می‌مونی، جلوی پیشخون وایستادی 🕙، که کسی قبل از تو برگرهات رو نگیره، چون شماره نوبت نیست. - - - -چون تو و عشقت مشغول این هستین که نذارین کسی جلوتون بیاد و هر وقت برگرها رسیدن اونا رو بگیره، نمی‌تونی به عشقت توجه کنی. 😞 - -این کار "هم‌زمان" هست، تو با صندوقدار/آشپز 👨‍🍳 "هم‌زمان" هستی. باید منتظر بمونی 🕙 و درست همون لحظه که صندوقدار/آشپز 👨‍🍳 برگرها رو تموم می‌کنه و بهت می‌ده اونجا باشی، وگرنه ممکنه یکی دیگه اونا رو بگیره. - - - -بعد صندوقدار/آشپزت 👨‍🍳 بالاخره بعد از یه مدت طولانی انتظار 🕙 جلوی پیشخون با برگرهات برمی‌گرده. - - - -برگرهات رو می‌گیری و با عشقت می‌ری سر میز. - -فقط می‌خورینشون، و تمومه. ⏹ - - - -حرف زدن یا لاس زدن زیاد نبود چون بیشتر وقت صرف انتظار 🕙 جلوی پیشخون شد. 😞 - -/// info - -تصاویر قشنگ از کترینا تامپسون. 🎨 - -/// - ---- - -توی این سناریوی برگرهای موازی، تو یه کامپیوتر / برنامه 🤖 با دو تا پردازنده (تو و عشقت) هستی، هر دو منتظر 🕙 و توجهشون ⏯ رو برای مدت طولانی "انتظار جلوی پیشخون" 🕙 گذاشتن. - -فست‌فود 8 تا پردازنده (صندوقدار/آشپز) داره. در حالی که فست‌فود برگرهای هم‌زمان شاید فقط 2 تا داشته (یه صندوقدار و یه آشپز). - -ولی با این حال، تجربه نهایی بهترین نیست. 😞 - ---- - -این معادل موازی داستان برگرها بود. 🍔 - -برای یه مثال "واقعی‌تر" از زندگی، یه بانک رو تصور کن. - -تا همین چند وقت پیش، بیشتر بانک‌ها چند تا صندوقدار 👨‍💼👨‍💼👨‍💼👨‍💼 داشتن و یه صف بزرگ 🕙🕙🕙🕙🕙🕙🕙🕙. - -همه صندوقدارها کار رو با یه مشتری بعد از اون یکی 👨‍💼⏯ انجام می‌دادن. - -و باید توی صف 🕙 مدت زیادی منتظر بمونی وگرنه نوبتت رو از دست می‌دی. - -احتمالاً نمی‌خوای عشقت 😍 رو با خودت ببری بانک 🏦 برای کارای روزمره. - -### نتیجه‌گیری برگرها - -توی این سناریوی "برگرهای فست‌فود با عشقت"، چون کلی انتظار 🕙 هست، خیلی منطقی‌تره که یه سیستم هم‌زمان ⏸🔀⏯ داشته باشی. - -این برای بیشتر برنامه‌های وب هم صدق می‌کنه. - -خیلی خیلی کاربر، ولی سرورت منتظر 🕙 اتصال نه‌چندان خوبشون هست تا درخواست‌هاشون رو بفرستن. - -و بعد دوباره منتظر 🕙 که جواب‌ها برگردن. - -این "انتظار" 🕙 توی میکروثانیه‌ها اندازه‌گیری می‌شه، ولی با این حال، جمعش که بکنی آخرش کلی انتظار می‌شه. - -برای همین استفاده از کد ناهم‌زمان ⏸🔀⏯ برای APIهای وب خیلی منطقیه. - -این نوع ناهم‌زمانی چیزیه که NodeJS رو محبوب کرد (گرچه NodeJS موازی نیست) و نقطه قوت Go به‌عنوان یه زبون برنامه‌نویسیه. - -و همون سطح عملکردی هست که با **FastAPI** می‌گیری. - -و چون می‌تونی هم‌زمانی و موازی‌سازی رو همزمان داشته باشی، عملکرد بالاتری از بیشتر فریم‌ورک‌های تست‌شده NodeJS می‌گیری و هم‌تراز با Go، که یه زبون کامپایل‌شده نزدیک به C هست (همه اینا به لطف Starlette). - -### آیا هم‌زمانی از موازی‌سازی بهتره؟ - -نه! این نتیجه داستان نیست. - -هم‌زمانی با موازی‌سازی فرق داره. و توی **سناریوهای خاص** که کلی انتظار دارن بهتره. به همین خاطر، معمولاً برای توسعه برنامه‌های وب خیلی از موازی‌سازی بهتره. ولی نه برای همه‌چیز. - -برای اینکه یه تعادل بذاریم، این داستان کوتاه رو تصور کن: - -> باید یه خونه بزرگ و کثیف رو تمیز کنی. - -*آره، کل داستان همینه*. - ---- - -هیچ انتظاری 🕙 اونجا نیست، فقط کلی کار برای انجام دادن توی جاهای مختلف خونه. - -می‌تونی مثل مثال برگرها نوبت بذاری، اول پذیرایی، بعد آشپزخونه، ولی چون منتظر چیزی نیستی 🕙، فقط داری تمیز می‌کنی و تمیز می‌کنی، نوبت‌ها هیچ تأثیری نداره. - -با نوبت یا بدون نوبت (هم‌زمانی) همون قدر طول می‌کشه تا تمومش کنی و همون مقدار کار رو کردی. - -ولی توی این موقعیت، اگه بتونی اون 8 تا صندوقدار/آشپز/حالا-تمیزکار رو بیاری، و هر کدومشون (به‌علاوه خودت) یه قسمت از خونه رو تمیز کنن، می‌تونی همه کار رو **موازی** انجام بدی، با کمک اضافی، و خیلی زودتر تمومش کنی. - -توی این سناریو، هر کدوم از تمیزکارها (از جمله خودت) یه پردازنده‌ست که کار خودش رو می‌کنه. - -و چون بیشتر زمان اجرا صرف کار واقعی می‌شه (به جای انتظار)، و کار توی کامپیوتر با CPU انجام می‌شه، به این مشکلات می‌گن "CPU bound". - ---- - -مثال‌های رایج عملیات CPU bound چیزایی هستن که نیاز به پردازش ریاضی پیچیده دارن. - -مثلاً: - -* پردازش **صدا** یا **تصویر**. -* **بینایی کامپیوتری**: یه تصویر از میلیون‌ها پیکسل تشکیل شده، هر پیکسل 3 تا مقدار / رنگ داره، پردازشش معمولاً نیاز داره چیزی رو رو اون پیکسل‌ها همزمان حساب کنی. -* **یادگیری ماشین**: معمولاً کلی ضرب "ماتریس" و "بردار" لازم داره. یه جدول بزرگ پر از عدد رو تصور کن که همه‌شون رو همزمان ضرب می‌کنی. -* **یادگیری عمیق**: این یه زیرشاخه از یادگیری ماشینه، پس همون قضیه صدق می‌کنه. فقط این که یه جدول عدد برای ضرب کردن نیست، بلکه یه مجموعه بزرگ از اونا هست، و توی خیلی موارد از یه پردازنده خاص برای ساخت و / یا استفاده از این مدل‌ها استفاده می‌کنی. - -### هم‌زمانی + موازی‌سازی: وب + یادگیری ماشین - -با **FastAPI** می‌تونی از هم‌زمانی که برای توسعه وب خیلی رایجه (همون جذابیت اصلی NodeJS) استفاده کنی. - -ولی می‌تونی از فواید موازی‌سازی و چندپردازشی (اجرای چند پروسه به‌صورت موازی) برای کارای **CPU bound** مثل سیستم‌های یادگیری ماشین هم بهره ببری. - -این، به‌علاوه این واقعیت ساده که پایتون زبون اصلی برای **علم داده**، یادگیری ماشین و به‌خصوص یادگیری عمیقه، باعث می‌شه FastAPI یه انتخاب خیلی خوب برای APIها و برنامه‌های وب علم داده / یادگیری ماشین باشه (بین خیلی چیزای دیگه). - -برای دیدن اینکه چطور توی محیط واقعی به این موازی‌سازی برسی، بخش [استقرار](deployment/index.md){.internal-link target=_blank} رو ببین. - -## `async` و `await` - -نسخه‌های مدرن پایتون یه راه خیلی ساده و قابل‌فهم برای تعریف کد ناهم‌زمان دارن. این باعث می‌شه مثل کد "ترتیبی" معمولی به نظر بیاد و توی لحظه‌های درست "انتظار" رو برات انجام بده. - -وقتی یه عملیاتی هست که قبل از دادن نتیجه‌ها نیاز به انتظار داره و از این قابلیت‌های جدید پایتون پشتیبانی می‌کنه، می‌تونی اینجوری کدنویسیش کنی: - -```Python -burgers = await get_burgers(2) -``` - -نکته کلیدی اینجا `await` هست. به پایتون می‌گه که باید ⏸ منتظر بمونه تا `get_burgers(2)` کارش 🕙 تموم بشه قبل از اینکه نتیجه‌ها رو توی `burgers` ذخیره کنه. با این، پایتون می‌دونه که می‌تونه بره یه کار دیگه 🔀 ⏯ توی این مدت بکنه (مثل گرفتن یه درخواست دیگه). - -برای اینکه `await` کار کنه، باید توی یه تابع باشه که از این ناهم‌زمانی پشتیبانی کنه. برای این کار، فقط با `async def` تعریفش می‌کنی: - -```Python hl_lines="1" -async def get_burgers(number: int): - # یه سری کار ناهم‌زمان برای ساختن برگرها انجام بده - return burgers -``` - -...به جای `def`: - -```Python hl_lines="2" -# این ناهم‌زمان نیست -def get_sequential_burgers(number: int): - # یه سری کار ترتیبی برای ساختن برگرها انجام بده - return burgers -``` - -با `async def`، پایتون می‌دونه که توی اون تابع باید حواسش به عبارت‌های `await` باشه، و می‌تونه اجرای اون تابع رو "موقتاً متوقف" ⏸ کنه و بره یه کار دیگه 🔀 قبل از برگشتن بکنه. - -وقتی می‌خوای یه تابع `async def` رو صدا کنی، باید "منتظرش" بمونی. پس این کار نمی‌کنه: - -```Python -# این کار نمی‌کنه، چون get_burgers با async def تعریف شده -burgers = get_burgers(2) -``` - ---- - -پس، اگه از یه کتابخونه استفاده می‌کنی که بهت می‌گه می‌تونی با `await` صداش کنی، باید *توابع عملیات مسیرت* که ازش استفاده می‌کنن رو با `async def` بسازی، مثل: - -```Python hl_lines="2-3" -@app.get('/burgers') -async def read_burgers(): - burgers = await get_burgers(2) - return burgers -``` - -### جزئیات فنی‌تر - -شاید متوجه شده باشی که `await` فقط توی توابعی که با `async def` تعریف شدن می‌تونه استفاده بشه. - -ولی در عین حال، توابعی که با `async def` تعریف شدن باید "منتظر"شون بمونی. پس توابع با `async def` فقط توی توابعی که با `async def` تعریف شدن می‌تونن صدا زده بشن. - -حالا، قضیه مرغ و تخم‌مرغ چیه، چطور اولین تابع `async` رو صدا می‌کنی؟ - -اگه با **FastAPI** کار می‌کنی، لازم نیست نگران این باشی، چون اون "اولین" تابع، *تابع عملیات مسیرت* هست، و FastAPI می‌دونه چطور کار درست رو بکنه. - -ولی اگه بخوای بدون FastAPI از `async` / `await` استفاده کنی، اینم ممکنه. - -### کد ناهم‌زمان خودت رو بنویس - -Starlette (و **FastAPI**) بر پایه AnyIO هستن، که باعث می‌شه با کتابخونه استاندارد پایتون asyncio و Trio سازگار باشه. - -به‌خصوص، می‌تونی مستقیماً از AnyIO برای موارد استفاده پیشرفته هم‌زمانی که نیاز به الگوهای پیچیده‌تر توی کد خودت دارن استفاده کنی. - -و حتی اگه از FastAPI استفاده نکنی، می‌تونی برنامه‌های ناهم‌زمان خودت رو با AnyIO بنویسی تا خیلی سازگار باشه و فوایدش رو بگیری (مثل *هم‌زمانی ساختاریافته*). - -من یه کتابخونه دیگه روی AnyIO ساختم، یه لایه نازک روش، تا یه کم annotationهای نوع رو بهتر کنم و **تکمیل خودکار** بهتر، **خطاهای درون‌خطی** و غیره بگیرم. یه مقدمه و آموزش ساده هم داره که بهت کمک می‌کنه **بفهمی** و **کد ناهم‌زمان خودت رو بنویسی**: Asyncer. اگه بخوای **کد ناهم‌زمان رو با کد معمولی** (بلاک‌کننده/هم‌زمان) ترکیب کنی خیلی به‌دردت می‌خوره. - -### شکل‌های دیگه کد ناهم‌زمان - -این سبک استفاده از `async` و `await` توی زبون نسبتاً جدیده. - -ولی کار با کد ناهم‌زمان رو خیلی ساده‌تر می‌کنه. - -همین سینتکس (یا تقریباً یکسان) اخیراً توی نسخه‌های مدرن جاوااسکریپت (توی مرورگر و NodeJS) هم اضافه شده. - -ولی قبل از اون، مدیریت کد ناهم‌زمان خیلی پیچیده‌تر و سخت‌تر بود. - -توی نسخه‌های قبلی پایتون، می‌تونستی از نخ‌ها یا Gevent استفاده کنی. ولی کد خیلی پیچیده‌تر می‌شه برای فهمیدن، دیباگ کردن و فکر کردن بهش. - -توی نسخه‌های قبلی NodeJS / جاوااسکریپت مرورگر، از "کال‌بک‌ها" استفاده می‌کردی. که می‌رسید به "جهان کال‌بک‌ها". - -## کروتین‌ها - -**کروتین** فقط یه اصطلاح خیلی شیک برای چیزیه که یه تابع `async def` برمی‌گردونه. پایتون می‌دونه که این یه چیزی مثل تابع هست، می‌تونه شروع بشه و یه جایی تموم بشه، ولی ممکنه داخلش هم موقف ⏸ بشه، هر وقت یه `await` توش باشه. - -ولی همه این قابلیت استفاده از کد ناهم‌زمان با `async` و `await` خیلی وقتا خلاصه می‌شه به استفاده از "کروتین‌ها". این قابل مقایسه با ویژگی اصلی Go، یعنی "Goroutineها" هست. - -## نتیجه‌گیری - -بیاید همون جمله از بالا رو ببینیم: - -> نسخه‌های مدرن پایتون از **"کد ناهم‌زمان"** با چیزی که بهش **"کروتین"** می‌گن پشتیبانی می‌کنن، با سینتکس **`async` و `await`**. - -حالا باید بیشتر برات معنی بده. ✨ - -همه اینا چیزیه که به FastAPI (از طریق Starlette) قدرت می‌ده و باعث می‌شه عملکرد چشمگیری داشته باشه. - -## جزئیات خیلی فنی - -/// warning - -احتمالاً می‌تونی اینو رد کنی. - -اینا جزئیات خیلی فنی از نحوه کار **FastAPI** زیر پوسته‌ست. - -اگه یه کم دانش فنی (کروتین‌ها، نخ‌ها، بلاک کردن و غیره) داری و کنجکاوی که FastAPI چطور `async def` رو در مقابل `def` معمولی مدیریت می‌کنه، ادامه بده. - -/// - -### توابع عملیات مسیر - -وقتی یه *تابع عملیات مسیر* رو با `def` معمولی به جای `async def` تعریف می‌کنی، توی یه استخر نخ خارجی اجرا می‌شه که بعدش منتظرش می‌مونن، به جای اینکه مستقیم صداش کنن (چون سرور رو بلاک می‌کنه). - -اگه از یه فریم‌ورک ناهم‌زمان دیگه میای که به روش بالا کار نمی‌کنه و عادت داری *توابع عملیات مسیر* ساده فقط محاسباتی رو با `def` معمولی برای یه سود کوچیک عملکرد (حدود 100 نانوثانیه) تعریف کنی، توجه کن که توی **FastAPI** اثرش کاملاً برعکسه. توی این موارد، بهتره از `async def` استفاده کنی مگه اینکه *توابع عملیات مسیرت* کدی داشته باشن که عملیات I/O بلاک‌کننده انجام بده. - -با این حال، توی هر دو موقعیت، احتمالش زیاده که **FastAPI** هنوز [سریع‌تر](index.md#performance){.internal-link target=_blank} از فریم‌ورک قبلی‌ات باشه (یا حداقل قابل مقایسه باهاش). - -### وابستگی‌ها - -همین برای [وابستگی‌ها](tutorial/dependencies/index.md){.internal-link target=_blank} هم صدق می‌کنه. اگه یه وابستگی یه تابع `def` معمولی به جای `async def` باشه، توی استخر نخ خارجی اجرا می‌شه. - -### زیروابستگی‌ها - -می‌تونی چند تا وابستگی و [زیروابستگی](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} داشته باشی که همدیگه رو نیاز دارن (به‌عنوان پارامترهای تعریف تابع)، بعضی‌هاشون ممکنه با `async def` ساخته بشن و بعضی‌ها با `def` معمولی. بازم کار می‌کنه، و اونایی که با `def` معمولی ساخته شدن توی یه نخ خارجی (از استخر نخ) صدا زده می‌شن به جای اینکه "منتظرشون" بمونن. - -### توابع کاربردی دیگه - -هر تابع کاربردی دیگه‌ای که مستقیم خودت صداش می‌کنی می‌تونه با `def` معمولی یا `async def` ساخته بشه و FastAPI رو نحوه صدازدنش تأثیر نمی‌ذاره. - -این برخلاف توابعی هست که FastAPI برات صداشون می‌کنه: *توابع عملیات مسیر* و وابستگی‌ها. - -اگه تابع کاربردیت یه تابع معمولی با `def` باشه، مستقیم صداش می‌کنن (همون‌طور که توی کدت نوشتی)، نه توی استخر نخ، اگه تابع با `async def` ساخته شده باشه، باید وقتی توی کدت صداش می‌کنی `await`ش کنی. - ---- - -دوباره، اینا جزئیات خیلی فنی هستن که احتمالاً اگه دنبالشون اومده باشی برات مفید باشن. - -وگرنه، با راهنمایی‌های بخش بالا باید خوب باشی: عجله داری؟. diff --git a/docs/fa/docs/environment-variables.md b/docs/fa/docs/environment-variables.md deleted file mode 100644 index 75309ce1fe..0000000000 --- a/docs/fa/docs/environment-variables.md +++ /dev/null @@ -1,298 +0,0 @@ -# متغیرهای محیطی - -/// tip - -اگه از قبل می‌دونی متغیرهای محیطی چی هستن و چطور ازشون استفاده می‌شه، می‌تونی این بخش رو رد کنی. - -/// - -یه متغیر محیطی (که بهش "**env var**" هم می‌گن) یه متغیریه که **خارج** از کد پایتون، توی **سیستم‌عامل** زندگی می‌کنه و می‌تونه توسط کد پایتونت (یا برنامه‌های دیگه) خونده بشه. - -متغیرهای محیطی می‌تونن برای مدیریت **تنظیمات** برنامه، به‌عنوان بخشی از **نصب** پایتون و غیره مفید باشن. - -## ساخت و استفاده از متغیرهای محیطی - -می‌تونی متغیرهای محیطی رو توی **شل (ترمینال)** **بسازی** و ازشون استفاده کنی، بدون اینکه به پایتون نیاز داشته باشی: - -//// tab | لینوکس، مک‌اواس، ویندوز بش - -
- -```console -// می‌تونی یه متغیر محیطی به اسم MY_NAME بسازی با -$ export MY_NAME="Wade Wilson" - -// بعد می‌تونی با برنامه‌های دیگه ازش استفاده کنی، مثل -$ echo "Hello $MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -//// tab | ویندوز پاورشل - -
- -```console -// یه متغیر محیطی به اسم MY_NAME بساز -$ $Env:MY_NAME = "Wade Wilson" - -// با برنامه‌های دیگه ازش استفاده کن، مثل -$ echo "Hello $Env:MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -## خوندن متغیرهای محیطی توی پایتون - -می‌تونی متغیرهای محیطی رو **خارج** از پایتون، توی ترمینال (یا با هر روش دیگه) بسازی، و بعد توی **پایتون** اونا رو بخونی. - -مثلاً می‌تونی یه فایل `main.py` داشته باشی با: - -```Python hl_lines="3" -import os - -name = os.getenv("MY_NAME", "World") -print(f"Hello {name} from Python") -``` - -/// tip - -آرگومان دوم `os.getenv()` مقدار پیش‌فرضیه که برمی‌گردونه. - -اگه ندی، به‌صورت پیش‌فرض `None` هست، اینجا ما `"World"` رو به‌عنوان مقدار پیش‌فرض گذاشتیم. - -/// - -بعد می‌تونی اون برنامه پایتون رو صدا کنی: - -//// tab | لینوکس، مک‌اواس، ویندوز بش - -
- -```console -// اینجا هنوز متغیر محیطی رو تنظیم نکردیم -$ python main.py - -// چون متغیر محیطی رو تنظیم نکردیم، مقدار پیش‌فرض رو می‌گیریم - -Hello World from Python - -// ولی اگه اول یه متغیر محیطی بسازیم -$ export MY_NAME="Wade Wilson" - -// و بعد دوباره برنامه رو صدا کنیم -$ python main.py - -// حالا می‌تونه متغیر محیطی رو بخونه - -Hello Wade Wilson from Python -``` - -
- -//// - -//// tab | ویندوز پاورشل - -
- -```console -// اینجا هنوز متغیر محیطی رو تنظیم نکردیم -$ python main.py - -// چون متغیر محیطی رو تنظیم نکردیم، مقدار پیش‌فرض رو می‌گیریم - -Hello World from Python - -// ولی اگه اول یه متغیر محیطی بسازیم -$ $Env:MY_NAME = "Wade Wilson" - -// و بعد دوباره برنامه رو صدا کنیم -$ python main.py - -// حالا می‌تونه متغیر محیطی رو بخونه - -Hello Wade Wilson from Python -``` - -
- -//// - -چون متغیرهای محیطی می‌تونن خارج از کد تنظیم بشن، ولی کد می‌تونه اونا رو بخونه، و لازم نیست با بقیه فایل‌ها ذخیره (کمیتی به `git`) بشن، معمولاً برای پیکربندی یا **تنظیمات** استفاده می‌شن. - -همچنین می‌تونی یه متغیر محیطی رو فقط برای **یه اجرای خاص برنامه** بسازی، که فقط برای اون برنامه و فقط برای مدت زمان اجراش در دسترسه. - -برای این کار، درست قبل از خود برنامه، توی همون خط بسازش: - -
- -```console -// یه متغیر محیطی MY_NAME رو توی خط برای این اجرای برنامه بساز -$ MY_NAME="Wade Wilson" python main.py - -// حالا می‌تونه متغیر محیطی رو بخونه - -Hello Wade Wilson from Python - -// متغیر محیطی بعدش دیگه وجود نداره -$ python main.py - -Hello World from Python -``` - -
- -/// tip - -می‌تونی بیشتر در موردش توی برنامه دوازده‌فاکتوری: پیکربندی بخونی. - -/// - -## نوع‌ها و اعتبارسنجی - -این متغیرهای محیطی فقط می‌تونن **رشته‌های متنی** رو نگه دارن، چون خارج از پایتون هستن و باید با برنامه‌های دیگه و بقیه سیستم (و حتی سیستم‌عامل‌های مختلف مثل لینوکس، ویندوز، مک‌اواس) سازگار باشن. - -یعنی **هر مقداری** که توی پایتون از یه متغیر محیطی خونده می‌شه یه `str` هست، و هر تبدیل به نوع دیگه یا هر اعتبارسنجی باید توی کد انجام بشه. - -توی [راهنمای کاربر پیشرفته - تنظیمات و متغیرهای محیطی](./advanced/settings.md){.internal-link target=_blank} بیشتر در مورد استفاده از متغیرهای محیطی برای مدیریت **تنظیمات برنامه** یاد می‌گیری. - -## متغیر محیطی `PATH` - -یه متغیر محیطی **خاص** به اسم **`PATH`** وجود داره که سیستم‌عامل‌ها (لینوکس، مک‌اواس، ویندوز) ازش برای پیدا کردن برنامه‌هایی که قراره اجرا بشن استفاده می‌کنن. - -مقدار متغیر `PATH` یه رشته طولانی از پوشه‌هاست که توی لینوکس و مک‌اواس با دونقطه `:` و توی ویندوز با نقطه‌ویرگول `;` از هم جدا شدن. - -مثلاً، متغیر محیطی `PATH` می‌تونه اینجوری باشه: - -//// tab | لینوکس، مک‌اواس - -```plaintext -/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -``` - -یعنی سیستم باید دنبال برنامه‌ها توی این پوشه‌ها بگرده: - -* `/usr/local/bin` -* `/usr/bin` -* `/bin` -* `/usr/sbin` -* `/sbin` - -//// - -//// tab | ویندوز - -```plaintext -C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 -``` - -یعنی سیستم باید دنبال برنامه‌ها توی این پوشه‌ها بگرده: - -* `C:\Program Files\Python312\Scripts` -* `C:\Program Files\Python312` -* `C:\Windows\System32` - -//// - -وقتی یه **دستور** توی ترمینال تایپ می‌کنی، سیستم‌عامل **دنبال** برنامه توی **هر کدوم از این پوشه‌ها** که توی متغیر محیطی `PATH` لیست شدن می‌گرده. - -مثلاً، وقتی توی ترمینال `python` تایپ می‌کنی، سیستم‌عامل دنبال یه برنامه به اسم `python` توی **اولین پوشه** توی اون لیست می‌گرده. - -اگه پیداش کنه، **استفاده‌ش می‌کنه**. وگرنه توی **پوشه‌های بعدی** دنبالش می‌گرده. - -### نصب پایتون و به‌روزرسانی `PATH` - -وقتی پایتون رو نصب می‌کنی، ممکنه ازت بپرسن آیا می‌خوای متغیر محیطی `PATH` رو به‌روزرسانی کنی. - -//// tab | لینوکس، مک‌اواس - -فرض کن پایتون رو نصب کردی و توی یه پوشه `/opt/custompython/bin` قرار گرفته. - -اگه بگی بله برای به‌روزرسانی متغیر محیطی `PATH`، نصاب `/opt/custompython/bin` رو به متغیر محیطی `PATH` اضافه می‌کنه. - -ممکنه اینجوری بشه: - -```plaintext -/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin -``` - -این‌جوری، وقتی توی ترمینال `python` تایپ می‌کنی، سیستم برنامه پایتون رو توی `/opt/custompython/bin` (آخرین پوشه) پیدا می‌کنه و از اون استفاده می‌کنه. - -//// - -//// tab | ویندوز - -فرض کن پایتون رو نصب کردی و توی یه پوشه `C:\opt\custompython\bin` قرار گرفته. - -اگه بگی بله برای به‌روزرسانی متغیر محیطی `PATH`، نصاب `C:\opt\custompython\bin` رو به متغیر محیطی `PATH` اضافه می‌کنه. - -```plaintext -C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin -``` - -این‌جوری، وقتی توی ترمینال `python` تایپ می‌کنی، سیستم برنامه پایتون رو توی `C:\opt\custompython\bin` (آخرین پوشه) پیدا می‌کنه و از اون استفاده می‌کنه. - -//// - -پس، اگه تایپ کنی: - -
- -```console -$ python -``` - -
- -//// tab | لینوکس، مک‌اواس - -سیستم برنامه `python` رو توی `/opt/custompython/bin` **پیدا** می‌کنه و اجراش می‌کنه. - -تقریباً معادل اینه که تایپ کنی: - -
- -```console -$ /opt/custompython/bin/python -``` - -
- -//// - -//// tab | ویندوز - -سیستم برنامه `python` رو توی `C:\opt\custompython\bin\python` **پیدا** می‌کنه و اجراش می‌کنه. - -تقریباً معادل اینه که تایپ کنی: - -
- -```console -$ C:\opt\custompython\bin\python -``` - -
- -//// - -این اطلاعات وقتی در مورد [محیط‌های مجازی](virtual-environments.md){.internal-link target=_blank} یاد می‌گیری به‌دردت می‌خوره. - -## نتیجه‌گیری - -با این باید یه درک پایه‌ای از **متغیرهای محیطی** و نحوه استفاده‌شون توی پایتون داشته باشی. - -می‌تونی بیشتر در موردشون توی ویکی‌پدیا برای متغیر محیطی بخونی. - -توی خیلی موارد مشخص نیست که متغیرهای محیطی چطور می‌تونن فوری مفید و کاربردی باشن. ولی توی موقعیت‌های مختلف توسعه مدام پیداشون می‌شه، پس خوبه که در موردشون بدونی. - -مثلاً، توی بخش بعدی در مورد [محیط‌های مجازی](virtual-environments.md) به این اطلاعات نیاز داری. diff --git a/docs/fa/docs/features.md b/docs/fa/docs/features.md deleted file mode 100644 index c265d29702..0000000000 --- a/docs/fa/docs/features.md +++ /dev/null @@ -1,209 +0,0 @@ -# ویژگی ها - -## ویژگی های FastAPI - -**FastAPI** موارد زیر را به شما ارائه میدهد: - -### برپایه استاندارد های باز - -* OpenAPI برای ساخت API, شامل مشخص سازی path operation ها, پارامترها, body request ها, امنیت و غیره. -* مستندسازی خودکار data model با JSON Schema (همانطور که OpenAPI خود نیز مبتنی بر JSON Schema است). -* طراحی شده بر اساس استاندارد هایی که پس از یک مطالعه دقیق بدست آمده اند بجای طرحی ناپخته و بدون فکر. -* همچنین به شما اجازه میدهد تا از تولید خودکار client code در بسیاری از زبان ها استفاده کنید. - -### مستندات خودکار - -مستندات API تعاملی و ایجاد رابط کاربری وب. از آنجایی که این فریم ورک برپایه OpenAPI میباشد، آپشن های متعددی وجود دارد که ۲ مورد بصورت پیش فرض گنجانده شده اند. - -* Swagger UI، با کاوش تعاملی، API خود را مستقیما از طریق مرورگر صدازده و تست کنید. - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* مستندات API جایگزین با ReDoc. - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### فقط پایتون مدرن - -همه اینها برپایه type declaration های **پایتون ۳.۶** استاندارد (به لطف Pydantic) میباشند. سینتکس جدیدی درکار نیست. تنها پایتون مدرن استاندارد. - -اگر به یک یادآوری ۲ دقیقه ای در مورد نحوه استفاده از تایپ های پایتون دارید (حتی اگر از FastAPI استفاده نمیکنید) این آموزش کوتاه را بررسی کنید: [Python Types](python-types.md){.internal-link target=\_blank}. - -شما پایتون استاندارد را با استفاده از تایپ ها مینویسید: - -```Python -from datetime import date - -from pydantic import BaseModel - -# Declare a variable as a str -# and get editor support inside the function -def main(user_id: str): - return user_id - - -# A Pydantic model -class User(BaseModel): - id: int - name: str - joined: date -``` - -که سپس میتوان به این شکل از آن استفاده کرد: - -```Python -my_user: User = User(id=3, name="John Doe", joined="2018-07-19") - -second_user_data = { - "id": 4, - "name": "Mary", - "joined": "2018-11-30", -} - -my_second_user: User = User(**second_user_data) -``` - -/// info - -`**second_user_data` یعنی: - -کلید ها و مقادیر دیکشنری `second_user_data` را مستقیما به عنوان ارگومان های key-value بفرست، که معادل است با : `User(id=4, name="Mary", joined="2018-11-30")` - -/// - -### پشتیبانی ویرایشگر - -تمام فریم ورک به گونه ای طراحی شده که استفاده از آن آسان و شهودی باشد، تمام تصمیمات حتی قبل از شروع توسعه بر روی چندین ویرایشگر آزمایش شده اند، تا از بهترین تجربه توسعه اطمینان حاصل شود. - -در آخرین نظرسنجی توسعه دهندگان پایتون کاملا مشخص بود که بیشترین ویژگی مورد استفاده از "تکمیل خودکار" است. - -تمام فریم ورک **FastAPI** برپایه ای برای براورده کردن این نیاز نیز ایجاد گشته است. تکمیل خودکار در همه جا کار میکند. - -شما به ندرت نیاز به بازگشت به مستندات را خواهید داشت. - -ببینید که چگونه ویرایشگر شما ممکن است به شما کمک کند: - -* در Visual Studio Code: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -* در PyCharm: - -![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) - -شما پیشنهاد های تکمیل خودکاری را خواهید گرفت که حتی ممکن است قبلا آن را غیرممکن تصور میکردید. به عنوان مثال کلید `price` در داخل بدنه JSON (که میتوانست تودرتو نیز باشد) که از یک درخواست آمده است. - -دیگر خبری از تایپ کلید اشتباهی، برگشتن به مستندات یا پایین بالا رفتن برای فهمیدن اینکه شما از `username` یا `user_name` استفاده کرده اید نیست. - -### مختصر - -FastAPI **پیش فرض** های معقولی برای همه چیز دارد، با قابلیت تنظیمات اختیاری در همه جا. تمام پارامترها را میتوانید برای انجام انچه نیاز دارید و برای تعریف API مورد نیاز خود به خوبی تنظیم کنید. - -اما به طور پیش فرض، همه چیز **کار میکند**. - -### اعتبارسنجی - -* اعتبارسنجی برای بیشتر (یا همه؟) **data type** های پایتون، شامل: - - * JSON objects (`dict`) - * آرایه های (‍‍‍‍`list`) JSON با قابلیت مشخص سازی تایپ ایتم های درون لیست. - * فیلد های رشته (`str`)، به همراه مشخص سازی حداقل و حداکثر طول رشته. - * اعداد (‍‍`int`,`float`) با حداقل و حداکثر مقدار و غیره. - -* اعتبارسنجی برای تایپ های عجیب تر، مثل: - * URL. - * Email. - * UUID. - * و غیره. - -تمام اعتبارسنجی ها توسط کتابخانه اثبات شده و قدرتمند **Pydantic** انجام میشود. - -### امنیت و احراز هویت - -امنیت و احرازهویت بدون هیچگونه ارتباط و مصالحه ای با پایگاه های داده یا مدل های داده ایجاد شده اند. - -تمام طرح های امنیتی در OpenAPI تعریف شده اند، از جمله: - -* . -* **OAuth2** (همچنین با **JWT tokens**). آموزش را در [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=\_blank} مشاهده کنید. -* کلید های API: - * Headers - * Query parameters - * Cookies، و غیره. - -به علاوه تمام ویژگی های امنیتی از **Statlette** (شامل **session cookies**) - -همه اینها به عنوان ابزارها و اجزای قابل استفاده ای ساخته شده اند که به راحتی با سیستم های شما، مخازن داده، پایگاه های داده رابطه ای و NoSQL و غیره ادغام میشوند. - -### Dependency Injection - -FastAPI شامل یک سیستم Dependency Injection بسیار آسان اما بسیار قدرتمند است. - -* حتی وابستگی ها نیز میتوانند وابستگی هایی داشته باشند و یک سلسله مراتب یا **"گرافی" از وابستگی ها** ایجاد کنند. - -* همه چیز توسط فریم ورک **به طور خودکار اداره میشود** - -* همه وابستگی ها میتوانند به داده های request ها نیاز داشته باشند و مستندات خودکار و محدودیت های path operation را **افزایش** دهند. - -* با قابلیت **اعتبارسنجی خودکار** حتی برای path operation parameter های تعریف شده در وابستگی ها. - -* پشتیبانی از سیستم های پیچیده احرازهویت کاربر، **اتصالات پایگاه داده** و غیره. - -* بدون هیچ ارتباطی با دیتابیس ها، فرانت اند و غیره. اما ادغام آسان و راحت با همه آنها. - -### پلاگین های نامحدود - -یا به عبارت دیگر، هیچ نیازی به آنها نیست، کد موردنیاز خود را وارد و استفاده کنید. - -هر یکپارچه سازی به گونه ای طراحی شده است که استفاده از آن بسیار ساده باشد (با وابستگی ها) که میتوانید با استفاده از همان ساختار و روشی که برای _path operation_ های خود استفاده کرده اید تنها در ۲ خط کد "پلاگین" برنامه خودتان را ایجاد کنید. - -### تست شده - -* 100% پوشش تست. - -* 100% کد بر اساس type annotate ها. - -* استفاده شده در اپلیکیشن های تولید - -## ویژگی های Starlette - -**FastAPI** کاملا (و براساس) با Starlette سازگار است. بنابراین، هرکد اضافی Starlette که دارید، نیز کار خواهد کرد. - -‍‍`FastAPI` در واقع یک زیرکلاس از `Starlette` است. بنابراین اگر از قبل Starlette را میشناسید یا با آن کار کرده اید، بیشتر قابلیت ها به همین روش کار خواهد کرد. - -با **FastAPI** شما تمام ویژگی های **Starlette** را خواهید داشت (زیرا FastAPI یک نسخه و نمونه به تمام معنا از Starlette است): - -* عملکرد به طورجدی چشمگیر. این یکی از سریعترین فریم ورک های موجود در پایتون است که همتراز با **نود جی اس** و **گو** است. -* پشتیبانی از **WebSocket**. -* تسک های درجریان در پس زمینه. -* رویداد های راه اندازی و متوفق شدن. -* تست کلاینت ساخته شده به روی HTTPX. -* **CORS**, GZip, فایل های استاتیک, پاسخ های جریانی. -* پشتیبانی از **نشست ها و کوکی ها**. -* 100% پوشش با تست. -* 100% کد براساس type annotate ها. - -## ویژگی های Pydantic - -**FastAPI** کاملا (و براساس) با Pydantic سازگار است. بنابراین هرکد Pydantic اضافی که داشته باشید، نیز کار خواهد کرد. - -از جمله کتابخانه های خارجی نیز مبتنی بر Pydantic میتوان به ORM و ODM ها برای دیتابیس ها اشاره کرد. - -این همچنین به این معناست که در خیلی از موارد میتوانید همان ابجکتی که از request میگیرید را **مستقیما به دیتابیس** بفرستید زیرا همه چیز به طور خودکار تأیید میشود. - -همین امر برعکس نیز صدق می‌کند، در بسیاری از موارد شما می‌توانید ابجکتی را که از پایگاه داده دریافت می‌کنید را **مستقیماً به کاربر** ارسال کنید. - -با FastAPI شما تمام ویژگی های Pydantic را دراختیار دارید (زیرا FastAPI برای تمام بخش مدیریت دیتا بر اساس Pydantic عمل میکند): - -* **خبری از گیج شدن نیست**: - * هیچ زبان خردی برای یادگیری تعریف طرحواره های جدید وجود ندارد. - * اگر تایپ های پایتون را میشناسید، نحوه استفاده از Pydantic را نیز میدانید. -* به خوبی با **IDE/linter/مغز** شما عمل میکند: - * به این دلیل که ساختار داده Pydantic فقط نمونه هایی از کلاس هایی هستند که شما تعریف میکنید، تکمیل خودکار، mypy، linting و مشاهده شما باید به درستی با داده های معتبر شما کار کنند. -* اعتبار سنجی **ساختارهای پیچیده**: - * استفاده از مدل های سلسله مراتبی Pydantic, `List` و `Dict` کتابخانه `typing` پایتون و غیره. - * و اعتبارسنج ها اجازه میدهند که طرحواره های داده پیچیده به طور واضح و آسان تعریف، بررسی و بر پایه JSON مستند شوند. - * شما میتوانید ابجکت های عمیقا تودرتو JSON را که همگی تایید شده و annotated شده اند را داشته باشید. -* **قابل توسعه**: - * Pydantic اجازه میدهد تا data type های سفارشی تعریف شوند یا میتوانید اعتبارسنجی را با روش هایی به روی مدل ها با validator decorator گسترش دهید. -* 100% پوشش با تست. diff --git a/docs/fa/docs/index.md b/docs/fa/docs/index.md deleted file mode 100644 index 0f1cbe8e60..0000000000 --- a/docs/fa/docs/index.md +++ /dev/null @@ -1,467 +0,0 @@ -# FastAPI - - - -

- FastAPI -

-

- فریم‌ورک FastAPI، کارایی بالا، یادگیری آسان، کدنویسی سریع، آماده برای استفاده در محیط پروداکشن -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**مستندات**: https://fastapi.tiangolo.com - -**کد منبع**: https://github.com/fastapi/fastapi - ---- -FastAPI یک وب فریم‌ورک مدرن و سریع (با کارایی بالا) برای ایجاد APIهای متنوع (وب، وب‌سوکت و غبره) با زبان پایتون نسخه +۳.۶ است. این فریم‌ورک با رعایت کامل راهنمای نوع داده (Type Hint) ایجاد شده است. - -ویژگی‌های کلیدی این فریم‌ورک عبارتند از: - -* **سرعت**: کارایی بسیار بالا و قابل مقایسه با **NodeJS** و **Go** (با تشکر از Starlette و Pydantic). [یکی از سریع‌ترین فریم‌ورک‌های پایتونی موجود](#_10). - -* **کدنویسی سریع**: افزایش ۲۰۰ تا ۳۰۰ درصدی سرعت توسعه قابلیت‌های جدید. * -* **باگ کمتر**: کاهش ۴۰ درصدی خطاهای انسانی (برنامه‌نویسی). * -* **هوشمندانه**: پشتیبانی فوق‌العاده در محیط‌های توسعه یکپارچه (IDE). تکمیل در همه بخش‌های کد. کاهش زمان رفع باگ. -* **آسان**: طراحی شده برای یادگیری و استفاده آسان. کاهش زمان مورد نیاز برای مراجعه به مستندات. -* **کوچک**: کاهش تکرار در کد. چندین قابلیت برای هر پارامتر (منظور پارامترهای ورودی تابع هندلر می‌باشد، به بخش خلاصه در همین صفحه مراجعه شود). باگ کمتر. -* **استوار**: ایجاد کدی آماده برای استفاده در محیط پروداکشن و تولید خودکار مستندات تعاملی -* **مبتنی بر استانداردها**: مبتنی بر (و منطبق با) استانداردهای متن باز مربوط به API: OpenAPI (سوگر سابق) و JSON Schema. - -* تخمین‌ها بر اساس تست‌های انجام شده در یک تیم توسعه داخلی که مشغول ایجاد برنامه‌های کاربردی واقعی بودند صورت گرفته است. - -## اسپانسرهای طلایی - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor %} -{% endif %} - - - -دیگر اسپانسرها - -## نظر دیگران در مورد FastAPI - -
[...] I'm using FastAPI a ton these days. [...] I'm actually planning to use it for all of my team's ML services at Microsoft. Some of them are getting integrated into the core Windows product and some Office products."
- -
Kabir Khan - Microsoft (ref)
- ---- - -
"We adopted the FastAPI library to spawn a RESTserver that can be queried to obtain predictions. [for Ludwig]"
- -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -
"Netflix is pleased to announce the open-source release of our crisis management orchestration framework: Dispatch! [built with FastAPI]"
- -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -
"I’m over the moon excited about FastAPI. It’s so fun!"
- -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -
"Honestly, what you've built looks super solid and polished. In many ways, it's what I wanted Hug to be - it's really inspiring to see someone build that."
- -
Timothy Crosley - Hug creator (ref)
- ---- - -
"If you're looking to learn one modern framework for building REST APIs, check out FastAPI [...] It's fast, easy to use and easy to learn [...]"
- -
"We've switched over to FastAPI for our APIs [...] I think you'll like it [...]"
- -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
- ---- - -## **Typer**, فریم‌ورکی معادل FastAPI برای کار با واسط خط فرمان - - - -اگر در حال ساختن برنامه‌ای برای استفاده در CLI (به جای استفاده در وب) هستید، می‌توانید از **Typer**. استفاده کنید. - -**Typer** دوقلوی کوچکتر FastAPI است و قرار است معادلی برای FastAPI در برنامه‌های CLI باشد.️ 🚀 - -## نیازمندی‌ها - -پایتون +۳.۶ - -FastAPI مبتنی بر ابزارهای قدرتمند زیر است: - -* فریم‌ورک Starlette برای بخش وب. -* کتابخانه Pydantic برای بخش داده‌. - -## نصب - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -نصب یک سرور پروداکشن نظیر Uvicorn یا Hypercorn نیز جزء نیازمندی‌هاست. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## مثال - -### ایجاد کنید -* فایلی به نام `main.py` با محتوای زیر ایجاد کنید: - -```Python -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} -``` - -
-همچنین می‌توانید از async def... نیز استفاده کنید - -اگر در کدتان از `async` / `await` استفاده می‌کنید، از `async def` برای تعریف تابع خود استفاده کنید: - -```Python hl_lines="9 14" -from typing import Optional - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Optional[str] = None): - return {"item_id": item_id, "q": q} -``` - -**توجه**: - -اگر با `async / await` آشنا نیستید، به بخش _"عجله‌ دارید?"_ در صفحه درباره `async` و `await` در مستندات مراجعه کنید. - - -
- -### اجرا کنید - -با استفاده از دستور زیر سرور را اجرا کنید: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-درباره دستور uvicorn main:app --reload... - -دستور `uvicorn main:app` شامل موارد زیر است: - -* `main`: فایل `main.py` (ماژول پایتون ایجاد شده). -* `app`: شیء ایجاد شده در فایل `main.py` در خط `app = FastAPI()`. -* `--reload`: ریستارت کردن سرور با تغییر کد. تنها در هنگام توسعه از این گزینه استفاده شود.. - -
- -### بررسی کنید - -آدرس http://127.0.0.1:8000/items/5?q=somequery را در مرورگر خود باز کنید. - -پاسخ JSON زیر را مشاهده خواهید کرد: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -تا اینجا شما APIای ساختید که: - -* درخواست‌های HTTP به _مسیرهای_ `/` و `/items/{item_id}` را دریافت می‌کند. -* هردو _مسیر_ عملیات (یا HTTP _متد_) `GET` را پشتیبانی می‌کند. -* _مسیر_ `/items/{item_id}` شامل _پارامتر مسیر_ `item_id` از نوع `int` است. -* _مسیر_ `/items/{item_id}` شامل _پارامتر پرسمان_ اختیاری `q` از نوع `str` است. - -### مستندات API تعاملی - -حال به آدرس http://127.0.0.1:8000/docs بروید. - -مستندات API تعاملی (ایجاد شده به کمک Swagger UI) را مشاهده خواهید کرد: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### مستندات API جایگزین - -حال به آدرس http://127.0.0.1:8000/redoc بروید. - -مستندات خودکار دیگری را مشاهده خواهید کرد که به کمک ReDoc ایجاد می‌شود: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## تغییر مثال - -حال فایل `main.py` را مطابق زیر ویرایش کنید تا بتوانید بدنه یک درخواست `PUT` را دریافت کنید. - -به کمک Pydantic بدنه درخواست را با انواع استاندارد پایتون تعریف کنید. - -```Python hl_lines="4 9-12 25-27" -from typing import Optional - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Union[bool, None] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -سرور به صورت خودکار ری‌استارت می‌شود (زیرا پیشتر از گزینه `--reload` در دستور `uvicorn` استفاده کردیم). - -### تغییر مستندات API تعاملی - -مجددا به آدرس http://127.0.0.1:8000/docs بروید. - -* مستندات API تعاملی به صورت خودکار به‌روز شده است و شامل بدنه تعریف شده در مرحله قبل است: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* روی دکمه "Try it out" کلیک کنید، اکنون می‌توانید پارامترهای مورد نیاز هر API را مشخص کرده و به صورت مستقیم با آنها تعامل کنید: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* سپس روی دکمه "Execute" کلیک کنید، خواهید دید که واسط کاربری با APIهای تعریف شده ارتباط برقرار کرده، پارامترهای مورد نیاز را به آن‌ها ارسال می‌کند، سپس نتایج را دریافت کرده و در صفحه نشان می‌دهد: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### تغییر مستندات API جایگزین - -حال به آدرس http://127.0.0.1:8000/redoc بروید. - -* خواهید دید که مستندات جایگزین نیز به‌روزرسانی شده و شامل پارامتر پرسمان و بدنه تعریف شده می‌باشد: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### خلاصه - -به طور خلاصه شما **یک بار** انواع پارامترها، بدنه و غیره را به عنوان پارامترهای ورودی تابع خود تعریف می‌کنید. - - این کار را با استفاده از انواع استاندارد و مدرن موجود در پایتون انجام می‌دهید. - -نیازی به یادگیری نحو جدید یا متدها و کلاس‌های یک کتابخانه بخصوص و غیره نیست. - -تنها **پایتون +۳.۶**. - -به عنوان مثال برای یک پارامتر از نوع `int`: - -```Python -item_id: int -``` - -یا برای یک مدل پیچیده‌تر مثل `Item`: - -```Python -item: Item -``` - -...و با همین اعلان تمامی قابلیت‌های زیر در دسترس قرار می‌گیرد: - -* پشتیبانی ویرایشگر متنی شامل: - * تکمیل کد. - * بررسی انواع داده. -* اعتبارسنجی داده: - * خطاهای خودکار و مشخص در هنگام نامعتبر بودن داده. - * اعتبارسنجی، حتی برای اشیاء JSON تو در تو. -* تبدیل داده ورودی: که از شبکه رسیده به انواع و داد‌ه‌ پایتونی. این داده‌ شامل: - * JSON. - * پارامترهای مسیر. - * پارامترهای پرسمان. - * کوکی‌ها. - * سرآیند‌ها (هدرها). - * فرم‌ها. - * فایل‌ها. -* تبدیل داده خروجی: تبدیل از انواع و داده‌ پایتون به داده شبکه (مانند JSON): - * تبدیل انواع داده پایتونی (`str`, `int`, `float`, `bool`, `list` و غیره). - * اشیاء `datetime`. - * اشیاء `UUID`. - * qمدل‌های پایگاه‌داده. - * و موارد بیشمار دیگر. -* دو مدل مستند API تعاملی خودکار : - * Swagger UI. - * ReDoc. - ---- - -به مثال قبلی باز می‌گردیم، در این مثال **FastAPI** موارد زیر را انجام می‌دهد: - -* اعتبارسنجی اینکه پارامتر `item_id` در مسیر درخواست‌های `GET` و `PUT` موجود است. -* اعتبارسنجی اینکه پارامتر `item_id` در درخواست‌های `GET` و `PUT` از نوع `int` است. - * اگر غیر از این موارد باشد، سرویس‌گیرنده خطای مفید و مشخصی دریافت خواهد کرد. -* بررسی وجود پارامتر پرسمان اختیاری `q` (مانند `http://127.0.0.1:8000/items/foo?q=somequery`) در درخواست‌های `GET`. - * از آنجا که پارامتر `q` با `= None` مقداردهی شده است، این پارامتر اختیاری است. - * اگر از مقدار اولیه `None` استفاده نکنیم، این پارامتر الزامی خواهد بود (همانند بدنه درخواست در درخواست `PUT`). -* برای درخواست‌های `PUT` به آدرس `/items/{item_id}`، بدنه درخواست باید از نوع JSON تعریف شده باشد: - * بررسی اینکه بدنه شامل فیلدی با نام `name` و از نوع `str` است. - * بررسی اینکه بدنه شامل فیلدی با نام `price` و از نوع `float` است. - * بررسی اینکه بدنه شامل فیلدی اختیاری با نام `is_offer` است، که در صورت وجود باید از نوع `bool` باشد. - * تمامی این موارد برای اشیاء JSON در هر عمقی قابل بررسی می‌باشد. -* تبدیل از/به JSON به صورت خودکار. -* مستندسازی همه چیز با استفاده از OpenAPI، که می‌توان از آن برای موارد زیر استفاده کرد: - * سیستم مستندات تعاملی. - * تولید خودکار کد سرویس‌گیرنده‌ در زبان‌های برنامه‌نویسی بیشمار. -* فراهم سازی ۲ مستند تعاملی مبتنی بر وب به صورت پیش‌فرض. - ---- - -موارد ذکر شده تنها پاره‌ای از ویژگی‌های بیشمار FastAPI است اما ایده‌ای کلی از طرز کار آن در اختیار قرار می‌دهد. - -خط زیر را به این صورت تغییر دهید: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -از: - -```Python - ... "item_name": item.name ... -``` - -به: - -```Python - ... "item_price": item.price ... -``` - -در حین تایپ کردن توجه کنید که چگونه ویرایش‌گر، ویژگی‌های کلاس `Item` را تشخیص داده و به تکمیل خودکار آنها کمک می‌کند: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -برای مشاهده مثال‌های کامل‌تر که شامل قابلیت‌های بیشتری از FastAPI باشد به بخش آموزش - راهنمای کاربر مراجعه کنید. - -**هشدار اسپویل**: بخش آموزش - راهنمای کاربر شامل موارد زیر است: - -* اعلان **پارامترهای** موجود در بخش‌های دیگر درخواست، شامل: **سرآیند‌ (هدر)ها**، **کوکی‌ها**، **فیلد‌های فرم** و **فایل‌ها**. -* چگونگی تنظیم **محدودیت‌های اعتبارسنجی** به عنوان مثال `maximum_length` یا `regex`. -* سیستم **Dependency Injection** قوی و کاربردی. -* امنیت و تایید هویت, شامل پشتیبانی از **OAuth2** مبتنی بر **JWT tokens** و **HTTP Basic**. -* تکنیک پیشرفته برای تعریف **مدل‌های چند سطحی JSON** (بر اساس Pydantic). -* قابلیت‌های اضافی دیگر (بر اساس Starlette) شامل: - * **وب‌سوکت** - * **GraphQL** - * تست‌های خودکار آسان مبتنی بر HTTPX و `pytest` - * **CORS** - * **Cookie Sessions** - * و موارد بیشمار دیگر. - -## کارایی - -معیار (بنچمارک‌)های مستقل TechEmpower حاکی از آن است که برنامه‌های **FastAPI** که تحت Uvicorn اجرا می‌شود، یکی از سریع‌ترین فریم‌ورک‌های مبتنی بر پایتون، است که کمی ضعیف‌تر از Starlette و Uvicorn عمل می‌کند (فریم‌ورک و سروری که FastAPI بر اساس آنها ایجاد شده است) (*) - -برای درک بهتری از این موضوع به بخش بنچ‌مارک‌ها مراجعه کنید. - -## نیازمندی‌های اختیاری - -استفاده شده توسط Pydantic: - -* email-validator - برای اعتبارسنجی آدرس‌های ایمیل. - -استفاده شده توسط Starlette: - -* HTTPX - در صورتی که می‌خواهید از `TestClient` استفاده کنید. -* aiofiles - در صورتی که می‌خواهید از `FileResponse` و `StaticFiles` استفاده کنید. -* jinja2 - در صورتی که بخواهید از پیکربندی پیش‌فرض برای قالب‌ها استفاده کنید. -* python-multipart - در صورتی که بخواهید با استفاده از `request.form()` از قابلیت "تجزیه (parse)" فرم استفاده کنید. -* itsdangerous - در صورتی که بخواید از `SessionMiddleware` پشتیبانی کنید. -* pyyaml - برای پشتیبانی `SchemaGenerator` در Starlet (به احتمال زیاد برای کار کردن با FastAPI به آن نیازی پیدا نمی‌کنید). -* graphene - در صورتی که از `GraphQLApp` پشتیبانی می‌کنید. - -استفاده شده توسط FastAPI / Starlette: - -* uvicorn - برای سرور اجرا کننده برنامه وب. -* orjson - در صورتی که بخواهید از `ORJSONResponse` استفاده کنید. -* ujson - در صورتی که بخواهید از `UJSONResponse` استفاده کنید. - -می‌توان همه این موارد را با استفاده از دستور `pip install fastapi[all]`. به صورت یکجا نصب کرد. - -## لایسنس - -این پروژه مشمول قوانین و مقررات لایسنس MIT است. diff --git a/docs/fa/docs/learn/index.md b/docs/fa/docs/learn/index.md deleted file mode 100644 index 06aa7f00e7..0000000000 --- a/docs/fa/docs/learn/index.md +++ /dev/null @@ -1,5 +0,0 @@ -# یادگیری - -اینجا بخش‌های مقدماتی و آموزش‌هایی هستن که برای یادگیری **FastAPI** بهت کمک می‌کنن. - -می‌تونی اینو یه **کتاب**، یه **دوره آموزشی**، یا راه **رسمی** و پیشنهادی برای یادگیری FastAPI در نظر بگیری. 😎 diff --git a/docs/fa/docs/python-types.md b/docs/fa/docs/python-types.md deleted file mode 100644 index c428acbf74..0000000000 --- a/docs/fa/docs/python-types.md +++ /dev/null @@ -1,578 +0,0 @@ -# مقدمه‌ای بر انواع نوع در پایتون - -پایتون از "نوع‌نما"های اختیاری (که بهشون "type hints" یا "type annotations" هم می‌گن) پشتیبانی می‌کنه. - -این **"نوع‌نماها"** یا annotationها یه سینتکس خاص هستن که بهت اجازه می‌دن نوع یه متغیر رو مشخص کنی. - -با مشخص کردن نوع متغیرها، ویرایشگرها و ابزارها می‌تونن پشتیبانی بهتری بهت بدن. - -این فقط یه **آموزش سریع / یادآوری** در مورد نوع‌نماهای پایتونه. فقط حداقل چیزایی که برای استفاده ازشون با **FastAPI** لازمه رو پوشش می‌ده... که در واقع خیلی کمه. - -**FastAPI** کاملاً بر پایه این نوع‌نماهاست و این بهش کلی مزیت و فایده می‌ده. - -ولی حتی اگه هیچ‌وقت از **FastAPI** استفاده نکنی، بازم یادگیری یه کم در موردشون به نفعته. - -/// note - -اگه حرفه‌ای پایتونی و همه‌چیز رو در مورد نوع‌نماها می‌دونی، برو سراغ فصل بعدی. - -/// - -## انگیزه - -بیاید با یه مثال ساده شروع کنیم: - -{* ../../docs_src/python_types/tutorial001.py *} - -وقتی این برنامه رو اجرا کنی، خروجی اینه: - -``` -John Doe -``` - -این تابع این کارا رو می‌کنه: - -* یه `first_name` و `last_name` می‌گیره. -* حرف اول هر کدوم رو با `title()` بزرگ می‌کنه. -* ترکیبشون می‌کنه با یه فاصله وسطشون. - -{* ../../docs_src/python_types/tutorial001.py hl[2] *} - -### ویرایشش کن - -این یه برنامه خیلی ساده‌ست. - -ولی حالا تصور کن داری از صفر می‌نویسیش. - -یه جایی شروع کردی به تعریف تابع، پارامترهات آماده‌ست... - -ولی بعد باید "اون متدی که حرف اول رو بزرگ می‌کنه" رو صدا کنی. - -آیا اسمش `upper` بود؟ یا `uppercase`؟ شاید `first_uppercase`؟ یا `capitalize`؟ - -بعد، با دوست قدیمی برنامه‌نویسا، تکمیل خودکار ویرایشگر، امتحان می‌کنی. - -پارامتر اول تابع، `first_name` رو تایپ می‌کنی، بعد یه نقطه (`.`) می‌ذاری و `Ctrl+Space` رو می‌زنی تا تکمیل خودکار بیاد. - -ولی متأسفانه، چیز مفیدی نمی‌گیری: - - - -### نوع اضافه کن - -بیا فقط یه خط از نسخه قبلی رو تغییر بدیم. - -دقیقاً این بخش، پارامترهای تابع رو، از: - -```Python - first_name, last_name -``` - -به: - -```Python - first_name: str, last_name: str -``` - -عوض می‌کنیم. - -همینه. - -اینا همون "نوع‌نماها" هستن: - -{* ../../docs_src/python_types/tutorial002.py hl[1] *} - -این با تعریف مقدار پیش‌فرض فرق داره، مثل: - -```Python - first_name="john", last_name="doe" -``` - -یه چیز متفاوته. - -ما از دونقطه (`:`) استفاده می‌کنیم، نه علامت مساوی (`=`)‌. - -و اضافه کردن نوع‌نماها معمولاً چیزی که اتفاق می‌افته رو از چیزی که بدون اونا می‌افتاد تغییر نمی‌ده. - -ولی حالا، دوباره تصور کن وسط ساختن اون تابع هستی، ولی این بار با نوع‌نماها. - -توی همون نقطه، سعی می‌کنی تکمیل خودکار رو با `Ctrl+Space` فعال کنی و اینو می‌بینی: - - - -با این، می‌تونی اسکرول کنی، گزینه‌ها رو ببینی، تا وقتی که اون چیزی که "به نظرت آشنا میاد" رو پیدا کنی: - - - -## انگیزه بیشتر - -این تابع رو چک کن، الان نوع‌نما داره: - -{* ../../docs_src/python_types/tutorial003.py hl[1] *} - -چون ویرایشگر نوع متغیرها رو می‌دونه، فقط تکمیل خودکار نمی‌گیری، بلکه چک خطاها هم داری: - - - -حالا می‌دونی که باید درستش کنی، `age` رو با `str(age)` به یه رشته تبدیل کنی: - -{* ../../docs_src/python_types/tutorial004.py hl[2] *} - -## تعریف نوع‌ها - -تازه اصلی‌ترین جا برای تعریف نوع‌نماها رو دیدی. به‌عنوان پارامترهای تابع. - -این هم اصلی‌ترین جاییه که با **FastAPI** ازشون استفاده می‌کنی. - -### نوع‌های ساده - -می‌تونی همه نوع‌های استاندارد پایتون رو تعریف کنی، نه فقط `str`. - -مثلاً می‌تونی از اینا استفاده کنی: - -* `int` -* `float` -* `bool` -* `bytes` - -{* ../../docs_src/python_types/tutorial005.py hl[1] *} - -### نوع‌های عمومی با پارامترهای نوع - -یه سری ساختار داده هستن که می‌تونن مقدارهای دیگه رو نگه دارن، مثل `dict`، `list`، `set` و `tuple`. و مقدارهای داخلیشون هم می‌تونن نوع خودشون رو داشته باشن. - -به این نوع‌ها که نوع‌های داخلی دارن می‌گن "**عمومی**" یا "generic". و می‌شه اونا رو تعریف کرد، حتی با نوع‌های داخلیشون. - -برای تعریف این نوع‌ها و نوع‌های داخلیشون، می‌تونی از ماژول استاندارد پایتون `typing` استفاده کنی. این ماژول مخصوص پشتیبانی از نوع‌نماهاست. - -#### نسخه‌های جدیدتر پایتون - -سینتکس با استفاده از `typing` با همه نسخه‌ها، از پایتون 3.6 تا جدیدترین‌ها، از جمله پایتون 3.9، 3.10 و غیره **سازگاره**. - -با پیشرفت پایتون، **نسخه‌های جدیدتر** پشتیبانی بهتری برای این نوع‌نماها دارن و توی خیلی موارد حتی لازم نیست ماژول `typing` رو وارد کنی و ازش برای تعریف نوع‌نماها استفاده کنی. - -اگه بتونی برای پروژه‌ات از یه نسخه جدیدتر پایتون استفاده کنی، می‌تونی از این سادگی اضافه بهره ببری. - -توی همه مستندات، مثال‌هایی هستن که با هر نسخه پایتون سازگارن (وقتی تفاوتی هست). - -مثلاً "**Python 3.6+**" یعنی با پایتون 3.6 یا بالاتر (مثل 3.7، 3.8، 3.9، 3.10 و غیره) سازگاره. و "**Python 3.9+**" یعنی با پایتون 3.9 یا بالاتر (مثل 3.10 و غیره) سازگاره. - -اگه بتونی از **جدیدترین نسخه‌های پایتون** استفاده کنی، از مثال‌های نسخه آخر استفاده کن، چون اونا **بهترین و ساده‌ترین سینتکس** رو دارن، مثلاً "**Python 3.10+**". - -#### لیست - -مثلاً، بیایم یه متغیر تعریف کنیم که یه `list` از `str` باشه. - -//// tab | Python 3.9+ - -متغیر رو با همون سینتکس دونقطه (`:`) تعریف کن. - -به‌عنوان نوع، `list` رو بذار. - -چون لیست یه نوعه که نوع‌های داخلی داره، اونا رو توی کروشه‌ها می‌ذاری: - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -از `typing`، `List` رو (با `L` بزرگ) وارد کن: - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -متغیر رو با همون سینتکس دونقطه (`:`) تعریف کن. - -به‌عنوان نوع، `List` رو که از `typing` وارد کردی بذار. - -چون لیست یه نوعه که نوع‌های داخلی داره، اونا رو توی کروشه‌ها می‌ذاری: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// - -/// info - -اون نوع‌های داخلی توی کروشه‌ها بهشون "پارامترهای نوع" می‌گن. - -توی این مورد، `str` پارامتر نوعیه که به `List` (یا `list` توی پایتون 3.9 و بالاتر) پاس داده شده. - -/// - -یعنی: "متغیر `items` یه `list` هست، و هر کدوم از آیتم‌های این لیست یه `str` هستن". - -/// tip - -اگه از پایتون 3.9 یا بالاتر استفاده می‌کنی، لازم نیست `List` رو از `typing` وارد کنی، می‌تونی همون نوع معمولی `list` رو به جاش استفاده کنی. - -/// - -با این کار، ویرایشگرت حتی وقتی داری آیتم‌های لیست رو پردازش می‌کنی بهت کمک می‌کنه: - - - -بدون نوع‌ها، رسیدن به این تقریباً غیرممکنه. - -توجه کن که متغیر `item` یکی از عناصر توی لیست `items` هست. - -و با این حال، ویرایشگر می‌دونه که یه `str` هست و براش پشتیبانی می‌ده. - -#### تاپل و ست - -برای تعریف `tuple`ها و `set`ها هم همین کار رو می‌کنی: - -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// - -یعنی: - -* متغیر `items_t` یه `tuple` با 3 تا آیتمه، یه `int`، یه `int` دیگه، و یه `str`. -* متغیر `items_s` یه `set` هست، و هر کدوم از آیتم‌هاش از نوع `bytes` هستن. - -#### دیکشنری - -برای تعریف یه `dict`، 2 تا پارامتر نوع می‌دی، که با کاما از هم جدا شدن. - -پارامتر نوع اول برای کلیدهای `dict` هست. - -پارامتر نوع دوم برای مقدارهای `dict` هست: - -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// - -یعنی: - -* متغیر `prices` یه `dict` هست: - * کلیدهای این `dict` از نوع `str` هستن (مثلاً اسم هر آیتم). - * مقدارهای این `dict` از نوع `float` هستن (مثلاً قیمت هر آیتم). - -#### اتحادیه - -می‌تونی تعریف کنی که یه متغیر می‌تونه هر کدوم از **چند تا نوع** باشه، مثلاً یه `int` یا یه `str`. - -توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) می‌تونی از نوع `Union` توی `typing` استفاده کنی و نوع‌های ممکن رو توی کروشه‌ها بذاری. - -توی پایتون 3.10 یه **سینتکس جدید** هم هست که می‌تونی نوع‌های ممکن رو با یه خط عمودی (`|`) جدا کنی. - -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008b_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} -``` - -//// - -توی هر دو حالت یعنی `item` می‌تونه یه `int` یا یه `str` باشه. - -#### شاید `None` - -می‌تونی تعریف کنی که یه مقدار می‌تونه یه نوع باشه، مثلاً `str`، ولی می‌تونه `None` هم باشه. - -توی پایتون 3.6 و بالاتر (از جمله پایتون 3.10) می‌تونی با وارد کردن و استفاده از `Optional` از ماژول `typing` اینو تعریف کنی. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} -``` - -استفاده از `Optional[str]` به جای فقط `str` به ویرایشگر کمک می‌کنه خطاهایی که ممکنه فکر کنی یه مقدار همیشه `str` هست رو پیدا کنه، در حالی که می‌تونه `None` هم باشه. - -`Optional[Something]` در واقع میان‌بر برای `Union[Something, None]` هست، این دو تا معادلن. - -یعنی توی پایتون 3.10، می‌تونی از `Something | None` استفاده کنی: - -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial009_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} -``` - -//// - -//// tab | Python 3.8+ جایگزین - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} -``` - -//// - -#### استفاده از `Union` یا `Optional` - -اگه از نسخه پایتون زیر 3.10 استفاده می‌کنی، یه نکته از دید خیلی **شخصی** خودم: - -* 🚨 از `Optional[SomeType]` استفاده نکن -* به جاش ✨ **از `Union[SomeType, None]` استفاده کن** ✨. - -هر دو معادلن و زیر پوسته یکی‌ان، ولی من `Union` رو به `Optional` ترجیح می‌دم چون کلمه "**اختیاری**" انگار暗示 می‌کنه که مقدار اختیاریه، در حالی که در واقع یعنی "می‌تونه `None` باشه"، حتی اگه اختیاری نباشه و هنوز لازم باشه. - -فکر می‌کنم `Union[SomeType, None]` واضح‌تر نشون می‌ده چی معنی می‌ده. - -فقط بحث کلمات و اسم‌هاست. ولی این کلمات می‌تونن رو طرز فکر تو و تیمت نسبت به کد تأثیر بذارن. - -به‌عنوان مثال، این تابع رو ببین: - -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} - -پارامتر `name` به‌عنوان `Optional[str]` تعریف شده، ولی **اختیاری نیست**، نمی‌تونی تابع رو بدون پارامتر صدا کنی: - -```Python -say_hi() # اوه نه، این خطا می‌ده! 😱 -``` - -پارامتر `name` **هنوز لازمه** (نه *اختیاری*) چون مقدار پیش‌فرض نداره. با این حال، `name` مقدار `None` رو قبول می‌کنه: - -```Python -say_hi(name=None) # این کار می‌کنه، None معتبره 🎉 -``` - -خبر خوب اینه که وقتی رو پایتون 3.10 باشی، لازم نیست نگران این باشی، چون می‌تونی به‌سادگی از `|` برای تعریف اتحادیه نوع‌ها استفاده کنی: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - -اون موقع دیگه لازم نیست نگران اسم‌هایی مثل `Optional` و `Union` باشی. 😎 - -#### نوع‌های عمومی - -این نوع‌هایی که پارامترهای نوع رو توی کروشه‌ها می‌گیرن بهشون **نوع‌های عمومی** یا **Generics** می‌گن، مثلاً: - -//// tab | Python 3.10+ - -می‌تونی از همون نوع‌های داخلی به‌عنوان نوع‌های عمومی استفاده کنی (با کروشه‌ها و نوع‌ها داخلشون): - -* `list` -* `tuple` -* `set` -* `dict` - -و همون‌طور که توی پایتون 3.8 بود، از ماژول `typing`: - -* `Union` -* `Optional` (همون‌طور که توی پایتون 3.8 بود) -* ...و بقیه. - -توی پایتون 3.10، به‌عنوان جایگزین برای استفاده از نوع‌های عمومی `Union` و `Optional`، می‌تونی از خط عمودی (`|`) برای تعریف اتحادیه نوع‌ها استفاده کنی، که خیلی بهتر و ساده‌تره. - -//// - -//// tab | Python 3.9+ - -می‌تونی از همون نوع‌های داخلی به‌عنوان نوع‌های عمومی استفاده کنی (با کروشه‌ها و نوع‌ها داخلشون): - -* `list` -* `tuple` -* `set` -* `dict` - -و همون‌طور که توی پایتون 3.8 بود، از ماژول `typing`: - -* `Union` -* `Optional` -* ...و بقیه. - -//// - -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...و بقیه. - -//// - -### کلاس‌ها به‌عنوان نوع - -می‌تونی یه کلاس رو هم به‌عنوان نوع یه متغیر تعریف کنی. - -فرض کن یه کلاس `Person` داری، با یه نام: - -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} - -بعد می‌تونی یه متغیر رو از نوع `Person` تعریف کنی: - -{* ../../docs_src/python_types/tutorial010.py hl[6] *} - -و بعد، دوباره، همه پشتیبانی ویرایشگر رو داری: - - - -توجه کن که این یعنی "`one_person` یه **نمونه** از کلاس `Person` هست". - -یعنی "`one_person` خود **کلاس** به اسم `Person` نیست". - -## مدل‌های Pydantic - -Pydantic یه کتابخونه پایتونه برای اعتبارسنجی داده‌ها. - -"شکل" داده‌ها رو به‌عنوان کلاس‌هایی با ویژگی‌ها تعریف می‌کنی. - -و هر ویژگی یه نوع داره. - -بعد یه نمونه از اون کلاس رو با یه سری مقدار می‌سازی و اون مقدارها رو اعتبارسنجی می‌کنه، به نوع مناسب تبدیلشون می‌کنه (اگه لازم باشه) و یه شیء با همه داده‌ها بهت می‌ده. - -و با اون شیء نهایی همه پشتیبانی ویرایشگر رو می‌گیری. - -یه مثال از مستندات رسمی Pydantic: - -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// - -/// info - -برای اطلاعات بیشتر در مورد Pydantic، مستنداتش رو چک کن. - -/// - -**FastAPI** کاملاً بر پایه Pydantic هست. - -توی [آموزش - راهنمای کاربر](tutorial/index.md){.internal-link target=_blank} خیلی بیشتر از اینا رو توی عمل می‌بینی. - -/// tip - -Pydantic یه رفتار خاص داره وقتی از `Optional` یا `Union[Something, None]` بدون مقدار پیش‌فرض استفاده می‌کنی، می‌تونی توی مستندات Pydantic در مورد فیلدهای اختیاری لازم بیشتر بخونی. - -/// - -## نوع‌نماها با Annotationهای متادیتا - -پایتون یه قابلیت هم داره که بهت اجازه می‌ده **متادیتا اضافی** رو توی این نوع‌نماها بذاری با استفاده از `Annotated`. - -//// tab | Python 3.9+ - -توی پایتون 3.9، `Annotated` بخشی از کتابخونه استاندارده، پس می‌تونی از `typing` واردش کنی. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -توی نسخه‌های زیر پایتون 3.9، `Annotated` رو از `typing_extensions` وارد می‌کنی. - -با **FastAPI** از قبل نصب شده. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// - -خود پایتون با این `Annotated` کاری نمی‌کنه. و برای ویرایشگرها و ابزارهای دیگه، نوع هنوز `str` هست. - -ولی می‌تونی از این فضا توی `Annotated` استفاده کنی تا به **FastAPI** متادیتای اضافی در مورد اینکه چطور می‌خوای برنامه‌ات رفتار کنه بدی. - -نکته مهم اینه که **اولین *پارامتر نوع*** که به `Annotated` می‌دی، **نوع واقعی** هست. بقیش فقط متادیتا برای ابزارهای دیگه‌ست. - -الان فقط باید بدونی که `Annotated` وجود داره، و اینکه پایتون استاندارده. 😎 - -بعداً می‌بینی که چقدر **قوی** می‌تونه باشه. - -/// tip - -اینکه این **پایتون استاندارده** یعنی هنوز **بهترین تجربه توسعه‌دهنده** رو توی ویرایشگرت، با ابزارهایی که برای تحلیل و بازسازی کدت استفاده می‌کنی و غیره می‌گیری. ✨ - -و همین‌طور کدت با خیلی از ابزارها و کتابخونه‌های دیگه پایتون خیلی سازگار می‌مونه. 🚀 - -/// - -## نوع‌نماها توی **FastAPI** - -**FastAPI** از این نوع‌نماها استفاده می‌کنه تا چند تا کار بکنه. - -با **FastAPI** پارامترها رو با نوع‌نماها تعریف می‌کنی و اینا رو می‌گیری: - -* **پشتیبانی ویرایشگر**. -* **چک نوع‌ها**. - -...و **FastAPI** از همون تعریف‌ها برای اینا استفاده می‌کنه: - -* **تعریف نیازها**: از پارامترهای مسیر درخواست، پارامترهای کوئری، هدرها، بدنه‌ها، وابستگی‌ها و غیره. -* **تبدیل داده**: از درخواست به نوع مورد نیاز. -* **اعتبارسنجی داده**: که از هر درخواست میاد: - * تولید **خطاهای خودکار** که به کلاینت برمی‌گرده وقتی داده نامعتبره. -* **مستندسازی** API با استفاده از OpenAPI: - * که بعدش توسط رابط‌های کاربری مستندات تعاملی خودکار استفاده می‌شه. - -اینا شاید همه‌ش انتزاعی به نظر بیاد. نگران نباش. همه اینا رو توی عمل توی [آموزش - راهنمای کاربر](tutorial/index.md){.internal-link target=_blank} می‌بینی. - -نکته مهم اینه که با استفاده از نوع‌های استاندارد پایتون، توی یه جا (به جای اضافه کردن کلاس‌های بیشتر، دکوراتورها و غیره)، **FastAPI** کلی از کار رو برات انجام می‌ده. - -/// info - -اگه همه آموزش رو گذروندی و برگشتی که بیشتر در مورد نوع‌ها ببینی، یه منبع خوب "تقلب‌نامه" از `mypy` هست. - -/// diff --git a/docs/fa/docs/tutorial/middleware.md b/docs/fa/docs/tutorial/middleware.md deleted file mode 100644 index d68c25d82b..0000000000 --- a/docs/fa/docs/tutorial/middleware.md +++ /dev/null @@ -1,63 +0,0 @@ -# میان‌افزار - middleware - -شما میتوانید میان‌افزارها را در **FastAPI** اضافه کنید. - -"میان‌افزار" یک تابع است که با هر درخواست(request) قبل از پردازش توسط هر path operation (عملیات مسیر) خاص کار می‌کند. همچنین با هر پاسخ(response) قبل از بازگشت آن نیز کار می‌کند. - -* هر **درخواستی (request)** که به برنامه شما می آید را می گیرد. -* سپس می تواند کاری برای آن **درخواست** انجام دهید یا هر کد مورد نیازتان را اجرا کنید. -* سپس **درخواست** را به بخش دیگری از برنامه (توسط یک path operation مشخص) برای پردازش ارسال می کند. -* سپس **پاسخ** تولید شده توسط برنامه را (توسط یک path operation مشخص) دریافت می‌کند. -* می تواند کاری با **پاسخ** انجام دهید یا هر کد مورد نیازتان را اجرا کند. -* سپس **پاسخ** را برمی گرداند. - -/// توجه | جزئیات فنی - -در صورت وجود وابستگی هایی با `yield`، کد خروجی **پس از** اجرای میان‌‌افزار اجرا خواهد شد. - -در صورت وجود هر گونه وظایف پس زمینه (که در ادامه توضیح داده می‌شوند)، تمام میان‌افزارها *پس از آن* اجرا خواهند شد. - -/// - -## ساخت یک میان افزار - -برای ایجاد یک میان‌افزار، از دکوریتور `@app.middleware("http")` در بالای یک تابع استفاده می‌شود. - -تابع میان افزار دریافت می کند: -* `درخواست` -* تابع `call_next` که `درخواست` را به عنوان پارامتر دریافت می کند - * این تابع `درخواست` را به *path operation* مربوطه ارسال می کند. - * سپس `پاسخ` تولید شده توسط *path operation* مربوطه را برمی‌گرداند. -* شما می‌توانید سپس `پاسخ` را تغییر داده و پس از آن را برگردانید. - -{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} - -/// نکته | به خاطر داشته باشید که هدرهای اختصاصی سفارشی را می توان با استفاده از پیشوند "X-" اضافه کرد. - -اما اگر هدرهای سفارشی دارید که می‌خواهید مرورگر کاربر بتواند آنها را ببیند، باید آنها را با استفاده از پارامتر `expose_headers` که در مستندات CORS از Starlette توضیح داده شده است، به پیکربندی CORS خود اضافه کنید. - -/// - -/// توجه | جزئیات فنی - -شما همچنین می‌توانید از `from starlette.requests import Request` استفاده کنید. - -**FastAPI** این را به عنوان یک سهولت برای شما به عنوان برنامه‌نویس فراهم می‌کند. اما این مستقیما از Starlette به دست می‌آید. - -/// - -### قبل و بعد از `پاسخ` - -شما می‌توانید کدی را برای اجرا با `درخواست`، قبل از اینکه هر *path operation* آن را دریافت کند، اضافه کنید. - -همچنین پس از تولید `پاسخ`، قبل از بازگشت آن، می‌توانید کدی را اضافه کنید. - -به عنوان مثال، می‌توانید یک هدر سفارشی به نام `X-Process-Time` که شامل زمان پردازش درخواست و تولید پاسخ به صورت ثانیه است، اضافه کنید. - -{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} - - ## سایر میان افزار - -شما می‌توانید بعداً در مورد میان‌افزارهای دیگر در [راهنمای کاربر پیشرفته: میان‌افزار پیشرفته](../advanced/middleware.md){.internal-link target=_blank} بیشتر بخوانید. - -شما در بخش بعدی در مورد این که چگونه با استفاده از یک میان‌افزار، CORS را مدیریت کنید، خواهید خواند. diff --git a/docs/fa/docs/tutorial/security/index.md b/docs/fa/docs/tutorial/security/index.md deleted file mode 100644 index c0827a8b33..0000000000 --- a/docs/fa/docs/tutorial/security/index.md +++ /dev/null @@ -1,106 +0,0 @@ -# امنیت - -روش‌های مختلفی برای مدیریت امنیت، تأیید هویت و اعتبارسنجی وجود دارد. - -عموماً این یک موضوع پیچیده و "سخت" است. - -در بسیاری از فریم ورک ها و سیستم‌ها، فقط مدیریت امنیت و تأیید هویت نیاز به تلاش و کد نویسی زیادی دارد (در بسیاری از موارد می‌تواند 50% یا بیشتر کل کد نوشته شده باشد). - - -فریم ورک **FastAPI** ابزارهای متعددی را در اختیار شما قرار می دهد تا به راحتی، با سرعت، به صورت استاندارد و بدون نیاز به مطالعه و یادگیری همه جزئیات امنیت، در مدیریت **امنیت** به شما کمک کند. - -اما قبل از آن، بیایید برخی از مفاهیم کوچک را بررسی کنیم. - -## عجله دارید؟ - -اگر به هیچ یک از این اصطلاحات اهمیت نمی دهید و فقط نیاز به افزودن امنیت با تأیید هویت بر اساس نام کاربری و رمز عبور دارید، *همین الان* به فصل های بعدی بروید. - -## پروتکل استاندارد OAuth2 - -پروتکل استاندارد OAuth2 یک مشخصه است که چندین روش برای مدیریت تأیید هویت و اعتبار سنجی تعریف می کند. - -این مشخصه بسیار گسترده است و چندین حالت استفاده پیچیده را پوشش می دهد. - -در آن روش هایی برای تأیید هویت با استفاده از "برنامه های شخص ثالث" وجود دارد. - -این همان چیزی است که تمامی سیستم های با "ورود با فیسبوک، گوگل، توییتر، گیت هاب" در پایین آن را استفاده می کنند. - -### پروتکل استاندارد OAuth 1 - -پروتکل استاندارد OAuth1 نیز وجود داشت که با OAuth2 خیلی متفاوت است و پیچیدگی بیشتری داشت، زیرا شامل مشخصات مستقیم در مورد رمزگذاری ارتباط بود. - -در حال حاضر OAuth1 بسیار محبوب یا استفاده شده نیست. - -پروتکل استاندارد OAuth2 روش رمزگذاری ارتباط را مشخص نمی کند، بلکه انتظار دارد که برنامه شما با HTTPS سرویس دهی شود. - -/// نکته - -در بخش در مورد **استقرار** ، شما یاد خواهید گرفت که چگونه با استفاده از Traefik و Let's Encrypt رایگان HTTPS را راه اندازی کنید. - -/// - -## استاندارد OpenID Connect - -استاندارد OpenID Connect، مشخصه‌ای دیگر است که بر پایه **OAuth2** ساخته شده است. - -این مشخصه، به گسترش OAuth2 می‌پردازد و برخی مواردی که در OAuth2 نسبتاً تردید برانگیز هستند را مشخص می‌کند تا سعی شود آن را با سایر سیستم‌ها قابل ارتباط کند. - -به عنوان مثال، ورود به سیستم گوگل از OpenID Connect استفاده می‌کند (که در زیر از OAuth2 استفاده می‌کند). - -اما ورود به سیستم فیسبوک، از OpenID Connect پشتیبانی نمی‌کند. به جای آن، نسخه خودش از OAuth2 را دارد. - -### استاندارد OpenID (نه "OpenID Connect" ) - -همچنین مشخصه "OpenID" نیز وجود داشت که سعی در حل مسائل مشابه OpenID Connect داشت، اما بر پایه OAuth2 ساخته نشده بود. - -بنابراین، یک سیستم جداگانه بود. - -اکنون این مشخصه کمتر استفاده می‌شود و محبوبیت زیادی ندارد. - -## استاندارد OpenAPI - -استاندارد OpenAPI (قبلاً با نام Swagger شناخته می‌شد) یک open specification برای ساخت APIs (که در حال حاضر جزئی از بنیاد لینوکس میباشد) است. - -فریم ورک **FastAPI** بر اساس **OpenAPI** است. - -این خاصیت، امکان دارد تا چندین رابط مستندات تعاملی خودکار(automatic interactive documentation interfaces)، تولید کد و غیره وجود داشته باشد. - -مشخصه OpenAPI روشی برای تعریف چندین "schemes" دارد. - -با استفاده از آن‌ها، شما می‌توانید از همه این ابزارهای مبتنی بر استاندارد استفاده کنید، از جمله این سیستم‌های مستندات تعاملی(interactive documentation systems). - -استاندارد OpenAPI شیوه‌های امنیتی زیر را تعریف می‌کند: - -* شیوه `apiKey`: یک کلید اختصاصی برای برنامه که می‌تواند از موارد زیر استفاده شود: - * پارامتر جستجو. - * هدر. - * کوکی. -* شیوه `http`: سیستم‌های استاندارد احراز هویت HTTP، از جمله: - * مقدار `bearer`: یک هدر `Authorization` با مقدار `Bearer` به همراه یک توکن. این از OAuth2 به ارث برده شده است. - * احراز هویت پایه HTTP. - * ویژگی HTTP Digest و غیره. -* شیوه `oauth2`: تمام روش‌های OAuth2 برای مدیریت امنیت (به نام "flows"). - * چندین از این flows برای ساخت یک ارائه‌دهنده احراز هویت OAuth 2.0 مناسب هستند (مانند گوگل، فیسبوک، توییتر، گیت‌هاب و غیره): - * ویژگی `implicit` - * ویژگی `clientCredentials` - * ویژگی `authorizationCode` - * اما یک "flow" خاص وجود دارد که می‌تواند به طور کامل برای مدیریت احراز هویت در همان برنامه به کار رود: - * بررسی `password`: چند فصل بعدی به مثال‌های این مورد خواهیم پرداخت. -* شیوه `openIdConnect`: یک روش برای تعریف نحوه کشف داده‌های احراز هویت OAuth2 به صورت خودکار. - * کشف خودکار این موضوع را که در مشخصه OpenID Connect تعریف شده است، مشخص می‌کند. - -/// نکته - -ادغام سایر ارائه‌دهندگان احراز هویت/اجازه‌دهی مانند گوگل، فیسبوک، توییتر، گیت‌هاب و غیره نیز امکان‌پذیر و نسبتاً آسان است. - -مشکل پیچیده‌ترین مسئله، ساخت یک ارائه‌دهنده احراز هویت/اجازه‌دهی مانند آن‌ها است، اما **FastAPI** ابزارهای لازم برای انجام این کار را با سهولت به شما می‌دهد و همه کارهای سنگین را برای شما انجام می‌دهد. - -/// - -## ابزارهای **FastAPI** - -فریم ورک FastAPI ابزارهایی برای هر یک از این شیوه‌های امنیتی در ماژول`fastapi.security` فراهم می‌کند که استفاده از این مکانیزم‌های امنیتی را ساده‌تر می‌کند. - -در فصل‌های بعدی، شما یاد خواهید گرفت که چگونه با استفاده از این ابزارهای ارائه شده توسط **FastAPI**، امنیت را به API خود اضافه کنید. - -همچنین، خواهید دید که چگونه به صورت خودکار در سیستم مستندات تعاملی ادغام می‌شود. diff --git a/docs/fa/mkdocs.yml b/docs/fa/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/fa/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml diff --git a/scripts/docs.py b/scripts/docs.py index 73f60e68c5..75583a1cb2 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -353,7 +353,6 @@ def get_updated_config_content() -> Dict[str, Any]: raise typer.Abort() use_name = f"{code} - {local_language_names[code]}" new_alternate.append({"link": url, "name": use_name}) - new_alternate.append({"link": "/em/", "name": "😉"}) config["extra"]["alternate"] = new_alternate return config From ed20bf6bf3a9ad8d7e93f02578ea24d20b52fca7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 20:59:17 +0000 Subject: [PATCH 089/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 93d7f51bc9..f90d191361 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🔥 Remove inactive/scarce translations to Persian. PR [#14542](https://github.com/fastapi/fastapi/pull/14542) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove translation to emoji to simplify the new setup with LLM autotranslations. PR [#14541](https://github.com/fastapi/fastapi/pull/14541) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for pt (update-outdated). PR [#14537](https://github.com/fastapi/fastapi/pull/14537) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for es (update-outdated). PR [#14532](https://github.com/fastapi/fastapi/pull/14532) by [@tiangolo](https://github.com/tiangolo). From 818ce0fa2f93f9001c7258616444243e09dacde3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 16 Dec 2025 13:05:18 -0800 Subject: [PATCH 090/140] =?UTF-8?q?=F0=9F=94=A5=20Remove=20inactive/scarce?= =?UTF-8?q?=20translations=20to=20Vietnamese=20(#14543)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/mkdocs.yml | 2 - docs/vi/docs/deployment/cloud.md | 14 - docs/vi/docs/deployment/index.md | 21 - docs/vi/docs/deployment/versions.md | 93 --- docs/vi/docs/environment-variables.md | 300 --------- docs/vi/docs/fastapi-cli.md | 75 --- docs/vi/docs/features.md | 200 ------ docs/vi/docs/index.md | 475 --------------- docs/vi/docs/python-types.md | 593 ------------------ docs/vi/docs/tutorial/first-steps.md | 335 ---------- docs/vi/docs/tutorial/index.md | 83 --- docs/vi/docs/tutorial/static-files.md | 40 -- docs/vi/docs/virtual-environments.md | 842 -------------------------- docs/vi/mkdocs.yml | 1 - 14 files changed, 3074 deletions(-) delete mode 100644 docs/vi/docs/deployment/cloud.md delete mode 100644 docs/vi/docs/deployment/index.md delete mode 100644 docs/vi/docs/deployment/versions.md delete mode 100644 docs/vi/docs/environment-variables.md delete mode 100644 docs/vi/docs/fastapi-cli.md delete mode 100644 docs/vi/docs/features.md delete mode 100644 docs/vi/docs/index.md delete mode 100644 docs/vi/docs/python-types.md delete mode 100644 docs/vi/docs/tutorial/first-steps.md delete mode 100644 docs/vi/docs/tutorial/index.md delete mode 100644 docs/vi/docs/tutorial/static-files.md delete mode 100644 docs/vi/docs/virtual-environments.md delete mode 100644 docs/vi/mkdocs.yml diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index ea7be2deeb..60d2f977e5 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -331,8 +331,6 @@ extra: name: tr - Türkçe - link: /uk/ name: uk - українська мова - - link: /vi/ - name: vi - Tiếng Việt - link: /zh/ name: zh - 简体中文 - link: /zh-hant/ diff --git a/docs/vi/docs/deployment/cloud.md b/docs/vi/docs/deployment/cloud.md deleted file mode 100644 index 1d76522c3e..0000000000 --- a/docs/vi/docs/deployment/cloud.md +++ /dev/null @@ -1,14 +0,0 @@ -# Triển khai FastAPI trên các Dịch vụ Cloud - -Bạn có thể sử dụng **bất kỳ nhà cung cấp dịch vụ cloud** nào để triển khai ứng dụng FastAPI của mình. - -Trong hầu hết các trường hợp, các nhà cung cấp dịch vụ cloud lớn đều có hướng dẫn triển khai FastAPI với họ. - -## Nhà cung cấp dịch vụ Cloud - Nhà tài trợ -Một vài nhà cung cấp dịch vụ cloud ✨ [**tài trợ cho FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, điều này giúp đảm bảo sự phát triển liên tục và khỏe mạnh của FastAPI và hệ sinh thái của nó. - -Thêm nữa, điều này cũng thể hiện cam kết thực sự của họ đối với FastAPI và **cộng đồng người dùng** (bạn), vì họ không chỉ muốn cung cấp cho bạn một **dịch vụ tốt** mà còn muốn đảm bảo rằng bạn có một **framework tốt và bền vững**, đó chính là FastAPI. 🙇 - -Bạn có thể thử các dịch vụ của họ và làm theo hướng dẫn của họ: - -* Render diff --git a/docs/vi/docs/deployment/index.md b/docs/vi/docs/deployment/index.md deleted file mode 100644 index 24ffdc71b0..0000000000 --- a/docs/vi/docs/deployment/index.md +++ /dev/null @@ -1,21 +0,0 @@ -# Triển khai - -Triển khai một ứng dụng **FastAPI** khá dễ dàng. - -## Triển khai là gì - -Triển khai một ứng dụng có nghĩa là thực hiện các bước cần thiết để làm cho nó **sẵn sàng phục vụ người dùng**. - -Đối với một **API web**, điều này có nghĩa là đặt nó trong một **máy chủ từ xa**, với một **chương trình máy chủ** cung cấp hiệu suất tốt, ổn định, v.v., để người dùng của bạn có thể truy cập ứng dụng của bạn một cách hiệu quả và không bị gián đoạn hoặc gặp vấn đề. - -Điều này trái ngược với các **giai đoạn phát triển**, trong đó bạn liên tục thay đổi mã, phá vỡ nó và sửa nó, ngừng và khởi động lại máy chủ phát triển, v.v. - -## Các Chiến lược Triển khai - -Có nhiều cách để triển khai ứng dụng của bạn tùy thuộc vào trường hợp sử dụng của bạn và các công cụ mà bạn sử dụng. - -Bạn có thể **triển khai một máy chủ** của riêng bạn bằng cách sử dụng một sự kết hợp các công cụ, hoặc bạn có thể sử dụng một **dịch vụ cloud** để làm một số công việc cho bạn, hoặc các tùy chọn khác. - -Tôi sẽ chỉ ra một số khái niệm chính cần thiết khi triển khai một ứng dụng **FastAPI** (mặc dù hầu hết nó áp dụng cho bất kỳ loại ứng dụng web nào). - -Bạn sẽ thấy nhiều chi tiết cần thiết và một số kỹ thuật để triển khai trong các phần tiếp theo. ✨ diff --git a/docs/vi/docs/deployment/versions.md b/docs/vi/docs/deployment/versions.md deleted file mode 100644 index 04de393e72..0000000000 --- a/docs/vi/docs/deployment/versions.md +++ /dev/null @@ -1,93 +0,0 @@ -# Về các phiên bản của FastAPI - -**FastAPI** đã được sử dụng ở quy mô thực tế (production) trong nhiều ứng dụng và hệ thống. Và phạm vi kiểm thử được giữ ở mức 100%. Nhưng việc phát triển của nó vẫn đang diễn ra nhanh chóng. - -Các tính năng mới được bổ sung thường xuyên, lỗi được sửa định kỳ, và mã nguồn vẫn đang được cải thiện liên tục - -Đó là lí do các phiên bản hiện tại vẫn còn là 0.x.x, điều này phản ánh rằng mỗi phiên bản có thể có các thay đổi gây mất tương thích. Điều này tuân theo các quy ước về Semantic Versioning. - -Bạn có thể tạo ra sản phẩm thực tế với **FastAPI** ngay bây giờ (và bạn có thể đã làm điều này trong một thời gian dài), bạn chỉ cần đảm bảo rằng bạn sử dụng một phiên bản hoạt động đúng với các đoạn mã còn lại của bạn. - -## Cố định phiên bản của `fastapi` - -Điều đầu tiên bạn nên làm là "cố định" phiên bản của **FastAPI** bạn đang sử dụng để phiên bản mới nhất mà bạn biết hoạt động đúng với ứng dụng của bạn. - -Ví dụ, giả sử bạn đang sử dụng phiên bản `0.112.0` trong ứng dụng của bạn. - -Nếu bạn sử dụng một tệp `requirements.txt` bạn có thể chỉ định phiên bản với: - -```txt -fastapi[standard]==0.112.0 -``` - -Như vậy, bạn sẽ sử dụng chính xác phiên bản `0.112.0`. - -Hoặc bạn cũng có thể cố định nó với: - -```txt -fastapi[standard]>=0.112.0,<0.113.0 -``` - -Như vậy, bạn sẽ sử dụng các phiên bản `0.112.0` trở lên, nhưng nhỏ hơn `0.113.0`, ví dụ, một phiên bản `0.112.2` vẫn được chấp nhận. - -Nếu bạn sử dụng bất kỳ công cụ nào để quản lý cài đặt của bạn, như `uv`, Poetry, Pipenv, hoặc bất kỳ công cụ nào khác, chúng đều có một cách để bạn có thể định nghĩa các phiên bản cụ thể cho các gói của bạn. - -## Các phiên bản có sẵn - -Bạn có thể xem các phiên bản có sẵn (ví dụ để kiểm tra phiên bản mới nhất) trong [Release Notes](../release-notes.md){.internal-link target=_blank}. - -## Về các phiên bản - -Theo quy ước về Semantic Versioning, bất kỳ phiên bản nào bên dưới `1.0.0` có thể thêm các thay đổi gây mất tương thích. - -**FastAPI** cũng theo quy ước rằng bất kỳ thay đổi phiên bản "PATCH" nào là cho các lỗi và các thay đổi không gây mất tương thích. - -/// tip - -"PATCH" là số cuối cùng, ví dụ, trong `0.2.3`, phiên bản PATCH là `3`. - -/// - -Vì vậy, bạn có thể cố định đến một phiên bản như: - -```txt -fastapi>=0.45.0,<0.46.0 -``` - -Các thay đổi gây mất tương thích và các tính năng mới được thêm vào trong các phiên bản "MINOR". - -/// tip - -"MINOR" là số ở giữa, ví dụ, trong `0.2.3`, phiên bản MINOR là `2`. - -/// - -## Nâng cấp các phiên bản của FastAPI - -Bạn nên thêm các bài kiểm tra (tests) cho ứng dụng của bạn. - -Với **FastAPI** điều này rất dễ dàng (nhờ vào Starlette), kiểm tra tài liệu: [Testing](../tutorial/testing.md){.internal-link target=_blank} - -Sau khi bạn có các bài kiểm tra, bạn có thể nâng cấp phiên bản **FastAPI** lên một phiên bản mới hơn, và đảm bảo rằng tất cả mã của bạn hoạt động đúng bằng cách chạy các bài kiểm tra của bạn. - -Nếu mọi thứ đang hoạt động, hoặc sau khi bạn thực hiện các thay đổi cần thiết, và tất cả các bài kiểm tra của bạn đều đi qua, thì bạn có thể cố định phiên bản của `fastapi` đến phiên bản mới hơn. - -## Về Starlette - -Bạn không nên cố định phiên bản của `starlette`. - -Các phiên bản khác nhau của **FastAPI** sẽ sử dụng một phiên bản Starlette mới hơn. - -Vì vậy, bạn có thể để **FastAPI** sử dụng phiên bản Starlette phù hợp. - -## Về Pydantic - -Pydantic bao gồm các bài kiểm tra của riêng nó cho **FastAPI**, vì vậy các phiên bản mới hơn của Pydantic (trên `1.0.0`) luôn tương thích với **FastAPI**. - -Bạn có thể cố định Pydantic đến bất kỳ phiên bản nào trên `1.0.0` mà bạn muốn. - -Ví dụ: - -```txt -pydantic>=2.7.0,<3.0.0 -``` diff --git a/docs/vi/docs/environment-variables.md b/docs/vi/docs/environment-variables.md deleted file mode 100644 index dd06f8959a..0000000000 --- a/docs/vi/docs/environment-variables.md +++ /dev/null @@ -1,300 +0,0 @@ -# Biến môi trường (Environment Variables) - -/// tip - -Nếu bạn đã biết về "biến môi trường" và cách sử dụng chúng, bạn có thể bỏ qua phần này. - -/// - -Một biến môi trường (còn được gọi là "**env var**") là một biến mà tồn tại **bên ngoài** đoạn mã Python, ở trong **hệ điều hành**, và có thể được đọc bởi đoạn mã Python của bạn (hoặc bởi các chương trình khác). - -Các biến môi trường có thể được sử dụng để xử lí **các thiết lập** của ứng dụng, như một phần của **các quá trình cài đặt** Python, v.v. - -## Tạo và Sử dụng các Biến Môi Trường - -Bạn có thể **tạo** và sử dụng các biến môi trường trong **shell (terminal)**, mà không cần sử dụng Python: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -// Bạn có thể tạo một biến môi trường MY_NAME với -$ export MY_NAME="Wade Wilson" - -// Sau đó bạn có thể sử dụng nó với các chương trình khác, như -$ echo "Hello $MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -// Tạo một biến môi trường MY_NAME -$ $Env:MY_NAME = "Wade Wilson" - -// Sử dụng nó với các chương trình khác, như là -$ echo "Hello $Env:MY_NAME" - -Hello Wade Wilson -``` - -
- -//// - -## Đọc các Biến Môi Trường trong Python - -Bạn cũng có thể tạo các biến môi trường **bên ngoài** đoạn mã Python, trong terminal (hoặc bằng bất kỳ phương pháp nào khác), và sau đó **đọc chúng trong Python**. - -Ví dụ, bạn có một file `main.py` với: - -```Python hl_lines="3" -import os - -name = os.getenv("MY_NAME", "World") -print(f"Hello {name} from Python") -``` - -/// tip - -Tham số thứ hai cho `os.getenv()` là giá trị mặc định để trả về. - -Nếu không được cung cấp, nó mặc định là `None`, ở đây chúng ta cung cấp `"World"` là giá trị mặc định để sử dụng. - -/// - -Sau đó bạn có thể gọi chương trình Python: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -// Ở đây chúng ta chưa cài đặt biến môi trường -$ python main.py - -// Vì chúng ta chưa cài đặt biến môi trường, chúng ta nhận được giá trị mặc định - -Hello World from Python - -// Nhưng nếu chúng ta tạo một biến môi trường trước đó -$ export MY_NAME="Wade Wilson" - -// Và sau đó gọi chương trình lại -$ python main.py - -// Bây giờ nó có thể đọc biến môi trường - -Hello Wade Wilson from Python -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -// Ở đây chúng ta chưa cài đặt biến môi trường -$ python main.py - -// Vì chúng ta chưa cài đặt biến môi trường, chúng ta nhận được giá trị mặc định - -Hello World from Python - -// Nhưng nếu chúng ta tạo một biến môi trường trước đó -$ $Env:MY_NAME = "Wade Wilson" - -// Và sau đó gọi chương trình lại -$ python main.py - -// Bây giờ nó có thể đọc biến môi trường - -Hello Wade Wilson from Python -``` - -
- -//// - -Vì các biến môi trường có thể được tạo bên ngoài đoạn mã Python, nhưng có thể được đọc bởi đoạn mã Python, và không cần được lưu trữ (commit vào `git`) cùng với các file khác, nên chúng thường được sử dụng để lưu các thiết lập hoặc **cấu hình**. - -Bạn cũng có thể tạo ra một biến môi trường dành riêng cho một **lần gọi chương trình**, chỉ có thể được sử dụng bởi chương trình đó, và chỉ trong thời gian chạy của chương trình. - -Để làm điều này, tạo nó ngay trước chương trình đó, trên cùng một dòng: - -
- -```console -// Tạo một biến môi trường MY_NAME cho lần gọi chương trình này -$ MY_NAME="Wade Wilson" python main.py - -// Bây giờ nó có thể đọc biến môi trường - -Hello Wade Wilson from Python - -// Biến môi trường không còn tồn tại sau đó -$ python main.py - -Hello World from Python -``` - -
- -/// tip - -Bạn có thể đọc thêm về điều này tại The Twelve-Factor App: Config. - -/// - -## Các Kiểu (Types) và Kiểm tra (Validation) - -Các biến môi trường có thể chỉ xử lí **chuỗi ký tự**, vì chúng nằm bên ngoài đoạn mã Python và phải tương thích với các chương trình khác và phần còn lại của hệ thống (và thậm chí với các hệ điều hành khác, như Linux, Windows, macOS). - -Điều này có nghĩa là **bất kỳ giá trị nào** được đọc trong Python từ một biến môi trường **sẽ là một `str`**, và bất kỳ hành động chuyển đổi sang kiểu dữ liệu khác hoặc hành động kiểm tra nào cũng phải được thực hiện trong đoạn mã. - -Bạn sẽ học thêm về việc sử dụng biến môi trường để xử lí **các thiết lập ứng dụng** trong [Hướng dẫn nâng cao - Các thiết lập và biến môi trường](./advanced/settings.md){.internal-link target=_blank}. - -## Biến môi trường `PATH` - -Có một biến môi trường **đặc biệt** được gọi là **`PATH`** được sử dụng bởi các hệ điều hành (Linux, macOS, Windows) nhằm tìm các chương trình để thực thi. - -Giá trị của biến môi trường `PATH` là một chuỗi dài được tạo bởi các thư mục được phân tách bởi dấu hai chấm `:` trên Linux và macOS, và bởi dấu chấm phẩy `;` trên Windows. - -Ví dụ, biến môi trường `PATH` có thể có dạng như sau: - -//// tab | Linux, macOS - -```plaintext -/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -``` - -Điều này có nghĩa là hệ thống sẽ tìm kiếm các chương trình trong các thư mục: - -* `/usr/local/bin` -* `/usr/bin` -* `/bin` -* `/usr/sbin` -* `/sbin` - -//// - -//// tab | Windows - -```plaintext -C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 -``` - -Điều này có nghĩa là hệ thống sẽ tìm kiếm các chương trình trong các thư mục: - -* `C:\Program Files\Python312\Scripts` -* `C:\Program Files\Python312` -* `C:\Windows\System32` - -//// - -Khi bạn gõ một **lệnh** trong terminal, hệ điều hành **tìm kiếm** chương trình trong **mỗi thư mục** được liệt kê trong biến môi trường `PATH`. - -Ví dụ, khi bạn gõ `python` trong terminal, hệ điều hành tìm kiếm một chương trình được gọi `python` trong **thư mục đầu tiên** trong danh sách đó. - -Nếu tìm thấy, nó sẽ **sử dụng** nó. Nếu không tìm thấy, nó sẽ tiếp tục tìm kiếm trong **các thư mục khác**. - -### Cài đặt Python và cập nhật biến môi trường `PATH` - -Khi bạn cài đặt Python, bạn có thể được hỏi nếu bạn muốn cập nhật biến môi trường `PATH`. - -//// tab | Linux, macOS - -Giả sử bạn cài đặt Python vào thư mục `/opt/custompython/bin`. - -Nếu bạn chọn cập nhật biến môi trường `PATH`, thì cài đặt sẽ thêm `/opt/custompython/bin` vào biến môi trường `PATH`. - -Nó có thể có dạng như sau: - -```plaintext -/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin -``` - -Như vậy, khi bạn gõ `python` trong terminal, hệ thống sẽ tìm thấy chương trình Python trong `/opt/custompython/bin` (thư mục cuối) và sử dụng nó. - -//// - -//// tab | Windows - -Giả sử bạn cài đặt Python vào thư mục `C:\opt\custompython\bin`. - -Nếu bạn chọn cập nhật biến môi trường `PATH`, thì cài đặt sẽ thêm `C:\opt\custompython\bin` vào biến môi trường `PATH`. - -Nó có thể có dạng như sau: - -```plaintext -C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin -``` - -Như vậy, khi bạn gõ `python` trong terminal, hệ thống sẽ tìm thấy chương trình Python trong `C:\opt\custompython\bin` (thư mục cuối) và sử dụng nó. - -//// - -Vậy, nếu bạn gõ: - -
- -```console -$ python -``` - -
- -//// tab | Linux, macOS - -Hệ thống sẽ **tìm kiếm** chương trình `python` trong `/opt/custompython/bin` và thực thi nó. - -Nó tương đương với việc bạn gõ: - -
- -```console -$ /opt/custompython/bin/python -``` - -
- -//// - -//// tab | Windows - -Hệ thống sẽ **tìm kiếm** chương trình `python` trong `C:\opt\custompython\bin\python` và thực thi nó. - -Nó tương đương với việc bạn gõ: - -
- -```console -$ C:\opt\custompython\bin\python -``` - -
- -//// - -Thông tin này sẽ hữu ích khi bạn học về [Môi trường ảo](virtual-environments.md){.internal-link target=_blank}. - -## Kết luận - -Với những thông tin này, bạn có thể hiểu được **các biến môi trường là gì** và **cách sử dụng chúng trong Python**. - -Bạn có thể đọc thêm về chúng tại Wikipedia cho Biến môi trường. - -Trong nhiều trường hợp, cách các biến môi trường trở nên hữu ích và có thể áp dụng không thực sự rõ ràng ngay từ đầu, nhưng chúng sẽ liên tục xuất hiện trong rất nhiều tình huống khi bạn phát triển ứng dụng, vì vậy việc hiểu biết về chúng là hữu ích. - -Chẳng hạn, bạn sẽ cần những thông tin này khi bạn học về [Môi trường ảo](virtual-environments.md). diff --git a/docs/vi/docs/fastapi-cli.md b/docs/vi/docs/fastapi-cli.md deleted file mode 100644 index e758f4d3a7..0000000000 --- a/docs/vi/docs/fastapi-cli.md +++ /dev/null @@ -1,75 +0,0 @@ -# FastAPI CLI - -**FastAPI CLI** là một chương trình dòng lệnh có thể được sử dụng để phục vụ ứng dụng FastAPI của bạn, quản lý dự án FastAPI của bạn và nhiều hoạt động khác. - -Khi bạn cài đặt FastAPI (vd với `pip install "fastapi[standard]"`), nó sẽ bao gồm một gói được gọi là `fastapi-cli`, gói này cung cấp lệnh `fastapi` trong terminal. - -Để chạy ứng dụng FastAPI của bạn cho quá trình phát triển (development), bạn có thể sử dụng lệnh `fastapi dev`: - -
- -```console -$ fastapi dev main.py - - FastAPI Starting development server 🚀 - - Searching for package file structure from directories with - __init__.py files - Importing from /home/user/code/awesomeapp - - module 🐍 main.py - - code Importing the FastAPI app object from the module with the - following code: - - from main import app - - app Using import string: main:app - - server Server started at http://127.0.0.1:8000 - server Documentation at http://127.0.0.1:8000/docs - - tip Running in development mode, for production use: - fastapi run - - Logs: - - INFO Will watch for changes in these directories: - ['/home/user/code/awesomeapp'] - INFO Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to - quit) - INFO Started reloader process [383138] using WatchFiles - INFO Started server process [383153] - INFO Waiting for application startup. - INFO Application startup complete. -``` - -
- -Chương trình dòng lệnh `fastapi` là **FastAPI CLI**. - -FastAPI CLI nhận đường dẫn đến chương trình Python của bạn (vd `main.py`) và tự động phát hiện đối tượng `FastAPI` (thường được gọi là `app`), xác định quá trình nhập đúng, và sau đó chạy nó (serve). - -Đối với vận hành thực tế (production), bạn sẽ sử dụng `fastapi run` thay thế. 🚀 - -Ở bên trong, **FastAPI CLI** sử dụng Uvicorn, một server ASGI có hiệu suất cao, sẵn sàng cho vận hành thực tế (production). 😎 - -## `fastapi dev` - -Chạy `fastapi dev` sẽ khởi động quá trình phát triển. - -Mặc định, **auto-reload** được bật, tự động tải lại server khi bạn thay đổi code của bạn. Điều này tốn nhiều tài nguyên và có thể kém ổn định hơn khi nó bị tắt. Bạn nên sử dụng nó cho quá trình phát triển. Nó cũng lắng nghe địa chỉ IP `127.0.0.1`, đó là địa chỉ IP của máy tính để tự giao tiếp với chính nó (`localhost`). - -## `fastapi run` - -Chạy `fastapi run` mặc định sẽ khởi động FastAPI cho quá trình vận hành thực tế. - -Mặc định, **auto-reload** bị tắt. Nó cũng lắng nghe địa chỉ IP `0.0.0.0`, đó là tất cả các địa chỉ IP có sẵn, như vậy nó sẽ được truy cập công khai bởi bất kỳ ai có thể giao tiếp với máy tính. Đây là cách bạn thường chạy nó trong sản phẩm hoàn thiện, ví dụ trong một container. - -Trong hầu hết các trường hợp, bạn sẽ (và nên) có một "proxy điểm cuối (termination proxy)" xử lý HTTPS cho bạn, điều này sẽ phụ thuộc vào cách bạn triển khai ứng dụng của bạn, nhà cung cấp có thể làm điều này cho bạn, hoặc bạn có thể cần thiết lập nó. - -/// tip - -Bạn có thể tìm hiểu thêm về FastAPI CLI trong [tài liệu triển khai](deployment/index.md){.internal-link target=_blank}. - -/// diff --git a/docs/vi/docs/features.md b/docs/vi/docs/features.md deleted file mode 100644 index 2220d9fa5a..0000000000 --- a/docs/vi/docs/features.md +++ /dev/null @@ -1,200 +0,0 @@ -# Tính năng - -## Tính năng của FastAPI - -**FastAPI** cho bạn những tính năng sau: - -### Dựa trên những tiêu chuẩn mở - -* OpenAPI cho việc tạo API, bao gồm những khai báo về đường dẫn các toán tử, tham số, body requests, cơ chế bảo mật, etc. -* Tự động tài liệu hóa data model theo JSON Schema (OpenAPI bản thân nó được dựa trên JSON Schema). -* Được thiết kế xung quanh các tiêu chuẩn này sau khi nghiên cứu tỉ mỉ thay vì chỉ suy nghĩ đơn giản và sơ xài. -* Điều này cho phép tự động hóa **trình sinh code client** cho nhiều ngôn ngữ lập trình khác nhau. - -### Tự động hóa tài liệu - - -Tài liệu tương tác API và web giao diện người dùng. Là một framework được dựa trên OpenAPI do đó có nhiều tùy chọn giao diện cho tài liệu API, 2 giao diện bên dưới là mặc định. - -* Swagger UI, với giao diện khám phá, gọi và kiểm thử API trực tiếp từ trình duyệt. - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Thay thế với tài liệu API với ReDoc. - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Chỉ cần phiên bản Python hiện đại - -Tất cả được dựa trên khai báo kiểu dữ liệu chuẩn của **Python 3.8** (cảm ơn Pydantic). Bạn không cần học cú pháp mới, chỉ cần biết chuẩn Python hiện đại. - -Nếu bạn cần 2 phút để làm mới lại cách sử dụng các kiểu dữ liệu mới của Python (thậm chí nếu bạn không sử dụng FastAPI), xem hướng dẫn ngắn: [Kiểu dữ liệu Python](python-types.md){.internal-link target=_blank}. - -Bạn viết chuẩn Python với kiểu dữ liệu như sau: - -```Python -from datetime import date - -from pydantic import BaseModel - -# Declare a variable as a str -# and get editor support inside the function -def main(user_id: str): - return user_id - - -# A Pydantic model -class User(BaseModel): - id: int - name: str - joined: date -``` - -Sau đó có thể được sử dụng: - -```Python -my_user: User = User(id=3, name="John Doe", joined="2018-07-19") - -second_user_data = { - "id": 4, - "name": "Mary", - "joined": "2018-11-30", -} - -my_second_user: User = User(**second_user_data) -``` - -/// info - -`**second_user_data` nghĩa là: - -Truyền các khóa và giá trị của dict `second_user_data` trực tiếp như các tham số kiểu key-value, tương đương với: `User(id=4, name="Mary", joined="2018-11-30")` - -/// - -### Được hỗ trợ từ các trình soạn thảo - - -Toàn bộ framework được thiết kế để sử dụng dễ dàng và trực quan, toàn bộ quyết định đã được kiểm thử trên nhiều trình soạn thảo thậm chí trước khi bắt đầu quá trình phát triển, để chắc chắn trải nghiệm phát triển là tốt nhất. - -Trong lần khảo sát cuối cùng dành cho các lập trình viên Python, đã rõ ràng rằng đa số các lập trình viên sử dụng tính năng "autocompletion". - -Toàn bộ framework "FastAPI" phải đảm bảo rằng: autocompletion hoạt động ở mọi nơi. Bạn sẽ hiếm khi cần quay lại để đọc tài liệu. - -Đây là các trình soạn thảo có thể giúp bạn: - -* trong Visual Studio Code: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -* trong PyCharm: - -![editor support](https://fastapi.tiangolo.com/img/pycharm-completion.png) - -Bạn sẽ có được auto-completion trong code, thậm chí trước đó là không thể. Như trong ví dụ, khóa `price` bên trong một JSON (đó có thể được lồng nhau) đến từ một request. - -Không còn nhập sai tên khóa, quay đi quay lại giữa các tài liệu hoặc cuộn lên cuộn xuống để tìm xem cuối cùng bạn đã sử dụng `username` hay `user_name`. - -### Ngắn gọn - -FastAPI có các giá trị mặc định hợp lý cho mọi thứ, với các cấu hình tùy chọn ở mọi nơi. Tất cả các tham số có thể được tinh chỉnh để thực hiện những gì bạn cần và để định nghĩa API bạn cần. - -Nhưng mặc định, tất cả **đều hoạt động**. - -### Validation - -* Validation cho đa số (hoặc tất cả?) **các kiểu dữ liệu** Python, bao gồm: - * JSON objects (`dict`). - * Mảng JSON (`list`) định nghĩa kiểu dữ liệu từng phần tử. - * Xâu (`str`), định nghĩa độ dài lớn nhất, nhỏ nhất. - * Số (`int`, `float`) với các giá trị lớn nhất, nhỏ nhất, etc. - -* Validation cho nhiều kiểu dữ liệu bên ngoài như: - * URL. - * Email. - * UUID. - * ...và nhiều cái khác. - -Tất cả validation được xử lí bằng những thiết lập tốt và mạnh mẽ của **Pydantic**. - -### Bảo mật và xác thực - -Bảo mật và xác thực đã tích hợp mà không làm tổn hại tới cơ sở dữ liệu hoặc data models. - -Tất cả cơ chế bảo mật định nghĩa trong OpenAPI, bao gồm: - -* HTTP Basic. -* **OAuth2** (với **JWT tokens**). Xem hướng dẫn [OAuth2 with JWT](tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. -* API keys in: - * Headers. - * Các tham số trong query string. - * Cookies, etc. - -Cộng với tất cả các tính năng bảo mật từ Starlette (bao gồm **session cookies**). - -Tất cả được xây dựng dưới dạng các công cụ và thành phần có thể tái sử dụng, dễ dàng tích hợp với hệ thống, kho lưu trữ dữ liệu, cơ sở dữ liệu quan hệ và NoSQL của bạn,... - -### Dependency Injection - -FastAPI bao gồm một hệ thống Dependency Injection vô cùng dễ sử dụng nhưng vô cùng mạnh mẽ. - -* Thậm chí, các dependency có thể có các dependency khác, tạo thành một phân cấp hoặc **"một đồ thị" của các dependency**. -* Tất cả **được xử lí tự động** bởi framework. -* Tất cả các dependency có thể yêu cầu dữ liệu từ request và **tăng cường các ràng buộc từ đường dẫn** và tự động tài liệu hóa. -* **Tự động hóa validation**, thậm chí với các tham số *đường dẫn* định nghĩa trong các dependency. -* Hỗ trợ hệ thống xác thực người dùng phức tạp, **các kết nối cơ sở dữ liệu**,... -* **Không làm tổn hại** cơ sở dữ liệu, frontends,... Nhưng dễ dàng tích hợp với tất cả chúng. - -### Không giới hạn "plug-ins" - -Hoặc theo một cách nào khác, không cần chúng, import và sử dụng code bạn cần. - -Bất kì tích hợp nào được thiết kế để sử dụng đơn giản (với các dependency), đến nỗi bạn có thể tạo một "plug-in" cho ứng dụng của mình trong 2 dòng code bằng cách sử dụng cùng một cấu trúc và cú pháp được sử dụng cho *path operations* của bạn. - -### Đã được kiểm thử - -* 100% test coverage. -* 100% type annotated code base. -* Được sử dụng cho các ứng dụng sản phẩm. - -## Tính năng của Starlette - -`FastAPI` is thực sự là một sub-class của `Starlette`. Do đó, nếu bạn đã biết hoặc đã sử dụng Starlette, đa số các chức năng sẽ làm việc giống như vậy. - -Với **FastAPI**, bạn có được tất cả những tính năng của **Starlette**: - -* Hiệu năng thực sự ấn tượng. Nó là một trong nhưng framework Python nhanh nhất, khi so sánh với **NodeJS** và **Go**. -* Hỗ trợ **WebSocket**. -* In-process background tasks. -* Startup and shutdown events. -* Client cho kiểm thử xây dựng trên HTTPX. -* **CORS**, GZip, Static Files, Streaming responses. -* Hỗ trợ **Session and Cookie**. -* 100% test coverage. -* 100% type annotated codebase. - -## Tính năng của Pydantic - -**FastAPI** tương thích đầy đủ với (và dựa trên) Pydantic. Do đó, bất kì code Pydantic nào bạn thêm vào cũng sẽ hoạt động. - -Bao gồm các thư viện bên ngoài cũng dựa trên Pydantic, như ORMs, ODMs cho cơ sở dữ liệu. - -Nó cũng có nghĩa là trong nhiều trường hợp, bạn có thể truyền cùng object bạn có từ một request **trực tiếp cho cơ sở dữ liệu**, vì mọi thứ được validate tự động. - -Điều tương tự áp dụng cho các cách khác nhau, trong nhiều trường hợp, bạn có thể chỉ truyền object từ cơ sở dữ liêu **trực tiếp tới client**. - -Với **FastAPI**, bạn có tất cả những tính năng của **Pydantic** (FastAPI dựa trên Pydantic cho tất cả những xử lí về dữ liệu): - -* **Không gây rối não**: - * Không cần học ngôn ngữ mô tả cấu trúc mới. - * Nếu bạn biết kiểu dữ liệu Python, bạn biết cách sử dụng Pydantic. -* Sử dụng tốt với **IDE/linter/não của bạn**: - - * Bởi vì các cấu trúc dữ liệu của Pydantic chỉ là các instances của class bạn định nghĩa; auto-completion, linting, mypy và trực giác của bạn nên làm việc riêng biệt với những dữ liệu mà bạn đã validate. -* Validate **các cấu trúc phức tạp**: - * Sử dụng các models Pydantic phân tầng, `List` và `Dict` của Python `typing`,... - * Và các validators cho phép các cấu trúc dữ liệu phức tạp trở nên rõ ràng và dễ dàng để định nghĩa, kiểm tra và tài liệu hóa thành JSON Schema. - * Bạn có thể có các object **JSON lồng nhau** và tất cả chúng đã validate và annotated. -* **Có khả năng mở rộng**: - * Pydantic cho phép bạn tùy chỉnh kiểu dữ liệu bằng việc định nghĩa hoặc bạn có thể mở rộng validation với các decorator trong model. -* 100% test coverage. diff --git a/docs/vi/docs/index.md b/docs/vi/docs/index.md deleted file mode 100644 index a5ac1bfb76..0000000000 --- a/docs/vi/docs/index.md +++ /dev/null @@ -1,475 +0,0 @@ -# FastAPI - - - -

- FastAPI -

-

- FastAPI framework, hiệu năng cao, dễ học, dễ code, sẵn sàng để tạo ra sản phẩm -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Tài liệu**: https://fastapi.tiangolo.com - -**Mã nguồn**: https://github.com/fastapi/fastapi - ---- - -FastAPI là một web framework hiện đại, hiệu năng cao để xây dựng web APIs với Python dựa trên tiêu chuẩn Python type hints. - -Những tính năng như: - -* **Nhanh**: Hiệu năng rất cao khi so sánh với **NodeJS** và **Go** (cảm ơn Starlette và Pydantic). [Một trong những Python framework nhanh nhất](#hieu-nang). -* **Code nhanh**: Tăng tốc độ phát triển tính năng từ 200% tới 300%. * -* **Ít lỗi hơn**: Giảm khoảng 40% những lỗi phát sinh bởi con người (nhà phát triển). * -* **Trực giác tốt hơn**: Được các trình soạn thảo hỗ tuyệt vời. Completion mọi nơi. Ít thời gian gỡ lỗi. -* **Dễ dàng**: Được thiết kế để dễ dàng học và sử dụng. Ít thời gian đọc tài liệu. -* **Ngắn**: Tối thiểu code bị trùng lặp. Nhiều tính năng được tích hợp khi định nghĩa tham số. Ít lỗi hơn. -* **Tăng tốc**: Có được sản phẩm cùng với tài liệu (được tự động tạo) có thể tương tác. -* **Được dựa trên các tiêu chuẩn**: Dựa trên (và hoàn toàn tương thích với) các tiêu chuẩn mở cho APIs : OpenAPI (trước đó được biết đến là Swagger) và JSON Schema. - -* ước tính được dựa trên những kiểm chứng trong nhóm phát triển nội bộ, xây dựng các ứng dụng sản phẩm. - -## Nhà tài trợ - - - -{% if sponsors %} -{% for sponsor in sponsors.gold -%} - -{% endfor -%} -{%- for sponsor in sponsors.silver -%} - -{% endfor %} -{% endif %} - - - -Những nhà tài trợ khác - -## Ý kiến đánh giá - -"_[...] Tôi đang sử dụng **FastAPI** vô cùng nhiều vào những ngày này. [...] Tôi thực sự đang lên kế hoạch sử dụng nó cho tất cả các nhóm **dịch vụ ML tại Microsoft**. Một vài trong số đó đang tích hợp vào sản phẩm lõi của **Window** và một vài sản phẩm cho **Office**._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_Chúng tôi tích hợp thư viện **FastAPI** để sinh ra một **REST** server, nó có thể được truy vấn để thu được những **dự đoán**._ [bởi Ludwid] " - -
Piero Molino, Yaroslav Dudin, và Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** vui mừng thông báo việc phát hành framework mã nguồn mở của chúng tôi cho *quản lí khủng hoảng* tập trung: **Dispatch**! [xây dựng với **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_Tôi vô cùng hào hứng về **FastAPI**. Nó rất thú vị_" - -
Brian Okken - Python Bytes podcast host (ref)
- ---- - -"_Thành thật, những gì bạn đã xây dựng nhìn siêu chắc chắn và bóng bẩy. Theo nhiều cách, nó là những gì tôi đã muốn Hug trở thành - thật sự truyền cảm hứng để thấy ai đó xây dựng nó._" - -
Timothy Crosley - người tạo ra Hug (ref)
- ---- - -"_Nếu bạn đang tìm kiếm một **framework hiện đại** để xây dựng một REST APIs, thử xem xét **FastAPI** [...] Nó nhanh, dễ dùng và dễ học [...]_" - -"_Chúng tôi đã chuyển qua **FastAPI cho **APIs** của chúng tôi [...] Tôi nghĩ bạn sẽ thích nó [...]_" - -
Ines Montani - Matthew Honnibal - Explosion AI founders - spaCy creators (ref) - (ref)
-
Ines Montani - Matthew Honnibal - nhà sáng lập Explosion AI - người tạo ra spaCy (ref) - (ref)
- ---- - -"_Nếu ai đó đang tìm cách xây dựng sản phẩm API bằng Python, tôi sẽ đề xuất **FastAPI**. Nó **được thiết kế đẹp đẽ**, **sử dụng đơn giản** và **có khả năng mở rộng cao**, nó đã trở thành một **thành phần quan trọng** trong chiến lược phát triển API của chúng tôi và đang thúc đẩy nhiều dịch vụ và mảng tự động hóa như Kỹ sư TAC ảo của chúng tôi._" - -
Deon Pillsbury - Cisco (ref)
- ---- - -## **Typer**, giao diện dòng lệnh của FastAPI - - - -Nếu bạn đang xây dựng một CLI - ứng dụng được sử dụng trong giao diện dòng lệnh, xem về **Typer**. - -**Typer** là một người anh em của FastAPI. Và nó được dự định trở thành **giao diện dòng lệnh cho FastAPI**. ⌨️ 🚀 - -## Yêu cầu - -FastAPI đứng trên vai những người khổng lồ: - -* Starlette cho phần web. -* Pydantic cho phần data. - -## Cài đặt - -
- -```console -$ pip install fastapi - ----> 100% -``` - -
- -Bạn cũng sẽ cần một ASGI server cho production như Uvicorn hoặc Hypercorn. - -
- -```console -$ pip install "uvicorn[standard]" - ----> 100% -``` - -
- -## Ví dụ - -### Khởi tạo - -* Tạo một tệp tin `main.py` như sau: - -```Python -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} -``` - -
-Hoặc sử dụng async def... - -Nếu code của bạn sử dụng `async` / `await`, hãy sử dụng `async def`: - -```Python hl_lines="9 14" -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} -``` - -**Lưu ý**: - -Nếu bạn không biết, xem phần _"In a hurry?"_ về `async` và `await` trong tài liệu này. - -
- -### Chạy ứng dụng - -Chạy server như sau: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-Về lệnh uvicorn main:app --reload... - -Lệnh `uvicorn main:app` tham chiếu tới những thành phần sau: - -* `main`: tệp tin `main.py` (một Python "module"). -* `app`: object được tạo trong tệp tin `main.py` tại dòng `app = FastAPI()`. -* `--reload`: chạy lại server sau khi code thay đổi. Chỉ sử dụng trong quá trình phát triển. - -
- -### Kiểm tra - -Mở trình duyệt của bạn tại http://127.0.0.1:8000/items/5?q=somequery. - -Bạn sẽ thấy một JSON response: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -Bạn đã sẵn sàng để tạo một API như sau: - -* Nhận HTTP request với _đường dẫn_ `/` và `/items/{item_id}`. -* Cả hai _đường dẫn_ sử dụng toán tử `GET` (cũng đươc biết đến là _phương thức_ HTTP). -* _Đường dẫn_ `/items/{item_id}` có một _tham số đường dẫn_ `item_id`, nó là một tham số kiểu `int`. -* _Đường dẫn_ `/items/{item_id}` có một _tham số query string_ `q`, nó là một tham số tùy chọn kiểu `str`. - -### Tài liệu tương tác API - -Truy cập http://127.0.0.1:8000/docs. - -Bạn sẽ thấy tài liệu tương tác API được tạo tự động (cung cấp bởi Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Tài liệu API thay thế - -Và bây giờ, hãy truy cập tới http://127.0.0.1:8000/redoc. - -Bạn sẽ thấy tài liệu được thay thế (cung cấp bởi ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Nâng cấp ví dụ - -Bây giờ sửa tệp tin `main.py` để nhận body từ một request `PUT`. - -Định nghĩa của body sử dụng kiểu dữ liệu chuẩn của Python, cảm ơn Pydantic. - -```Python hl_lines="4 9-12 25-27" -from typing import Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: Union[bool, None] = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -Server nên tự động chạy lại (bởi vì bạn đã thêm `--reload` trong lệnh `uvicorn` ở trên). - -### Nâng cấp tài liệu API - -Bây giờ truy cập tới http://127.0.0.1:8000/docs. - -* Tài liệu API sẽ được tự động cập nhật, bao gồm body mới: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click vào nút "Try it out", nó cho phép bạn điền những tham số và tương tác trực tiếp với API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Sau khi click vào nút "Execute", giao diện người dùng sẽ giao tiếp với API của bạn bao gồm: gửi các tham số, lấy kết quả và hiển thị chúng trên màn hình: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Nâng cấp tài liệu API thay thế - -Và bây giờ truy cập tới http://127.0.0.1:8000/redoc. - -* Tài liệu thay thế cũng sẽ phản ánh tham số và body mới: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Tóm lại - -Bạn khai báo **một lần** kiểu dữ liệu của các tham số, body, etc là các tham số của hàm số. - -Bạn định nghĩa bằng cách sử dụng các kiểu dữ liệu chuẩn của Python. - -Bạn không phải học một cú pháp mới, các phương thức và class của một thư viện cụ thể nào. - -Chỉ cần sử dụng các chuẩn của **Python**. - -Ví dụ, với một tham số kiểu `int`: - -```Python -item_id: int -``` - -hoặc với một model `Item` phức tạp hơn: - -```Python -item: Item -``` - -...và với định nghĩa đơn giản đó, bạn có được: - -* Sự hỗ trợ từ các trình soạn thảo, bao gồm: - * Completion. - * Kiểm tra kiểu dữ liệu. -* Kiểm tra kiểu dữ liệu: - * Tự động sinh lỗi rõ ràng khi dữ liệu không hợp lệ . - * Kiểm tra JSON lồng nhau . -* Chuyển đổi dữ liệu đầu vào: tới từ network sang dữ liệu kiểu Python. Đọc từ: - * JSON. - * Các tham số trong đường dẫn. - * Các tham số trong query string. - * Cookies. - * Headers. - * Forms. - * Files. -* Chuyển đổi dữ liệu đầu ra: chuyển đổi từ kiểu dữ liệu Python sang dữ liệu network (như JSON): - * Chuyển đổi kiểu dữ liệu Python (`str`, `int`, `float`, `bool`, `list`,...). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...và nhiều hơn thế. -* Tự động tạo tài liệu tương tác API, bao gồm 2 giao diện người dùng: - * Swagger UI. - * ReDoc. - ---- - -Quay trở lại ví dụ trước, **FastAPI** sẽ thực hiện: - -* Kiểm tra xem có một `item_id` trong đường dẫn với các request `GET` và `PUT` không? -* Kiểm tra xem `item_id` có phải là kiểu `int` trong các request `GET` và `PUT` không? - * Nếu không, client sẽ thấy một lỗi rõ ràng và hữu ích. -* Kiểm tra xem nếu có một tham số `q` trong query string (ví dụ như `http://127.0.0.1:8000/items/foo?q=somequery`) cho request `GET`. - * Tham số `q` được định nghĩa `= None`, nó là tùy chọn. - * Nếu không phải `None`, nó là bắt buộc (như body trong trường hợp của `PUT`). -* Với request `PUT` tới `/items/{item_id}`, đọc body như JSON: - * Kiểm tra xem nó có một thuộc tính bắt buộc kiểu `str` là `name` không? - * Kiểm tra xem nó có một thuộc tính bắt buộc kiểu `float` là `price` không? - * Kiểm tra xem nó có một thuộc tính tùy chọn là `is_offer` không? Nếu có, nó phải có kiểu `bool`. - * Tất cả những kiểm tra này cũng được áp dụng với các JSON lồng nhau. -* Chuyển đổi tự động các JSON object đến và JSON object đi. -* Tài liệu hóa mọi thứ với OpenAPI, tài liệu đó có thể được sử dụng bởi: - - * Các hệ thống tài liệu có thể tương tác. - * Hệ thống sinh code tự động, cho nhiều ngôn ngữ lập trình. -* Cung cấp trực tiếp 2 giao diện web cho tài liệu tương tác - ---- - -Chúng tôi chỉ trình bày những thứ cơ bản bên ngoài, nhưng bạn đã hiểu cách thức hoạt động của nó. - -Thử thay đổi dòng này: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...từ: - -```Python - ... "item_name": item.name ... -``` - -...sang: - -```Python - ... "item_price": item.price ... -``` - -...và thấy trình soạn thảo của bạn nhận biết kiểu dữ liệu và gợi ý hoàn thiện các thuộc tính. - -![trình soạn thảo hỗ trợ](https://fastapi.tiangolo.com/img/vscode-completion.png) - -Ví dụ hoàn chỉnh bao gồm nhiều tính năng hơn, xem Tutorial - User Guide. - - -**Cảnh báo tiết lỗ**: Tutorial - User Guide: - -* Định nghĩa **tham số** từ các nguồn khác nhau như: **headers**, **cookies**, **form fields** và **files**. -* Cách thiết lập **các ràng buộc cho validation** như `maximum_length` hoặc `regex`. -* Một hệ thống **Dependency Injection vô cùng mạnh mẽ và dễ dàng sử dụng. -* Bảo mật và xác thực, hỗ trợ **OAuth2**(với **JWT tokens**) và **HTTP Basic**. -* Những kĩ thuật nâng cao hơn (nhưng tương đối dễ) để định nghĩa **JSON models lồng nhau** (cảm ơn Pydantic). -* Tích hợp **GraphQL** với Strawberry và các thư viện khác. -* Nhiều tính năng mở rộng (cảm ơn Starlette) như: - * **WebSockets** - * kiểm thử vô cùng dễ dàng dựa trên HTTPX và `pytest` - * **CORS** - * **Cookie Sessions** - * ...và nhiều hơn thế. - -## Hiệu năng - -Independent TechEmpower benchmarks cho thấy các ứng dụng **FastAPI** chạy dưới Uvicorn là một trong những Python framework nhanh nhất, chỉ đứng sau Starlette và Uvicorn (được sử dụng bên trong FastAPI). (*) - -Để hiểu rõ hơn, xem phần Benchmarks. - -## Các dependency tùy chọn - -Sử dụng bởi Pydantic: - -* email-validator - cho email validation. - -Sử dụng Starlette: - -* httpx - Bắt buộc nếu bạn muốn sử dụng `TestClient`. -* jinja2 - Bắt buộc nếu bạn muốn sử dụng cấu hình template engine mặc định. -* python-multipart - Bắt buộc nếu bạn muốn hỗ trợ "parsing", form với `request.form()`. -* itsdangerous - Bắt buộc để hỗ trợ `SessionMiddleware`. -* pyyaml - Bắt buộc để hỗ trợ `SchemaGenerator` cho Starlette (bạn có thể không cần nó trong FastAPI). - -Sử dụng bởi FastAPI / Starlette: - -* uvicorn - Server để chạy ứng dụng của bạn. -* orjson - Bắt buộc nếu bạn muốn sử dụng `ORJSONResponse`. -* ujson - Bắt buộc nếu bạn muốn sử dụng `UJSONResponse`. - -Bạn có thể cài đặt tất cả những dependency trên với `pip install "fastapi[all]"`. - -## Giấy phép - -Dự án này được cấp phép dưới những điều lệ của giấy phép MIT. diff --git a/docs/vi/docs/python-types.md b/docs/vi/docs/python-types.md deleted file mode 100644 index 403e899300..0000000000 --- a/docs/vi/docs/python-types.md +++ /dev/null @@ -1,593 +0,0 @@ -# Giới thiệu kiểu dữ liệu Python - -Python hỗ trợ tùy chọn "type hints" (còn được gọi là "type annotations"). - -Những **"type hints"** hay chú thích là một cú pháp đặc biệt cho phép khai báo kiểu dữ liệu của một biến. - -Bằng việc khai báo kiểu dữ liệu cho các biến của bạn, các trình soạn thảo và các công cụ có thể hỗ trợ bạn tốt hơn. - -Đây chỉ là một **hướng dẫn nhanh** về gợi ý kiểu dữ liệu trong Python. Nó chỉ bao gồm những điều cần thiết tối thiểu để sử dụng chúng với **FastAPI**... đó thực sự là rất ít. - -**FastAPI** hoàn toàn được dựa trên những gợi ý kiểu dữ liệu, chúng mang đến nhiều ưu điểm và lợi ích. - -Nhưng thậm chí nếu bạn không bao giờ sử dụng **FastAPI**, bạn sẽ được lợi từ việc học một ít về chúng. - -/// note - -Nếu bạn là một chuyên gia về Python, và bạn đã biết mọi thứ về gợi ý kiểu dữ liệu, bỏ qua và đi tới chương tiếp theo. - -/// - -## Động lực - -Hãy bắt đầu với một ví dụ đơn giản: - -{* ../../docs_src/python_types/tutorial001.py *} - - -Kết quả khi gọi chương trình này: - -``` -John Doe -``` - -Hàm thực hiện như sau: - -* Lấy một `first_name` và `last_name`. -* Chuyển đổi kí tự đầu tiên của mỗi biến sang kiểu chữ hoa với `title()`. -* Nối chúng lại với nhau bằng một kí tự trắng ở giữa. - -{* ../../docs_src/python_types/tutorial001.py hl[2] *} - - -### Sửa đổi - -Nó là một chương trình rất đơn giản. - -Nhưng bây giờ hình dung rằng bạn đang viết nó từ đầu. - -Tại một vài thời điểm, bạn sẽ bắt đầu định nghĩa hàm, bạn có các tham số... - -Nhưng sau đó bạn phải gọi "phương thức chuyển đổi kí tự đầu tiên sang kiểu chữ hoa". - -Có phải là `upper`? Có phải là `uppercase`? `first_uppercase`? `capitalize`? - -Sau đó, bạn thử hỏi người bạn cũ của mình, autocompletion của trình soạn thảo. - -Bạn gõ tham số đầu tiên của hàm, `first_name`, sau đó một dấu chấm (`.`) và sau đó ấn `Ctrl+Space` để kích hoạt bộ hoàn thành. - -Nhưng đáng buồn, bạn không nhận được điều gì hữu ích cả: - - - -### Thêm kiểu dữ liệu - -Hãy sửa một dòng từ phiên bản trước. - -Chúng ta sẽ thay đổi chính xác đoạn này, tham số của hàm, từ: - -```Python - first_name, last_name -``` - -sang: - -```Python - first_name: str, last_name: str -``` - -Chính là nó. - -Những thứ đó là "type hints": - -{* ../../docs_src/python_types/tutorial002.py hl[1] *} - - -Đó không giống như khai báo những giá trị mặc định giống như: - -```Python - first_name="john", last_name="doe" -``` - -Nó là một thứ khác. - -Chúng ta sử dụng dấu hai chấm (`:`), không phải dấu bằng (`=`). - -Và việc thêm gợi ý kiểu dữ liệu không làm thay đổi những gì xảy ra so với khi chưa thêm chúng. - -But now, imagine you are again in the middle of creating that function, but with type hints. - -Tại cùng một điểm, bạn thử kích hoạt autocomplete với `Ctrl+Space` và bạn thấy: - - - -Với cái đó, bạn có thể cuộn, nhìn thấy các lựa chọn, cho đến khi bạn tìm thấy một "tiếng chuông": - - - -## Động lực nhiều hơn - -Kiểm tra hàm này, nó đã có gợi ý kiểu dữ liệu: - -{* ../../docs_src/python_types/tutorial003.py hl[1] *} - - -Bởi vì trình soạn thảo biết kiểu dữ liệu của các biến, bạn không chỉ có được completion, bạn cũng được kiểm tra lỗi: - - - -Bây giờ bạn biết rằng bạn phải sửa nó, chuyển `age` sang một xâu với `str(age)`: - -{* ../../docs_src/python_types/tutorial004.py hl[2] *} - - -## Khai báo các kiểu dữ liệu - -Bạn mới chỉ nhìn thấy những nơi chủ yếu để đặt khai báo kiểu dữ liệu. Như là các tham số của hàm. - -Đây cũng là nơi chủ yếu để bạn sử dụng chúng với **FastAPI**. - -### Kiểu dữ liệu đơn giản - -Bạn có thể khai báo tất cả các kiểu dữ liệu chuẩn của Python, không chỉ là `str`. - -Bạn có thể sử dụng, ví dụ: - -* `int` -* `float` -* `bool` -* `bytes` - -{* ../../docs_src/python_types/tutorial005.py hl[1] *} - - -### Các kiểu dữ liệu tổng quát với tham số kiểu dữ liệu - -Có một vài cấu trúc dữ liệu có thể chứa các giá trị khác nhau như `dict`, `list`, `set` và `tuple`. Và những giá trị nội tại cũng có thể có kiểu dữ liệu của chúng. - -Những kiểu dữ liệu nội bộ này được gọi là những kiểu dữ liệu "**tổng quát**". Và có khả năng khai báo chúng, thậm chí với các kiểu dữ liệu nội bộ của chúng. - -Để khai báo những kiểu dữ liệu và những kiểu dữ liệu nội bộ đó, bạn có thể sử dụng mô đun chuẩn của Python là `typing`. Nó có hỗ trợ những gợi ý kiểu dữ liệu này. - -#### Những phiên bản mới hơn của Python - -Cú pháp sử dụng `typing` **tương thích** với tất cả các phiên bản, từ Python 3.6 tới những phiên bản cuối cùng, bao gồm Python 3.9, Python 3.10,... - -As Python advances, **những phiên bản mới** mang tới sự hỗ trợ được cải tiến cho những chú thích kiểu dữ liệu và trong nhiều trường hợp bạn thậm chí sẽ không cần import và sử dụng mô đun `typing` để khai báo chú thích kiểu dữ liệu. - -Nếu bạn có thể chọn một phiên bản Python gần đây hơn cho dự án của bạn, ban sẽ có được những ưu điểm của những cải tiến đơn giản đó. - -Trong tất cả các tài liệu tồn tại những ví dụ tương thích với mỗi phiên bản Python (khi có một sự khác nhau). - -Cho ví dụ "**Python 3.6+**" có nghĩa là nó tương thích với Python 3.7 hoặc lớn hơn (bao gồm 3.7, 3.8, 3.9, 3.10,...). và "**Python 3.9+**" nghĩa là nó tương thích với Python 3.9 trở lên (bao gồm 3.10,...). - -Nếu bạn có thể sử dụng **phiên bản cuối cùng của Python**, sử dụng những ví dụ cho phiên bản cuối, những cái đó sẽ có **cú pháp đơn giản và tốt nhât**, ví dụ, "**Python 3.10+**". - -#### List - -Ví dụ, hãy định nghĩa một biến là `list` các `str`. - -//// tab | Python 3.9+ - -Khai báo biến với cùng dấu hai chấm (`:`). - -Tương tự kiểu dữ liệu `list`. - -Như danh sách là một kiểu dữ liệu chứa một vài kiểu dữ liệu có sẵn, bạn đặt chúng trong các dấu ngoặc vuông: - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -Từ `typing`, import `List` (với chữ cái `L` viết hoa): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -Khai báo biến với cùng dấu hai chấm (`:`). - -Tương tự như kiểu dữ liệu, `List` bạn import từ `typing`. - -Như danh sách là một kiểu dữ liệu chứa các kiểu dữ liệu có sẵn, bạn đặt chúng bên trong dấu ngoặc vuông: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// - -/// info - -Các kiểu dữ liệu có sẵn bên trong dấu ngoặc vuông được gọi là "tham số kiểu dữ liệu". - -Trong trường hợp này, `str` là tham số kiểu dữ liệu được truyền tới `List` (hoặc `list` trong Python 3.9 trở lên). - -/// - -Có nghĩa là: "biến `items` là một `list`, và mỗi phần tử trong danh sách này là một `str`". - -/// tip - -Nếu bạn sử dụng Python 3.9 hoặc lớn hơn, bạn không phải import `List` từ `typing`, bạn có thể sử dụng `list` để thay thế. - -/// - -Bằng cách này, trình soạn thảo của bạn có thể hỗ trợ trong khi xử lí các phần tử trong danh sách: - - - -Đa phần đều không thể đạt được nếu không có các kiểu dữ liệu. - -Chú ý rằng, biến `item` là một trong các phần tử trong danh sách `items`. - -Và do vậy, trình soạn thảo biết nó là một `str`, và cung cấp sự hỗ trợ cho nó. - -#### Tuple and Set - -Bạn sẽ làm điều tương tự để khai báo các `tuple` và các `set`: - -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// - -Điều này có nghĩa là: - -* Biến `items_t` là một `tuple` với 3 phần tử, một `int`, một `int` nữa, và một `str`. -* Biến `items_s` là một `set`, và mỗi phần tử của nó có kiểu `bytes`. - -#### Dict - -Để định nghĩa một `dict`, bạn truyền 2 tham số kiểu dữ liệu, phân cách bởi dấu phẩy. - -Tham số kiểu dữ liệu đầu tiên dành cho khóa của `dict`. - -Tham số kiểu dữ liệu thứ hai dành cho giá trị của `dict`. - -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// - -Điều này có nghĩa là: - -* Biến `prices` là một `dict`: - * Khóa của `dict` này là kiểu `str` (đó là tên của mỗi vật phẩm). - * Giá trị của `dict` này là kiểu `float` (đó là giá của mỗi vật phẩm). - -#### Union - -Bạn có thể khai báo rằng một biến có thể là **một vài kiểu dữ liệu" bất kì, ví dụ, một `int` hoặc một `str`. - -Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể sử dụng kiểu `Union` từ `typing` và đặt trong dấu ngoặc vuông những giá trị được chấp nhận. - -In Python 3.10 there's also a **new syntax** where you can put the possible types separated by a vertical bar (`|`). - -Trong Python 3.10 cũng có một **cú pháp mới** mà bạn có thể đặt những kiểu giá trị khả thi phân cách bởi một dấu sổ dọc (`|`). - - -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008b_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} -``` - -//// - -Trong cả hai trường hợp có nghĩa là `item` có thể là một `int` hoặc `str`. - -#### Khả năng `None` - -Bạn có thể khai báo một giá trị có thể có một kiểu dữ liệu, giống như `str`, nhưng nó cũng có thể là `None`. - -Trong Python 3.6 hoặc lớn hơn (bao gồm Python 3.10) bạn có thể khai báo nó bằng các import và sử dụng `Optional` từ mô đun `typing`. - -```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} -``` - -Sử dụng `Optional[str]` thay cho `str` sẽ cho phép trình soạn thảo giúp bạn phát hiện các lỗi mà bạn có thể gặp như một giá trị luôn là một `str`, trong khi thực tế nó rất có thể là `None`. - -`Optional[Something]` là một cách viết ngắn gọn của `Union[Something, None]`, chúng là tương đương nhau. - -Điều này cũng có nghĩa là trong Python 3.10, bạn có thể sử dụng `Something | None`: - -//// tab | Python 3.10+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial009_py310.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} -``` - -//// - -//// tab | Python 3.8+ alternative - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} -``` - -//// - -#### Sử dụng `Union` hay `Optional` - -If you are using a Python version below 3.10, here's a tip from my very **subjective** point of view: - -Nếu bạn đang sử dụng phiên bản Python dưới 3.10, đây là một mẹo từ ý kiến rất "chủ quan" của tôi: - -* 🚨 Tránh sử dụng `Optional[SomeType]` -* Thay vào đó ✨ **sử dụng `Union[SomeType, None]`** ✨. - -Cả hai là tương đương và bên dưới chúng giống nhau, nhưng tôi sẽ đễ xuất `Union` thay cho `Optional` vì từ "**tùy chọn**" có vẻ ngầm định giá trị là tùy chọn, và nó thực sự có nghĩa rằng "nó có thể là `None`", do đó nó không phải là tùy chọn và nó vẫn được yêu cầu. - -Tôi nghĩ `Union[SomeType, None]` là rõ ràng hơn về ý nghĩa của nó. - -Nó chỉ là về các từ và tên. Nhưng những từ đó có thể ảnh hưởng cách bạn và những đồng đội của bạn suy nghĩ về code. - -Cho một ví dụ, hãy để ý hàm này: - -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} - - -Tham số `name` được định nghĩa là `Optional[str]`, nhưng nó **không phải là tùy chọn**, bạn không thể gọi hàm mà không có tham số: - -```Python -say_hi() # Oh, no, this throws an error! 😱 -``` - -Tham số `name` **vẫn được yêu cầu** (không phải là *tùy chọn*) vì nó không có giá trị mặc định. Trong khi đó, `name` chấp nhận `None` như là giá trị: - -```Python -say_hi(name=None) # This works, None is valid 🎉 -``` - -Tin tốt là, khi bạn sử dụng Python 3.10, bạn sẽ không phải lo lắng về điều đó, bạn sẽ có thể sử dụng `|` để định nghĩa hợp của các kiểu dữ liệu một cách đơn giản: - -{* ../../docs_src/python_types/tutorial009c_py310.py hl[1,4] *} - - -Và sau đó, bạn sẽ không phải lo rằng những cái tên như `Optional` và `Union`. 😎 - - -#### Những kiểu dữ liệu tổng quát - -Những kiểu dữ liệu này lấy tham số kiểu dữ liệu trong dấu ngoặc vuông được gọi là **Kiểu dữ liệu tổng quát**, cho ví dụ: - -//// tab | Python 3.10+ - -Bạn có thể sử dụng các kiểu dữ liệu có sẵn như là kiểu dữ liệu tổng quát (với ngoặc vuông và kiểu dữ liệu bên trong): - -* `list` -* `tuple` -* `set` -* `dict` - -Và tương tự với Python 3.6, từ mô đun `typing`: - -* `Union` -* `Optional` (tương tự như Python 3.6) -* ...và các kiểu dữ liệu khác. - -Trong Python 3.10, thay vì sử dụng `Union` và `Optional`, bạn có thể sử dụng sổ dọc ('|') để khai báo hợp của các kiểu dữ liệu, điều đó tốt hơn và đơn giản hơn nhiều. - -//// - -//// tab | Python 3.9+ - -Bạn có thể sử dụng các kiểu dữ liệu có sẵn tương tự như (với ngoặc vuông và kiểu dữ liệu bên trong): - -* `list` -* `tuple` -* `set` -* `dict` - -Và tương tự với Python 3.6, từ mô đun `typing`: - -* `Union` -* `Optional` -* ...and others. - -//// - -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...và các kiểu khác. - -//// - -### Lớp như kiểu dữ liệu - -Bạn cũng có thể khai báo một lớp như là kiểu dữ liệu của một biến. - -Hãy nói rằng bạn muốn có một lớp `Person` với một tên: - -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} - - -Sau đó bạn có thể khai báo một biến có kiểu là `Person`: - -{* ../../docs_src/python_types/tutorial010.py hl[6] *} - - -Và lại một lần nữa, bạn có được tất cả sự hỗ trợ từ trình soạn thảo: - - - -Lưu ý rằng, điều này có nghĩa rằng "`one_person`" là một **thực thể** của lớp `Person`. - -Nó không có nghĩa "`one_person`" là một **lớp** gọi là `Person`. - -## Pydantic models - -Pydantic là một thư viện Python để validate dữ liệu hiệu năng cao. - -Bạn có thể khai báo "hình dạng" của dữa liệu như là các lớp với các thuộc tính. - -Và mỗi thuộc tính có một kiểu dữ liệu. - -Sau đó bạn tạo một thực thể của lớp đó với một vài giá trị và nó sẽ validate các giá trị, chuyển đổi chúng sang kiểu dữ liệu phù hợp (nếu đó là trường hợp) và cho bạn một object với toàn bộ dữ liệu. - -Và bạn nhận được tất cả sự hỗ trợ của trình soạn thảo với object kết quả đó. - -Một ví dụ từ tài liệu chính thức của Pydantic: - -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// - -/// info - -Để học nhiều hơn về Pydantic, tham khảo tài liệu của nó. - -/// - -**FastAPI** được dựa hoàn toàn trên Pydantic. - -Bạn sẽ thấy nhiều ví dụ thực tế hơn trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}. - -/// tip - -Pydantic có một hành vi đặc biệt khi bạn sử dụng `Optional` hoặc `Union[Something, None]` mà không có giá trị mặc dịnh, bạn có thể đọc nhiều hơn về nó trong tài liệu của Pydantic về Required Optional fields. - -/// - -## Type Hints với Metadata Annotations - -Python cũng có một tính năng cho phép đặt **metadata bổ sung** trong những gợi ý kiểu dữ liệu này bằng cách sử dụng `Annotated`. - -//// tab | Python 3.9+ - -Trong Python 3.9, `Annotated` là một phần của thư viện chuẩn, do đó bạn có thể import nó từ `typing`. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -Ở phiên bản dưới Python 3.9, bạn import `Annotated` từ `typing_extensions`. - -Nó đã được cài đặt sẵng cùng với **FastAPI**. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// - -Python bản thân nó không làm bất kì điều gì với `Annotated`. Với các trình soạn thảo và các công cụ khác, kiểu dữ liệu vẫn là `str`. - -Nhưng bạn có thể sử dụng `Annotated` để cung cấp cho **FastAPI** metadata bổ sung về cách mà bạn muốn ứng dụng của bạn xử lí. - -Điều quan trọng cần nhớ là ***tham số kiểu dữ liệu* đầu tiên** bạn truyền tới `Annotated` là **kiểu giá trị thực sự**. Phần còn lại chỉ là metadata cho các công cụ khác. - -Bây giờ, bạn chỉ cần biết rằng `Annotated` tồn tại, và nó là tiêu chuẩn của Python. 😎 - - -Sau đó, bạn sẽ thấy sự **mạnh mẽ** mà nó có thể làm. - -/// tip - -Thực tế, cái này là **tiêu chuẩn của Python**, nghĩa là bạn vẫn sẽ có được **trải nghiệm phát triển tốt nhất có thể** với trình soạn thảo của bạn, với các công cụ bạn sử dụng để phân tích và tái cấu trúc code của bạn, etc. ✨ - -Và code của bạn sẽ tương thích với nhiều công cụ và thư viện khác của Python. 🚀 - -/// - -## Các gợi ý kiểu dữ liệu trong **FastAPI** - -**FastAPI** lấy các ưu điểm của các gợi ý kiểu dữ liệu để thực hiện một số thứ. - -Với **FastAPI**, bạn khai báo các tham số với gợi ý kiểu và bạn có được: - -* **Sự hỗ trợ từ các trình soạn thảo**. -* **Kiểm tra kiểu dữ liệu (type checking)**. - -...và **FastAPI** sử dụng các khia báo để: - -* **Định nghĩa các yêu cầu**: từ tham số đường dẫn của request, tham số query, headers, bodies, các phụ thuộc (dependencies),... -* **Chuyển dổi dữ liệu*: từ request sang kiểu dữ liệu được yêu cầu. -* **Kiểm tra tính đúng đắn của dữ liệu**: tới từ mỗi request: - * Sinh **lỗi tự động** để trả về máy khác khi dữ liệu không hợp lệ. -* **Tài liệu hóa** API sử dụng OpenAPI: - * cái mà sau được được sử dụng bởi tài liệu tương tác người dùng. - -Điều này có thể nghe trừu tượng. Đừng lo lắng. Bạn sẽ thấy tất cả chúng trong [Hướng dẫn sử dụng](tutorial/index.md){.internal-link target=_blank}. - -Điều quan trọng là bằng việc sử dụng các kiểu dữ liệu chuẩn của Python (thay vì thêm các lớp, decorators,...), **FastAPI** sẽ thực hiện nhiều công việc cho bạn. - -/// info - -Nếu bạn đã đi qua toàn bộ các hướng dẫn và quay trở lại để tìm hiểu nhiều hơn về các kiểu dữ liệu, một tài nguyên tốt như "cheat sheet" từ `mypy`. - -/// diff --git a/docs/vi/docs/tutorial/first-steps.md b/docs/vi/docs/tutorial/first-steps.md deleted file mode 100644 index d1650539c1..0000000000 --- a/docs/vi/docs/tutorial/first-steps.md +++ /dev/null @@ -1,335 +0,0 @@ -# Những bước đầu tiên - -Tệp tin FastAPI đơn giản nhất có thể trông như này: - -{* ../../docs_src/first_steps/tutorial001.py *} - -Sao chép sang một tệp tin `main.py`. - -Chạy live server: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -/// note - -Câu lệnh `uvicorn main:app` được giải thích như sau: - -* `main`: tệp tin `main.py` (một Python "mô đun"). -* `app`: một object được tạo ra bên trong `main.py` với dòng `app = FastAPI()`. -* `--reload`: làm server khởi động lại sau mỗi lần thay đổi. Chỉ sử dụng trong môi trường phát triển. - -/// - -Trong output, có một dòng giống như: - -```hl_lines="4" -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -Dòng đó cho thấy URL, nơi mà app của bạn đang được chạy, trong máy local của bạn. - -### Kiểm tra - -Mở trình duyệt của bạn tại http://127.0.0.1:8000. - -Bạn sẽ thấy một JSON response như: - -```JSON -{"message": "Hello World"} -``` - -### Tài liệu tương tác API - -Bây giờ tới http://127.0.0.1:8000/docs. - -Bạn sẽ thấy một tài liệu tương tác API (cung cấp bởi Swagger UI): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Phiên bản thay thế của tài liệu API - -Và bây giờ tới http://127.0.0.1:8000/redoc. - -Bạn sẽ thấy một bản thay thế của tài liệu (cung cấp bởi ReDoc): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -### OpenAPI - -**FastAPI** sinh một "schema" với tất cả API của bạn sử dụng tiêu chuẩn **OpenAPI** cho định nghĩa các API. - -#### "Schema" - -Một "schema" là một định nghĩa hoặc mô tả thứ gì đó. Không phải code triển khai của nó, nhưng chỉ là một bản mô tả trừu tượng. - -#### API "schema" - -Trong trường hợp này, OpenAPI là một bản mô tả bắt buộc cơ chế định nghĩa API của bạn. - -Định nghĩa cấu trúc này bao gồm những đường dẫn API của bạn, các tham số có thể có,... - -#### "Cấu trúc" dữ liệu - -Thuật ngữ "cấu trúc" (schema) cũng có thể được coi như là hình dạng của dữ liệu, tương tự như một JSON content. - -Trong trường hợp đó, nó có nghĩa là các thuộc tính JSON và các kiểu dữ liệu họ có,... - -#### OpenAPI và JSON Schema - -OpenAPI định nghĩa một cấu trúc API cho API của bạn. Và cấu trúc đó bao gồm các dịnh nghĩa (or "schema") về dữ liệu được gửi đi và nhận về bởi API của bạn, sử dụng **JSON Schema**, một tiêu chuẩn cho cấu trúc dữ liệu JSON. - -#### Kiểm tra `openapi.json` - -Nếu bạn tò mò về việc cấu trúc OpenAPI nhìn như thế nào thì FastAPI tự động sinh một JSON (schema) với các mô tả cho tất cả API của bạn. - -Bạn có thể thấy nó trực tiếp tại: http://127.0.0.1:8000/openapi.json. - -Nó sẽ cho thấy một JSON bắt đầu giống như: - -```JSON -{ - "openapi": "3.1.0", - "info": { - "title": "FastAPI", - "version": "0.1.0" - }, - "paths": { - "/items/": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - - - -... -``` - -#### OpenAPI dùng để làm gì? - -Cấu trúc OpenAPI là sức mạnh của tài liệu tương tác. - -Và có hàng tá các bản thay thế, tất cả đều dựa trên OpenAPI. Bạn có thể dễ dàng thêm bất kì bản thay thế bào cho ứng dụng của bạn được xây dựng với **FastAPI**. - -Bạn cũng có thể sử dụng nó để sinh code tự động, với các client giao viết qua API của bạn. Ví dụ, frontend, mobile hoặc các ứng dụng IoT. - -## Tóm lại, từng bước một - -### Bước 1: import `FastAPI` - -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} - -`FastAPI` là một Python class cung cấp tất cả chức năng cho API của bạn. - -/// note | Chi tiết kĩ thuật - -`FastAPI` là một class kế thừa trực tiếp `Starlette`. - -Bạn cũng có thể sử dụng tất cả Starlette chức năng với `FastAPI`. - -/// - -### Bước 2: Tạo một `FastAPI` "instance" - -{* ../../docs_src/first_steps/tutorial001.py hl[3] *} - -Biến `app` này là một "instance" của class `FastAPI`. - -Đây sẽ là điểm cốt lõi để tạo ra tất cả API của bạn. - -`app` này chính là điều được nhắc tới bởi `uvicorn` trong câu lệnh: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -Nếu bạn tạo ứng dụng của bạn giống như: - -{* ../../docs_src/first_steps/tutorial002.py hl[3] *} - -Và đặt nó trong một tệp tin `main.py`, sau đó bạn sẽ gọi `uvicorn` giống như: - -
- -```console -$ uvicorn main:my_awesome_api --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -``` - -
- -### Bước 3: tạo một *đường dẫn toán tử* - -#### Đường dẫn - -"Đường dẫn" ở đây được nhắc tới là phần cuối cùng của URL bắt đầu từ `/`. - -Do đó, trong một URL nhìn giống như: - -``` -https://example.com/items/foo -``` - -...đường dẫn sẽ là: - -``` -/items/foo -``` - -/// info - -Một đường dẫn cũng là một cách gọi chung cho một "endpoint" hoặc một "route". - -/// - -Trong khi xây dựng một API, "đường dẫn" là các chính để phân tách "mối quan hệ" và "tài nguyên". - -#### Toán tử (Operation) - -"Toán tử" ở đây được nhắc tới là một trong các "phương thức" HTTP. - -Một trong những: - -* `POST` -* `GET` -* `PUT` -* `DELETE` - -...và một trong những cái còn lại: - -* `OPTIONS` -* `HEAD` -* `PATCH` -* `TRACE` - -Trong giao thức HTTP, bạn có thể giao tiếp trong mỗi đường dẫn sử dụng một (hoặc nhiều) trong các "phương thức này". - ---- - -Khi xây dựng các API, bạn thường sử dụng cụ thể các phương thức HTTP này để thực hiện một hành động cụ thể. - -Thông thường, bạn sử dụng - -* `POST`: để tạo dữ liệu. -* `GET`: để đọc dữ liệu. -* `PUT`: để cập nhật dữ liệu. -* `DELETE`: để xóa dữ liệu. - -Do đó, trong OpenAPI, mỗi phương thức HTTP được gọi là một "toán tử (operation)". - -Chúng ta cũng sẽ gọi chúng là "**các toán tử**". - -#### Định nghĩa moojt *decorator cho đường dẫn toán tử* - -{* ../../docs_src/first_steps/tutorial001.py hl[6] *} - -`@app.get("/")` nói **FastAPI** rằng hàm bên dưới có trách nhiệm xử lí request tới: - -* đường dẫn `/` -* sử dụng một toán tửget - -/// info | Thông tin về "`@decorator`" - -Cú pháp `@something` trong Python được gọi là một "decorator". - -Bạn đặt nó trên một hàm. Giống như một chiếc mũ xinh xắn (Tôi ddonas đó là lí do mà thuật ngữ này ra đời). - -Một "decorator" lấy một hàm bên dưới và thực hiện một vài thứ với nó. - -Trong trường hợp của chúng ta, decorator này nói **FastAPI** rằng hàm bên dưới ứng với **đường dẫn** `/` và một **toán tử** `get`. - -Nó là một "**decorator đường dẫn toán tử**". - -/// - -Bạn cũng có thể sử dụng với các toán tử khác: - -* `@app.post()` -* `@app.put()` -* `@app.delete()` - -Và nhiều hơn với các toán tử còn lại: - -* `@app.options()` -* `@app.head()` -* `@app.patch()` -* `@app.trace()` - -/// tip - -Bạn thoải mái sử dụng mỗi toán tử (phương thức HTTP) như bạn mơ ước. - -**FastAPI** không bắt buộc bất kì ý nghĩa cụ thể nào. - -Thông tin ở đây được biểu thị như là một chỉ dẫn, không phải là một yêu cầu bắt buộc. - -Ví dụ, khi sử dụng GraphQL bạn thông thường thực hiện tất cả các hành động chỉ bằng việc sử dụng các toán tử `POST`. - -/// - -### Step 4: Định nghĩa **hàm cho đường dẫn toán tử** - -Đây là "**hàm cho đường dẫn toán tử**": - -* **đường dẫn**: là `/`. -* **toán tử**: là `get`. -* **hàm**: là hàm bên dưới "decorator" (bên dưới `@app.get("/")`). - -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} - -Đây là một hàm Python. - -Nó sẽ được gọi bởi **FastAPI** bất cứ khi nào nó nhận một request tới URL "`/`" sử dụng một toán tử `GET`. - -Trong trường hợp này, nó là một hàm `async`. - ---- - -Bạn cũng có thể định nghĩa nó như là một hàm thông thường thay cho `async def`: - -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} - -/// note - -Nếu bạn không biết sự khác nhau, kiểm tra [Async: *"Trong khi vội vàng?"*](../async.md#in-a-hurry){.internal-link target=_blank}. - -/// - -### Bước 5: Nội dung trả về - -{* ../../docs_src/first_steps/tutorial001.py hl[8] *} - -Bạn có thể trả về một `dict`, `list`, một trong những giá trị đơn như `str`, `int`,... - -Bạn cũng có thể trả về Pydantic model (bạn sẽ thấy nhiều hơn về nó sau). - -Có nhiều object và model khác nhau sẽ được tự động chuyển đổi sang JSON (bao gồm cả ORM,...). Thử sử dụng loại ưa thích của bạn, nó có khả năng cao đã được hỗ trợ. - -## Tóm lại - -* Import `FastAPI`. -* Tạo một `app` instance. -* Viết một **decorator cho đường dẫn toán tử** (giống như `@app.get("/")`). -* Viết một **hàm cho đường dẫn toán tử** (giống như `def root(): ...` ở trên). -* Chạy server trong môi trường phát triển (giống như `uvicorn main:app --reload`). diff --git a/docs/vi/docs/tutorial/index.md b/docs/vi/docs/tutorial/index.md deleted file mode 100644 index dfeeed8c56..0000000000 --- a/docs/vi/docs/tutorial/index.md +++ /dev/null @@ -1,83 +0,0 @@ -# Hướng dẫn sử dụng - -Hướng dẫn này cho bạn thấy từng bước cách sử dụng **FastAPI** đa số các tính năng của nó. - -Mỗi phần được xây dựng từ những phần trước đó, nhưng nó được cấu trúc thành các chủ đề riêng biệt, do đó bạn có thể xem trực tiếp từng phần cụ thể bất kì để giải quyết những API cụ thể mà bạn cần. - -Nó cũng được xây dựng để làm việc như một tham chiếu trong tương lai. - -Do đó bạn có thể quay lại và tìm chính xác những gì bạn cần. - -## Chạy mã - -Tất cả các code block có thể được sao chép và sử dụng trực tiếp (chúng thực chất là các tệp tin Python đã được kiểm thử). - -Để chạy bất kì ví dụ nào, sao chép code tới tệp tin `main.py`, và bắt đầu `uvicorn` với: - -
- -```console -$ uvicorn main:app --reload - -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [28720] -INFO: Started server process [28722] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -**Khuyến khích** bạn viết hoặc sao chép code, sửa và chạy nó ở local. - -Sử dụng nó trong trình soạn thảo của bạn thực sự cho bạn thấy những lợi ích của FastAPI, thấy được cách bạn viết code ít hơn, tất cả đều được type check, autocompletion,... - ---- - -## Cài đặt FastAPI - -Bước đầu tiên là cài đặt FastAPI. - -Với hướng dẫn này, bạn có thể muốn cài đặt nó với tất cả các phụ thuộc và tính năng tùy chọn: - -
- -```console -$ pip install "fastapi[all]" - ----> 100% -``` - -
- -...dó cũng bao gồm `uvicorn`, bạn có thể sử dụng như một server để chạy code của bạn. - -/// note - -Bạn cũng có thể cài đặt nó từng phần. - -Đây là những gì bạn có thể sẽ làm một lần duy nhất bạn muốn triển khai ứng dụng của bạn lên production: - -``` -pip install fastapi -``` - -Cũng cài đặt `uvicorn` để làm việc như một server: - -``` -pip install "uvicorn[standard]" -``` - -Và tương tự với từng phụ thuộc tùy chọn mà bạn muốn sử dụng. - -/// - -## Hướng dẫn nâng cao - -Cũng có một **Hướng dẫn nâng cao** mà bạn có thể đọc nó sau **Hướng dẫn sử dụng**. - -**Hướng dẫn sử dụng nâng cao**, xây dựng dựa trên cái này, sử dụng các khái niệm tương tự, và dạy bạn những tính năng mở rộng. - -Nhưng bạn nên đọc **Hướng dẫn sử dụng** đầu tiên (những gì bạn đang đọc). - -Nó được thiết kế do đó bạn có thể xây dựng một ứng dụng hoàn chỉnh chỉ với **Hướng dẫn sử dụng**, và sau đó mở rộng nó theo các cách khác nhau, phụ thuộc vào những gì bạn cần, sử dụng một vài ý tưởng bổ sung từ **Hướng dẫn sử dụng nâng cao**. diff --git a/docs/vi/docs/tutorial/static-files.md b/docs/vi/docs/tutorial/static-files.md deleted file mode 100644 index 1bbec29e7c..0000000000 --- a/docs/vi/docs/tutorial/static-files.md +++ /dev/null @@ -1,40 +0,0 @@ -# Tệp tĩnh (Static Files) - -Bạn có thể triển khai tệp tĩnh tự động từ một thư mục bằng cách sử dụng StaticFiles. - -## Sử dụng `Tệp tĩnh` - -- Nhập `StaticFiles`. -- "Mount" a `StaticFiles()` instance in a specific path. - -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} - -/// note | Chi tiết kỹ thuật - -Bạn cũng có thể sử dụng `from starlette.staticfiles import StaticFiles`. - -**FastAPI** cung cấp cùng `starlette.staticfiles` như `fastapi.staticfiles` giúp đơn giản hóa việc sử dụng, nhưng nó thực sự đến từ Starlette. - -/// - -### "Mounting" là gì - -"Mounting" có nghĩa là thêm một ứng dụng "độc lập" hoàn chỉnh vào một đường dẫn cụ thể, sau đó ứng dụng đó sẽ chịu trách nhiệm xử lý tất cả các đường dẫn con. - -Điều này khác với việc sử dụng `APIRouter` vì một ứng dụng được gắn kết là hoàn toàn độc lập. OpenAPI và tài liệu từ ứng dụng chính của bạn sẽ không bao gồm bất kỳ thứ gì từ ứng dụng được gắn kết, v.v. - -Bạn có thể đọc thêm về điều này trong [Hướng dẫn Người dùng Nâng cao](../advanced/index.md){.internal-link target=\_blank}. - -## Chi tiết - -Đường dẫn đầu tiên `"/static"` là đường dẫn con mà "ứng dụng con" này sẽ được "gắn" vào. Vì vậy, bất kỳ đường dẫn nào bắt đầu bằng `"/static"` sẽ được xử lý bởi nó. - -Đường dẫn `directory="static"` là tên của thư mục chứa tệp tĩnh của bạn. - -Tham số `name="static"` đặt tên cho nó để có thể được sử dụng bên trong **FastAPI**. - -Tất cả các tham số này có thể khác với `static`, điều chỉnh chúng với phù hợp với ứng dụng của bạn. - -## Thông tin thêm - -Để biết thêm chi tiết và tùy chọn, hãy xem Starlette's docs about Static Files. diff --git a/docs/vi/docs/virtual-environments.md b/docs/vi/docs/virtual-environments.md deleted file mode 100644 index 22d8e153e5..0000000000 --- a/docs/vi/docs/virtual-environments.md +++ /dev/null @@ -1,842 +0,0 @@ -# Môi trường ảo (Virtual Environments) - -Khi bạn làm việc trong các dự án Python, bạn có thể sử dụng một **môi trường ảo** (hoặc một cơ chế tương tự) để cách ly các gói bạn cài đặt cho mỗi dự án. - -/// info -Nếu bạn đã biết về các môi trường ảo, cách tạo chúng và sử dụng chúng, bạn có thể bỏ qua phần này. 🤓 - -/// - -/// tip - -Một **môi trường ảo** khác với một **biến môi trường (environment variable)**. - -Một **biến môi trường** là một biến trong hệ thống có thể được sử dụng bởi các chương trình. - -Một **môi trường ảo** là một thư mục với một số tệp trong đó. - -/// - -/// info - -Trang này sẽ hướng dẫn bạn cách sử dụng các **môi trường ảo** và cách chúng hoạt động. - -Nếu bạn đã sẵn sàng sử dụng một **công cụ có thể quản lý tất cả mọi thứ** cho bạn (bao gồm cả việc cài đặt Python), hãy thử uv. - -/// - -## Tạo một Dự án - -Đầu tiên, tạo một thư mục cho dự án của bạn. - -Cách tôi thường làm là tạo một thư mục có tên `code` trong thư mục `home/user`. - -Và trong thư mục đó, tôi tạo một thư mục cho mỗi dự án. - -
- -```console -// Đi đến thư mục home -$ cd -// Tạo một thư mục cho tất cả các dự án của bạn -$ mkdir code -// Vào thư mục code -$ cd code -// Tạo một thư mục cho dự án này -$ mkdir awesome-project -// Vào thư mục dự án -$ cd awesome-project -``` - -
- -## Tạo một Môi trường ảo - -Khi bạn bắt đầu làm việc với một dự án Python **trong lần đầu**, hãy tạo một môi trường ảo **trong thư mục dự án của bạn**. - -/// tip - -Bạn cần làm điều này **một lần cho mỗi dự án**, không phải mỗi khi bạn làm việc. -/// - -//// tab | `venv` - -Để tạo một môi trường ảo, bạn có thể sử dụng module `venv` có sẵn của Python. - -
- -```console -$ python -m venv .venv -``` - -
- -/// details | Cách các lệnh hoạt động - -* `python`: sử dụng chương trình `python` -* `-m`: gọi một module như một script, chúng ta sẽ nói về module đó sau -* `venv`: sử dụng module `venv` được cài đặt sẵn của Python -* `.venv`: tạo môi trường ảo trong thư mục mới `.venv` - -/// - -//// - -//// tab | `uv` - -Nếu bạn có `uv` được cài đặt, bạn có thể sử dụng nó để tạo một môi trường ảo. - -
- -```console -$ uv venv -``` - -
- -/// tip - -Mặc định, `uv` sẽ tạo một môi trường ảo trong một thư mục có tên `.venv`. - -Nhưng bạn có thể tùy chỉnh nó bằng cách thêm một đối số với tên thư mục. - -/// - -//// - -Lệnh này tạo một môi trường ảo mới trong một thư mục có tên `.venv`. - -/// details | `.venv` hoặc tên khác - -Bạn có thể tạo môi trường ảo trong một thư mục khác, nhưng thường người ta quy ước đặt nó là `.venv`. - -/// - -## Kích hoạt Môi trường ảo - -Kích hoạt môi trường ảo mới để bất kỳ lệnh Python nào bạn chạy hoặc gói nào bạn cài đặt sẽ sử dụng nó. - -/// tip - -Làm điều này **mỗi khi** bạn bắt đầu một **phiên terminal mới** để làm việc trên dự án. - -/// - -//// tab | Linux, macOS - -
- -```console -$ source .venv/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .venv\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Nếu bạn sử dụng Bash cho Windows (ví dụ: Git Bash): - -
- -```console -$ source .venv/Scripts/activate -``` - -
- -//// - -/// tip - -Mỗi khi bạn cài đặt thêm một **package mới** trong môi trường đó, hãy **kích hoạt** môi trường đó lại. - -Điều này đảm bảo rằng khi bạn sử dụng một **chương trình dòng lệnh (CLI)** được cài đặt từ gói đó, bạn sẽ dùng bản cài đặt từ môi trường ảo của mình thay vì bản được cài đặt toàn cục khác có thể có phiên bản khác với phiên bản bạn cần. - -/// - -## Kiểm tra xem Môi trường ảo đã được Kích hoạt chưa - -Kiểm tra xem môi trường ảo đã được kích hoạt chưa (lệnh trước đó đã hoạt động). - -/// tip - -Điều này là **không bắt buộc**, nhưng nó là một cách tốt để **kiểm tra** rằng mọi thứ đang hoạt động như mong đợi và bạn đang sử dụng đúng môi trường ảo mà bạn đã định. - -/// - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which python - -/home/user/code/awesome-project/.venv/bin/python -``` - -
- -Nếu nó hiển thị `python` binary tại `.venv/bin/python`, trong dự án của bạn (trong trường hợp `awesome-project`), thì tức là nó hoạt động. 🎉 - -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command python - -C:\Users\user\code\awesome-project\.venv\Scripts\python -``` - -
- -Nếu nó hiển thị `python` binary tại `.venv\Scripts\python`, trong dự án của bạn (trong trường hợp `awesome-project`), thì tức là nó hoạt động. 🎉 - -//// - -## Nâng cấp `pip` - -/// tip - -Nếu bạn sử dụng `uv` bạn sử dụng nó để cài đặt thay vì `pip`, thì bạn không cần cập nhật `pip`. 😎 - -/// - -Nếu bạn sử dụng `pip` để cài đặt gói (nó được cài đặt mặc định với Python), bạn nên **nâng cấp** nó lên phiên bản mới nhất. - -Nhiều lỗi khác nhau trong khi cài đặt gói được giải quyết chỉ bằng cách nâng cấp `pip` trước. - -/// tip - -Bạn thường làm điều này **một lần**, ngay sau khi bạn tạo môi trường ảo. - -/// - -Đảm bảo rằng môi trường ảo đã được kích hoạt (với lệnh trên) và sau đó chạy: - -
- -```console -$ python -m pip install --upgrade pip - ----> 100% -``` - -
- -## Thêm `.gitignore` - -Nếu bạn sử dụng **Git** (nên làm), hãy thêm một file `.gitignore` để Git bỏ qua mọi thứ trong `.venv`. - -/// tip - -Nếu bạn sử dụng `uv` để tạo môi trường ảo, nó đã tự động làm điều này cho bạn, bạn có thể bỏ qua bước này. 😎 - -/// - -/// tip - -Làm điều này **một lần**, ngay sau khi bạn tạo môi trường ảo. - -/// - -
- -```console -$ echo "*" > .venv/.gitignore -``` - -
- -/// details | Cách lệnh hoạt động - -* `echo "*"`: sẽ "in" văn bản `*` trong terminal (phần tiếp theo sẽ thay đổi điều đó một chút) -* `>`: bất kỳ văn bản nào được in ra terminal bởi lệnh trước `>` không được in ra mà thay vào đó được viết vào file ở phía bên phải của `>` -* `.gitignore`: tên của file mà văn bản sẽ được viết vào - -Và `*` với Git có nghĩa là "mọi thứ". Vì vậy, nó sẽ bỏ qua mọi thứ trong thư mục `.venv`. - -Lệnh này sẽ tạo một file `.gitignore` với nội dung: - -```gitignore -* -``` - -/// - -## Cài đặt gói (packages) - -Sau khi kích hoạt môi trường, bạn có thể cài đặt các gói trong đó. - -/// tip - -Thực hiện điều này **một lần** khi cài đặt hoặc cập nhật gói cần thiết cho dự án của bạn. - -Nếu bạn cần cập nhật phiên bản hoặc thêm một gói mới, bạn sẽ **thực hiện điều này lại**. - -/// - -### Cài đặt gói trực tiếp - -Nếu bạn cần cập nhật phiên bản hoặc thêm một gói mới, bạn sẽ **thực hiện điều này lại**. - -/// tip -Để quản lý dự án tốt hơn, hãy liệt kê tất cả các gói và phiên bản cần thiết trong một file (ví dụ `requirements.txt` hoặc `pyproject.toml`). - -/// - -//// tab | `pip` - -
- -```console -$ pip install "fastapi[standard]" - ----> 100% -``` - -
- -//// - -//// tab | `uv` - -Nếu bạn có `uv`: - -
- -```console -$ uv pip install "fastapi[standard]" ----> 100% -``` - -
- -//// - -### Cài đặt từ `requirements.txt` - -Nếu bạn có một tệp `requirements.txt`, bạn có thể sử dụng nó để cài đặt các gói. - -//// tab | `pip` - -
- -```console -$ pip install -r requirements.txt ----> 100% -``` - -
- -//// - -//// tab | `uv` - -Nếu bạn có `uv`: - -
- -```console -$ uv pip install -r requirements.txt ----> 100% -``` - -
- -//// - -/// details | `requirements.txt` - -Một tệp `requirements.txt` với một số gói sẽ trông như thế này: - -```requirements.txt -fastapi[standard]==0.113.0 -pydantic==2.8.0 -``` - -/// - -## Chạy Chương trình của bạn - -Sau khi kích hoạt môi trường ảo, bạn có thể chạy chương trình của mình, nó sẽ sử dụng Python trong môi trường ảo của bạn với các gói bạn đã cài đặt. - -
- -```console -$ python main.py - -Hello World -``` - -
- -## Cấu hình Trình soạn thảo của bạn - -Nếu bạn sử dụng một trình soạn thảo, hãy đảm bảo bạn cấu hình nó để sử dụng cùng môi trường ảo mà bạn đã tạo (trình soạn thảo sẽ tự động phát hiện môi trường ảo) để bạn có thể nhận được tính năng tự động hoàn thành câu lệnh (autocomplete) và in lỗi trực tiếp trong trình soạn thảo (inline errors). - -Ví dụ: - -* VS Code -* PyCharm - -/// tip - -Bạn thường chỉ cần làm điều này **một lần**, khi bạn tạo môi trường ảo. - -/// - -## Huỷ kích hoạt Môi trường ảo - -Khi bạn hoàn tất việc làm trên dự án của bạn, bạn có thể **huỷ kích hoạt** môi trường ảo. - -
- -```console -$ deactivate -``` - -
- -Như vậy, khi bạn chạy `python`, nó sẽ không chạy từ môi trường ảo đó với các gói đã cài đặt. - -## Sẵn sàng để Làm việc - -Bây giờ bạn đã sẵn sàng để làm việc trên dự án của mình rồi đấy. - -/// tip - -Bạn muốn hiểu tất cả những gì ở trên? - -Tiếp tục đọc. 👇🤓 - -/// - -## Tại sao cần Môi trường ảo - -Để làm việc với FastAPI, bạn cần cài đặt Python. - -Sau đó, bạn sẽ cần **cài đặt** FastAPI và bất kỳ **gói** nào mà bạn muốn sử dụng. - -Để cài đặt gói, bạn thường sử dụng lệnh `pip` có sẵn với Python (hoặc các phiên bản tương tự). - -Tuy nhiên, nếu bạn sử dụng `pip` trực tiếp, các gói sẽ được cài đặt trong **môi trường Python toàn cục** của bạn (phần cài đặt toàn cục của Python). - -### Vấn đề - -Vậy, vấn đề gì khi cài đặt gói trong môi trường Python toàn cục? - -Trong một vài thời điểm, bạn sẽ phải viết nhiều chương trình khác nhau phụ thuộc vào **các gói khác nhau**. Và một số dự án bạn thực hiện lại phụ thuộc vào **các phiên bản khác nhau** của cùng một gói. 😱 - -Ví dụ, bạn có thể tạo một dự án được gọi là `philosophers-stone`, chương trình này phụ thuộc vào một gói khác được gọi là **`harry`, sử dụng phiên bản `1`**. Vì vậy, bạn cần cài đặt `harry`. - -```mermaid -flowchart LR - stone(philosophers-stone) -->|phụ thuộc| harry-1[harry v1] -``` - -Sau đó, vào một vài thời điểm sau, bạn tạo một dự án khác được gọi là `prisoner-of-azkaban`, và dự án này cũng phụ thuộc vào `harry`, nhưng dự án này cần **`harry` phiên bản `3`**. - -```mermaid -flowchart LR - azkaban(prisoner-of-azkaban) --> |phụ thuộc| harry-3[harry v3] -``` - -Bây giờ, vấn đề là, nếu bạn cài đặt các gói toàn cục (trong môi trường toàn cục) thay vì trong một **môi trường ảo cục bộ**, bạn sẽ phải chọn phiên bản `harry` nào để cài đặt. - -Nếu bạn muốn chạy `philosophers-stone` bạn sẽ cần phải cài đặt `harry` phiên bản `1`, ví dụ với: - -
- -```console -$ pip install "harry==1" -``` - -
- -Và sau đó bạn sẽ có `harry` phiên bản `1` được cài đặt trong môi trường Python toàn cục của bạn. - -```mermaid -flowchart LR - subgraph global[môi trường toàn cục] - harry-1[harry v1] - end - subgraph stone-project[dự án philosophers-stone ] - stone(philosophers-stone) -->|phụ thuộc| harry-1 - end -``` - -Nhưng sau đó, nếu bạn muốn chạy `prisoner-of-azkaban`, bạn sẽ cần phải gỡ bỏ `harry` phiên bản `1` và cài đặt `harry` phiên bản `3` (hoặc chỉ cần cài đặt phiên bản `3` sẽ tự động gỡ bỏ phiên bản `1`). - -
- -```console -$ pip install "harry==3" -``` - -
- -Và sau đó bạn sẽ có `harry` phiên bản `3` được cài đặt trong môi trường Python toàn cục của bạn. - -Và nếu bạn cố gắng chạy `philosophers-stone` lại, có khả năng nó sẽ **không hoạt động** vì nó cần `harry` phiên bản `1`. - -```mermaid -flowchart LR - subgraph global[môi trường toàn cục] - harry-1[harry v1] - style harry-1 fill:#ccc,stroke-dasharray: 5 5 - harry-3[harry v3] - end - subgraph stone-project[dự án philosophers-stone ] - stone(philosophers-stone) -.-x|⛔️| harry-1 - end - subgraph azkaban-project[dự án prisoner-of-azkaban ] - azkaban(prisoner-of-azkaban) --> |phụ thuộc| harry-3 - end -``` - -/// tip - -Mặc dù các gói Python thường cố gắng **tránh các thay đổi làm hỏng code** trong **phiên bản mới**, nhưng để đảm bảo an toàn, bạn nên chủ động cài đặt phiên bản mới và chạy kiểm thử để xác nhận mọi thứ vẫn hoạt động đúng. - -/// - -Bây giờ, hãy hình dung về **nhiều** gói khác nhau mà tất cả các dự án của bạn phụ thuộc vào. Rõ ràng rất khó để quản lý. Điều này dẫn tới việc là bạn sẽ có nhiều dự án với **các phiên bản không tương thích** của các gói, và bạn có thể không biết tại sao một số thứ không hoạt động. - -Hơn nữa, tuỳ vào hệ điều hành của bạn (vd Linux, Windows, macOS), có thể đã có Python được cài đặt sẵn. Trong trường hợp ấy, một vài gói nhiều khả năng đã được cài đặt trước với các phiên bản **cần thiết cho hệ thống của bạn**. Nếu bạn cài đặt các gói trong môi trường Python toàn cục, bạn có thể sẽ **phá vỡ** một số chương trình đã được cài đặt sẵn cùng hệ thống. - -## Nơi các Gói được Cài đặt - -Khi bạn cài đặt Python, nó sẽ tạo ra một vài thư mục và tệp trong máy tính của bạn. - -Một vài thư mục này là những thư mục chịu trách nhiệm có tất cả các gói bạn cài đặt. - -Khi bạn chạy: - -
- -```console -// Đừng chạy lệnh này ngay, đây chỉ là một ví dụ 🤓 -$ pip install "fastapi[standard]" ----> 100% -``` - -
- -Lệnh này sẽ tải xuống một tệp nén với mã nguồn FastAPI, thường là từ PyPI. - -Nó cũng sẽ **tải xuống** các tệp cho các gói khác mà FastAPI phụ thuộc vào. - -Sau đó, nó sẽ **giải nén** tất cả các tệp đó và đưa chúng vào một thư mục trong máy tính của bạn. - -Mặc định, nó sẽ đưa các tệp đã tải xuống và giải nén vào thư mục được cài đặt cùng Python của bạn, đó là **môi trường toàn cục**. - -## Những Môi trường ảo là gì? - -Cách giải quyết cho vấn đề có tất cả các gói trong môi trường toàn cục là sử dụng một **môi trường ảo cho mỗi dự án** bạn làm việc. - -Một môi trường ảo là một **thư mục**, rất giống với môi trường toàn cục, trong đó bạn có thể cài đặt các gói cho một dự án. - -Vì vậy, mỗi dự án sẽ có một môi trường ảo riêng của nó (thư mục `.venv`) với các gói riêng của nó. - -```mermaid -flowchart TB - subgraph stone-project[dự án philosophers-stone ] - stone(philosophers-stone) --->|phụ thuộc| harry-1 - subgraph venv1[.venv] - harry-1[harry v1] - end - end - subgraph azkaban-project[dự án prisoner-of-azkaban ] - azkaban(prisoner-of-azkaban) --->|phụ thuộc| harry-3 - subgraph venv2[.venv] - harry-3[harry v3] - end - end - stone-project ~~~ azkaban-project -``` - -## Kích hoạt Môi trường ảo nghĩa là gì - -Khi bạn kích hoạt một môi trường ảo, ví dụ với: - -//// tab | Linux, macOS - -
- -```console -$ source .venv/bin/activate -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ .venv\Scripts\Activate.ps1 -``` - -
- -//// - -//// tab | Windows Bash - -Nếu bạn sử dụng Bash cho Windows (ví dụ Git Bash): - -
- -```console -$ source .venv/Scripts/activate -``` - -
- -//// - -Lệnh này sẽ tạo hoặc sửa đổi một số [biến môi trường](environment-variables.md){.internal-link target=_blank} mà sẽ được sử dụng cho các lệnh tiếp theo. - -Một trong số đó là biến `PATH`. - -/// tip - -Bạn có thể tìm hiểu thêm về biến `PATH` trong [Biến môi trường](environment-variables.md#path-environment-variable){.internal-link target=_blank} section. - -/// - -Kích hoạt môi trường ảo thêm đường dẫn `.venv/bin` (trên Linux và macOS) hoặc `.venv\Scripts` (trên Windows) vào biến `PATH`. - -Giả sử rằng trước khi kích hoạt môi trường, biến `PATH` như sau: - -//// tab | Linux, macOS - -```plaintext -/usr/bin:/bin:/usr/sbin:/sbin -``` - -Nghĩa là hệ thống sẽ tìm kiếm chương trình trong: - -* `/usr/bin` -* `/bin` -* `/usr/sbin` -* `/sbin` - -//// - -//// tab | Windows - -```plaintext -C:\Windows\System32 -``` - -Nghĩa là hệ thống sẽ tìm kiếm chương trình trong: - -* `C:\Windows\System32` - -//// - -Sau khi kích hoạt môi trường ảo, biến `PATH` sẽ như sau: - -//// tab | Linux, macOS - -```plaintext -/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin -``` - -Nghĩa là hệ thống sẽ bắt đầu tìm kiếm chương trình trong: - -```plaintext -/home/user/code/awesome-project/.venv/bin -``` - -trước khi tìm kiếm trong các thư mục khác. - -Vì vậy, khi bạn gõ `python` trong terminal, hệ thống sẽ tìm thấy chương trình Python trong: - -```plaintext -/home/user/code/awesome-project/.venv/bin/python -``` - -và sử dụng chương trình đó. - -//// - -//// tab | Windows - -```plaintext -C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 -``` - -Nghĩa là hệ thống sẽ bắt đầu tìm kiếm chương trình trong: - -```plaintext -C:\Users\user\code\awesome-project\.venv\Scripts -``` - -trước khi tìm kiếm trong các thư mục khác. - -Vì vậy, khi bạn gõ `python` trong terminal, hệ thống sẽ tìm thấy chương trình Python trong: - -```plaintext -C:\Users\user\code\awesome-project\.venv\Scripts\python -``` - -và sử dụng chương trình đó. - -//// - -Một chi tiết quan trọng là nó sẽ đưa địa chỉ của môi trường ảo vào **đầu** của biến `PATH`. Hệ thống sẽ tìm kiếm nó **trước** khi tìm kiếm bất kỳ Python nào khác có sẵn. Vì vậy, khi bạn chạy `python`, nó sẽ sử dụng Python **từ môi trường ảo** thay vì bất kỳ Python nào khác (ví dụ, Python từ môi trường toàn cục). - -Kích hoạt một môi trường ảo cũng thay đổi một vài thứ khác, nhưng đây là một trong những điều quan trọng nhất mà nó thực hiện. - -## Kiểm tra một Môi trường ảo - -Khi bạn kiểm tra một môi trường ảo đã được kích hoạt chưa, ví dụ với: - -//// tab | Linux, macOS, Windows Bash - -
- -```console -$ which python - -/home/user/code/awesome-project/.venv/bin/python -``` - -
- -//// - -//// tab | Windows PowerShell - -
- -```console -$ Get-Command python - -C:\Users\user\code\awesome-project\.venv\Scripts\python -``` - -
- -//// - - -Điều đó có nghĩa là chương trình `python` sẽ được sử dụng là chương trình **trong môi trường ảo**. - -Bạn sử dụng `which` trên Linux và macOS và `Get-Command` trên Windows PowerShell. - -Cách hoạt động của lệnh này là nó sẽ đi và kiểm tra biến `PATH`, đi qua **mỗi đường dẫn theo thứ tự**, tìm kiếm chương trình được gọi là `python`. Khi nó tìm thấy nó, nó sẽ **hiển thị cho bạn đường dẫn** đến chương trình đó. - -Điều quan trọng nhất là khi bạn gọi `python`, đó chính là chương trình `python` được thực thi. - -Vì vậy, bạn có thể xác nhận nếu bạn đang ở trong môi trường ảo đúng. - -/// tip - -Dễ dàng kích hoạt một môi trường ảo, cài đặt Python, và sau đó **chuyển đến một dự án khác**. - -Và dự án thứ hai **sẽ không hoạt động** vì bạn đang sử dụng **Python không đúng**, từ một môi trường ảo cho một dự án khác. - -Thật tiện lợi khi có thể kiểm tra `python` nào đang được sử dụng 🤓 - -/// - -## Tại sao lại Huỷ kích hoạt một Môi trường ảo - -Ví dụ, bạn có thể làm việc trên một dự án `philosophers-stone`, **kích hoạt môi trường ảo**, cài đặt các gói và làm việc với môi trường ảo đó. - -Sau đó, bạn muốn làm việc trên **dự án khác** `prisoner-of-azkaban`. - -Bạn đi đến dự án đó: - -
- -```console -$ cd ~/code/prisoner-of-azkaban -``` - -
- -Nếu bạn không tắt môi trường ảo cho `philosophers-stone`, khi bạn chạy `python` trong terminal, nó sẽ cố gắng sử dụng Python từ `philosophers-stone`. - -
- -```console -$ cd ~/code/prisoner-of-azkaban - -$ python main.py - -// Lỗi khi import sirius, nó không được cài đặt 😱 -Traceback (most recent call last): - File "main.py", line 1, in - import sirius -``` - -
- -Nếu bạn huỷ kích hoạt môi trường ảo hiện tại và kích hoạt môi trường ảo mới cho `prisoner-of-azkaban`, khi bạn chạy `python`, nó sẽ sử dụng Python từ môi trường ảo trong `prisoner-of-azkaban`. - -
- -```console -$ cd ~/code/prisoner-of-azkaban - -// Bạn không cần phải ở trong thư mục trước để huỷ kích hoạt, bạn có thể làm điều đó ở bất kỳ đâu, ngay cả sau khi đi đến dự án khác 😎 -$ deactivate - -// Kích hoạt môi trường ảo trong prisoner-of-azkaban/.venv 🚀 -$ source .venv/bin/activate - -// Bây giờ khi bạn chạy python, nó sẽ tìm thấy gói sirius được cài đặt trong môi trường ảo này ✨ -$ python main.py - -I solemnly swear 🐺 - -(Tôi long trọng thề 🐺 - câu này được lấy từ Harry Potter, chú thích của người dịch) -``` - -
- -## Các cách làm tương tự - -Đây là một hướng dẫn đơn giản để bạn có thể bắt đầu và hiểu cách mọi thứ hoạt động **bên trong**. - -Có nhiều **cách khác nhau** để quản lí các môi trường ảo, các gói phụ thuộc (requirements), và các dự án. - -Một khi bạn đã sẵn sàng và muốn sử dụng một công cụ để **quản lí cả dự án**, các gói phụ thuộc, các môi trường ảo, v.v. Tôi sẽ khuyên bạn nên thử uv. - -`uv` có thể làm nhiều thứ, chẳng hạn: - -* **Cài đặt Python** cho bạn, bao gồm nhiều phiên bản khác nhau -* Quản lí **các môi trường ảo** cho các dự án của bạn -* Cài đặt **các gói (packages)** -* Quản lí **các thành phần phụ thuộc và phiên bản** của các gói cho dự án của bạn -* Đảm bảo rằng bạn có một **tập hợp chính xác** các gói và phiên bản để cài đặt, bao gồm các thành phần phụ thuộc của chúng, để bạn có thể đảm bảo rằng bạn có thể chạy dự án của bạn trong sản xuất chính xác như trong máy tính của bạn trong khi phát triển, điều này được gọi là **locking** -* Và còn nhiều thứ khác nữa - -## Kết luận - -Nếu bạn đã đọc và hiểu hết những điều này, khá chắc là bây giờ bạn đã **biết nhiều hơn** về môi trường ảo so với kha khá lập trình viên khác đấy. 🤓 - -Những hiểu biết chi tiết này có thể sẽ hữu ích với bạn trong tương lai khi mà bạn cần gỡ lỗi một vài thứ phức tạp, và bạn đã có những hiểu biết về **ngọn ngành gốc rễ cách nó hoạt động**. 😎 diff --git a/docs/vi/mkdocs.yml b/docs/vi/mkdocs.yml deleted file mode 100644 index de18856f44..0000000000 --- a/docs/vi/mkdocs.yml +++ /dev/null @@ -1 +0,0 @@ -INHERIT: ../en/mkdocs.yml From 272204c0c77c18723230f6204cab50239faedc16 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Dec 2025 21:05:45 +0000 Subject: [PATCH 091/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f90d191361..5270d91f7b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🔥 Remove inactive/scarce translations to Vietnamese. PR [#14543](https://github.com/fastapi/fastapi/pull/14543) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove inactive/scarce translations to Persian. PR [#14542](https://github.com/fastapi/fastapi/pull/14542) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove translation to emoji to simplify the new setup with LLM autotranslations. PR [#14541](https://github.com/fastapi/fastapi/pull/14541) by [@tiangolo](https://github.com/tiangolo). * 🌐 Update translations for pt (update-outdated). PR [#14537](https://github.com/fastapi/fastapi/pull/14537) by [@tiangolo](https://github.com/tiangolo). From 353b8b29faf5dc0664c7c8ec736d2e5ee6d651d1 Mon Sep 17 00:00:00 2001 From: Nils-Hero Lindemann Date: Wed, 17 Dec 2025 08:17:04 +0100 Subject: [PATCH 092/140] =?UTF-8?q?=F0=9F=8C=90=20Sync=20German=20docs=20(?= =?UTF-8?q?#14519)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync with #14505 (plus some more) I actually searched for those patterns in VS Code. Always: files to exclude: README.md, SECURITY.md, llm-prompt.md This search & replace cleans up the hash parts everywhere under docs/. It only finds the four occurences in the two _llm-test.md under docs/de/ and docs/pt/ files to include: docs/** Search regex: \{[^\S\n]*#([^\S\n]*[a-z0-9]+(?:-[a-z0-9]+)*)[^\S\n]*\} Replace with: { #$1 } This search finds headings without hash parts. It finds two headings in docs\de\docs\index.md (those which #14505 fixes) and all headings in docs\pt\docs\tutorial\security\index.md, which I also fixed on the way as that document seems to be in sync. docs\pt\docs\index.md is not in Sync, so I didnt touch it. files to include: docs/de/**, docs/pt/** Search regex: ^(#+)[^\S\n]*([^{}#\s]+(?:[^\S\n]+[^{}\s]+)*)[^\S\n]*(?=\n) (added the missing hash parts) --- docs/de/docs/_llm-test.md | 4 ++-- docs/de/docs/index.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/de/docs/_llm-test.md b/docs/de/docs/_llm-test.md index 3a95f42e8c..bc7ce363c7 100644 --- a/docs/de/docs/_llm-test.md +++ b/docs/de/docs/_llm-test.md @@ -15,7 +15,7 @@ So verwenden: Die Tests: -## Codeschnipsel { #code-snippets} +## Codeschnipsel { #code-snippets } //// tab | Test @@ -53,7 +53,7 @@ Siehe zum Beispiel den Abschnitt `### Quotes` in `docs/de/llm-prompt.md`. //// -## Anführungszeichen in Codeschnipseln { #quotes-in-code-snippets} +## Anführungszeichen in Codeschnipseln { #quotes-in-code-snippets } //// tab | Test diff --git a/docs/de/docs/index.md b/docs/de/docs/index.md index efa34652c8..1920df8ff3 100644 --- a/docs/de/docs/index.md +++ b/docs/de/docs/index.md @@ -52,13 +52,13 @@ Seine Schlüssel-Merkmale sind: -### Keystone-Sponsor +### Keystone-Sponsor { #keystone-sponsor } {% for sponsor in sponsors.keystone -%} {% endfor -%} -### Gold- und Silber-Sponsoren +### Gold- und Silber-Sponsoren { #gold-and-silver-sponsors } {% for sponsor in sponsors.gold -%} From e675b25c601d31921c806bf67a41854101eb84ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 07:17:24 +0000 Subject: [PATCH 093/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5270d91f7b..ce69bf9163 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Sync German docs. PR [#14519](https://github.com/fastapi/fastapi/pull/14519) by [@nilslindemann](https://github.com/nilslindemann). * 🔥 Remove inactive/scarce translations to Vietnamese. PR [#14543](https://github.com/fastapi/fastapi/pull/14543) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove inactive/scarce translations to Persian. PR [#14542](https://github.com/fastapi/fastapi/pull/14542) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove translation to emoji to simplify the new setup with LLM autotranslations. PR [#14541](https://github.com/fastapi/fastapi/pull/14541) by [@tiangolo](https://github.com/tiangolo). From 6a8a2d62c4b482c21f9b461f0ac698bf7418f81e Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:15:01 +0100 Subject: [PATCH 094/140] =?UTF-8?q?=F0=9F=8C=90=20Sync=20Spanish=20docs=20?= =?UTF-8?q?(outdated=20pages=20found=20with=20script)=20(#14553)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sebastián Ramírez --- docs/es/docs/advanced/behind-a-proxy.md | 4 +- docs/es/docs/advanced/custom-response.md | 2 +- docs/es/docs/advanced/events.md | 24 +++---- docs/es/docs/advanced/middleware.md | 17 ++--- docs/es/docs/advanced/response-cookies.md | 8 +-- docs/es/docs/advanced/response-headers.md | 12 ++-- .../docs/advanced/security/oauth2-scopes.md | 40 +++++------ docs/es/docs/advanced/templates.md | 16 ++--- docs/es/docs/advanced/testing-websockets.md | 4 +- .../docs/advanced/using-request-directly.md | 8 +-- docs/es/docs/advanced/websockets.md | 24 +++---- docs/es/docs/alternatives.md | 52 +++++++------- docs/es/docs/async.md | 54 +++++++------- docs/es/docs/deployment/manually.md | 72 ++++++++----------- docs/es/docs/fastapi-cli.md | 6 +- docs/es/docs/features.md | 40 +++++------ docs/es/docs/help-fastapi.md | 65 +++++++---------- docs/es/docs/history-design-future.md | 16 ++--- docs/es/docs/project-generation.md | 2 +- docs/es/docs/tutorial/background-tasks.md | 16 ++--- docs/es/docs/tutorial/middleware.md | 35 +++++++-- .../path-params-numeric-validations.md | 26 +++---- docs/es/docs/tutorial/path-params.md | 46 ++++++------ docs/es/docs/tutorial/query-params.md | 16 +++-- docs/es/docs/tutorial/security/index.md | 16 ++--- docs/es/docs/tutorial/static-files.md | 10 +-- 26 files changed, 316 insertions(+), 315 deletions(-) diff --git a/docs/es/docs/advanced/behind-a-proxy.md b/docs/es/docs/advanced/behind-a-proxy.md index 22f51a8fa2..407a35d2dd 100644 --- a/docs/es/docs/advanced/behind-a-proxy.md +++ b/docs/es/docs/advanced/behind-a-proxy.md @@ -414,11 +414,11 @@ Generará un esquema de OpenAPI como: }, { "url": "https://stag.example.com", - "description": "Entorno de pruebas" + "description": "Staging environment" }, { "url": "https://prod.example.com", - "description": "Entorno de producción" + "description": "Production environment" } ], "paths": { diff --git a/docs/es/docs/advanced/custom-response.md b/docs/es/docs/advanced/custom-response.md index 6ecbbd5032..ace26b3e04 100644 --- a/docs/es/docs/advanced/custom-response.md +++ b/docs/es/docs/advanced/custom-response.md @@ -220,7 +220,7 @@ Toma un generador `async` o un generador/iterador normal y transmite el cuerpo d #### Usando `StreamingResponse` con objetos similares a archivos { #using-streamingresponse-with-file-like-objects } -Si tienes un objeto similar a un archivo (por ejemplo, el objeto devuelto por `open()`), puedes crear una función generadora para iterar sobre ese objeto similar a un archivo. +Si tienes un objeto similar a un archivo (por ejemplo, el objeto devuelto por `open()`), puedes crear una función generadora para iterar sobre ese objeto similar a un archivo. De esa manera, no tienes que leerlo todo primero en memoria, y puedes pasar esa función generadora al `StreamingResponse`, y devolverlo. diff --git a/docs/es/docs/advanced/events.md b/docs/es/docs/advanced/events.md index a33b517910..bf06e715a7 100644 --- a/docs/es/docs/advanced/events.md +++ b/docs/es/docs/advanced/events.md @@ -1,4 +1,4 @@ -# Eventos de Lifespan +# Eventos de Lifespan { #lifespan-events } Puedes definir lógica (código) que debería ser ejecutada antes de que la aplicación **inicie**. Esto significa que este código será ejecutado **una vez**, **antes** de que la aplicación **comience a recibir requests**. @@ -8,7 +8,7 @@ Debido a que este código se ejecuta antes de que la aplicación **comience** a Esto puede ser muy útil para configurar **recursos** que necesitas usar para toda la app, y que son **compartidos** entre requests, y/o que necesitas **limpiar** después. Por ejemplo, un pool de conexiones a una base de datos, o cargando un modelo de machine learning compartido. -## Caso de Uso +## Caso de Uso { #use-case } Empecemos con un ejemplo de **caso de uso** y luego veamos cómo resolverlo con esto. @@ -22,7 +22,7 @@ Podrías cargarlo en el nivel superior del módulo/archivo, pero eso también si Eso es lo que resolveremos, vamos a cargar el modelo antes de que los requests sean manejados, pero solo justo antes de que la aplicación comience a recibir requests, no mientras el código se está cargando. -## Lifespan +## Lifespan { #lifespan } Puedes definir esta lógica de *startup* y *shutdown* usando el parámetro `lifespan` de la app de `FastAPI`, y un "context manager" (te mostraré lo que es en un momento). @@ -44,7 +44,7 @@ Quizás necesites iniciar una nueva versión, o simplemente te cansaste de ejecu /// -### Función de Lifespan +### Función de Lifespan { #lifespan-function } Lo primero que hay que notar es que estamos definiendo una función asíncrona con `yield`. Esto es muy similar a las Dependencias con `yield`. @@ -54,7 +54,7 @@ La primera parte de la función, antes del `yield`, será ejecutada **antes** de Y la parte después del `yield` será ejecutada **después** de que la aplicación haya terminado. -### Async Context Manager +### Async Context Manager { #async-context-manager } Si revisas, la función está decorada con un `@asynccontextmanager`. @@ -62,7 +62,7 @@ Eso convierte a la función en algo llamado un "**async context manager**". {* ../../docs_src/events/tutorial003.py hl[1,13] *} -Un **context manager** en Python es algo que puedes usar en una declaración `with`, por ejemplo, `open()` puede ser usado como un context manager: +Un **context manager** en Python es algo que puedes usar en un statement `with`, por ejemplo, `open()` puede ser usado como un context manager: ```Python with open("file.txt") as file: @@ -84,7 +84,7 @@ El parámetro `lifespan` de la app de `FastAPI` toma un **async context manager* {* ../../docs_src/events/tutorial003.py hl[22] *} -## Eventos Alternativos (obsoleto) +## Eventos Alternativos (obsoleto) { #alternative-events-deprecated } /// warning | Advertencia @@ -100,7 +100,7 @@ Puedes definir manejadores de eventos (funciones) que necesitan ser ejecutadas a Estas funciones pueden ser declaradas con `async def` o `def` normal. -### Evento `startup` +### Evento `startup` { #startup-event } Para añadir una función que debería ejecutarse antes de que la aplicación inicie, declárala con el evento `"startup"`: @@ -112,7 +112,7 @@ Puedes añadir más de un manejador de eventos. Y tu aplicación no comenzará a recibir requests hasta que todos los manejadores de eventos `startup` hayan completado. -### Evento `shutdown` +### Evento `shutdown` { #shutdown-event } Para añadir una función que debería ejecutarse cuando la aplicación se esté cerrando, declárala con el evento `"shutdown"`: @@ -138,7 +138,7 @@ Por eso, declaramos la función manejadora del evento con `def` estándar en vez /// -### `startup` y `shutdown` juntos +### `startup` y `shutdown` juntos { #startup-and-shutdown-together } Hay una gran posibilidad de que la lógica para tu *startup* y *shutdown* esté conectada, podrías querer iniciar algo y luego finalizarlo, adquirir un recurso y luego liberarlo, etc. @@ -146,7 +146,7 @@ Hacer eso en funciones separadas que no comparten lógica o variables juntas es Debido a eso, ahora se recomienda en su lugar usar el `lifespan` como se explicó arriba. -## Detalles Técnicos +## Detalles Técnicos { #technical-details } Solo un detalle técnico para los nerds curiosos. 🤓 @@ -160,6 +160,6 @@ Incluyendo cómo manejar el estado de lifespan que puede ser usado en otras áre /// -## Sub Aplicaciones +## Sub Aplicaciones { #sub-applications } 🚨 Ten en cuenta que estos eventos de lifespan (startup y shutdown) solo serán ejecutados para la aplicación principal, no para [Sub Aplicaciones - Mounts](sub-applications.md){.internal-link target=_blank}. diff --git a/docs/es/docs/advanced/middleware.md b/docs/es/docs/advanced/middleware.md index 0c8c44b882..4783612a4b 100644 --- a/docs/es/docs/advanced/middleware.md +++ b/docs/es/docs/advanced/middleware.md @@ -1,4 +1,4 @@ -# Middleware Avanzado +# Middleware Avanzado { #advanced-middleware } En el tutorial principal leíste cómo agregar [Middleware Personalizado](../tutorial/middleware.md){.internal-link target=_blank} a tu aplicación. @@ -6,9 +6,9 @@ Y luego también leíste cómo manejar [CORS con el `CORSMiddleware`](../tutoria En esta sección veremos cómo usar otros middlewares. -## Agregando middlewares ASGI +## Agregando middlewares ASGI { #adding-asgi-middlewares } -Como **FastAPI** está basado en Starlette e implementa la especificación ASGI, puedes usar cualquier middleware ASGI. +Como **FastAPI** está basado en Starlette e implementa la especificación ASGI, puedes usar cualquier middleware ASGI. Un middleware no tiene que estar hecho para FastAPI o Starlette para funcionar, siempre que siga la especificación ASGI. @@ -39,7 +39,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") `app.add_middleware()` recibe una clase de middleware como primer argumento y cualquier argumento adicional que se le quiera pasar al middleware. -## Middlewares integrados +## Middlewares integrados { #integrated-middlewares } **FastAPI** incluye varios middlewares para casos de uso común, veremos a continuación cómo usarlos. @@ -51,7 +51,7 @@ Para los próximos ejemplos, también podrías usar `from starlette.middleware.s /// -## `HTTPSRedirectMiddleware` +## `HTTPSRedirectMiddleware` { #httpsredirectmiddleware } Impone que todas las requests entrantes deben ser `https` o `wss`. @@ -59,7 +59,7 @@ Cualquier request entrante a `http` o `ws` será redirigida al esquema seguro. {* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} -## `TrustedHostMiddleware` +## `TrustedHostMiddleware` { #trustedhostmiddleware } Impone que todas las requests entrantes tengan correctamente configurado el header `Host`, para proteger contra ataques de HTTP Host Header. @@ -68,10 +68,11 @@ Impone que todas las requests entrantes tengan correctamente configurado el head Se soportan los siguientes argumentos: * `allowed_hosts` - Una list de nombres de dominio que deberían ser permitidos como nombres de host. Se soportan dominios comodín como `*.example.com` para hacer coincidir subdominios. Para permitir cualquier nombre de host, usa `allowed_hosts=["*"]` u omite el middleware. +* `www_redirect` - Si se establece en True, las requests a versiones sin www de los hosts permitidos serán redirigidas a sus equivalentes con www. Por defecto es `True`. Si una request entrante no se valida correctamente, se enviará un response `400`. -## `GZipMiddleware` +## `GZipMiddleware` { #gzipmiddleware } Maneja responses GZip para cualquier request que incluya `"gzip"` en el header `Accept-Encoding`. @@ -84,7 +85,7 @@ Se soportan los siguientes argumentos: * `minimum_size` - No comprimir con GZip responses que sean más pequeñas que este tamaño mínimo en bytes. Por defecto es `500`. * `compresslevel` - Usado durante la compresión GZip. Es un entero que varía de 1 a 9. Por defecto es `9`. Un valor más bajo resulta en una compresión más rápida pero archivos más grandes, mientras que un valor más alto resulta en una compresión más lenta pero archivos más pequeños. -## Otros middlewares +## Otros middlewares { #other-middlewares } Hay muchos otros middlewares ASGI. diff --git a/docs/es/docs/advanced/response-cookies.md b/docs/es/docs/advanced/response-cookies.md index 05b78528e0..ce2ff0281d 100644 --- a/docs/es/docs/advanced/response-cookies.md +++ b/docs/es/docs/advanced/response-cookies.md @@ -1,6 +1,6 @@ -# Cookies de Response +# Cookies de Response { #response-cookies } -## Usar un parámetro `Response` +## Usar un parámetro `Response` { #use-a-response-parameter } Puedes declarar un parámetro de tipo `Response` en tu *path operation function*. @@ -16,7 +16,7 @@ Y si declaraste un `response_model`, todavía se utilizará para filtrar y conve También puedes declarar el parámetro `Response` en las dependencias, y establecer cookies (y headers) en ellas. -## Devolver una `Response` directamente +## Devolver una `Response` directamente { #return-a-response-directly } También puedes crear cookies al devolver una `Response` directamente en tu código. @@ -36,7 +36,7 @@ Y también que no estés enviando ningún dato que debería haber sido filtrado /// -### Más información +### Más información { #more-info } /// note | Detalles Técnicos diff --git a/docs/es/docs/advanced/response-headers.md b/docs/es/docs/advanced/response-headers.md index 31a135c40f..c4fdebed42 100644 --- a/docs/es/docs/advanced/response-headers.md +++ b/docs/es/docs/advanced/response-headers.md @@ -1,8 +1,8 @@ -# Response Headers +# Headers de Response { #response-headers } -## Usa un parámetro `Response` +## Usa un parámetro `Response` { #use-a-response-parameter } -Puedes declarar un parámetro de tipo `Response` en tu *función de path operation* (como puedes hacer para cookies). +Puedes declarar un parámetro de tipo `Response` en tu *path operation function* (como puedes hacer para cookies). Y luego puedes establecer headers en ese objeto de response *temporal*. @@ -16,7 +16,7 @@ Y si declaraste un `response_model`, aún se usará para filtrar y convertir el También puedes declarar el parámetro `Response` en dependencias y establecer headers (y cookies) en ellas. -## Retorna una `Response` directamente +## Retorna una `Response` directamente { #return-a-response-directly } También puedes agregar headers cuando devuelves un `Response` directamente. @@ -34,8 +34,8 @@ Y como el `Response` se puede usar frecuentemente para establecer headers y cook /// -## Headers Personalizados +## Headers Personalizados { #custom-headers } -Ten en cuenta que los headers propietarios personalizados se pueden agregar usando el prefijo 'X-'. +Ten en cuenta que los headers propietarios personalizados se pueden agregar usando el prefijo `X-`. Pero si tienes headers personalizados que quieres que un cliente en un navegador pueda ver, necesitas agregarlos a tus configuraciones de CORS (leer más en [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md){.internal-link target=_blank}), usando el parámetro `expose_headers` documentado en la documentación CORS de Starlette. diff --git a/docs/es/docs/advanced/security/oauth2-scopes.md b/docs/es/docs/advanced/security/oauth2-scopes.md index a8d2f20b68..4e4580fde3 100644 --- a/docs/es/docs/advanced/security/oauth2-scopes.md +++ b/docs/es/docs/advanced/security/oauth2-scopes.md @@ -1,4 +1,4 @@ -# Scopes de OAuth2 +# Scopes de OAuth2 { #oauth2-scopes } Puedes usar scopes de OAuth2 directamente con **FastAPI**, están integrados para funcionar de manera fluida. @@ -26,7 +26,7 @@ Pero si sabes que lo necesitas, o tienes curiosidad, sigue leyendo. /// -## Scopes de OAuth2 y OpenAPI +## Scopes de OAuth2 y OpenAPI { #oauth2-scopes-and-openapi } La especificación de OAuth2 define "scopes" como una lista de strings separados por espacios. @@ -58,15 +58,15 @@ Para OAuth2 son solo strings. /// -## Vista global +## Vista global { #global-view } Primero, echemos un vistazo rápido a las partes que cambian desde los ejemplos en el **Tutorial - User Guide** principal para [OAuth2 con Password (y hashing), Bearer con tokens JWT](../../tutorial/security/oauth2-jwt.md){.internal-link target=_blank}. Ahora usando scopes de OAuth2: -{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:125,129:135,140,156] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,9,13,47,65,106,108:116,122:126,130:136,141,157] *} Ahora revisemos esos cambios paso a paso. -## Esquema de seguridad OAuth2 +## Esquema de seguridad OAuth2 { #oauth2-security-scheme } El primer cambio es que ahora estamos declarando el esquema de seguridad OAuth2 con dos scopes disponibles, `me` y `items`. @@ -82,7 +82,7 @@ Este es el mismo mecanismo utilizado cuando das permisos al iniciar sesión con -## Token JWT con scopes +## Token JWT con scopes { #jwt-token-with-scopes } Ahora, modifica la *path operation* del token para devolver los scopes solicitados. @@ -98,9 +98,9 @@ Pero en tu aplicación, por seguridad, deberías asegurarte de añadir solo los /// -{* ../../docs_src/security/tutorial005_an_py310.py hl[156] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[157] *} -## Declarar scopes en *path operations* y dependencias +## Declarar scopes en *path operations* y dependencias { #declare-scopes-in-path-operations-and-dependencies } Ahora declaramos que la *path operation* para `/users/me/items/` requiere el scope `items`. @@ -124,7 +124,7 @@ Lo estamos haciendo aquí para demostrar cómo **FastAPI** maneja scopes declara /// -{* ../../docs_src/security/tutorial005_an_py310.py hl[5,140,171] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[5,141,172] *} /// info | Información Técnica @@ -136,7 +136,7 @@ Pero cuando importas `Query`, `Path`, `Depends`, `Security` y otros de `fastapi` /// -## Usar `SecurityScopes` +## Usar `SecurityScopes` { #use-securityscopes } Ahora actualiza la dependencia `get_current_user`. @@ -152,7 +152,7 @@ Esta clase `SecurityScopes` es similar a `Request` (`Request` se usó para obten {* ../../docs_src/security/tutorial005_an_py310.py hl[9,106] *} -## Usar los `scopes` +## Usar los `scopes` { #use-the-scopes } El parámetro `security_scopes` será del tipo `SecurityScopes`. @@ -166,7 +166,7 @@ En esta excepción, incluimos los scopes requeridos (si los hay) como un string {* ../../docs_src/security/tutorial005_an_py310.py hl[106,108:116] *} -## Verificar el `username` y la forma de los datos +## Verificar el `username` y la forma de los datos { #verify-the-username-and-data-shape } Verificamos que obtenemos un `username`, y extraemos los scopes. @@ -180,17 +180,17 @@ En lugar de, por ejemplo, un `dict`, o algo más, ya que podría romper la aplic También verificamos que tenemos un usuario con ese username, y si no, lanzamos esa misma excepción que creamos antes. -{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:128] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[47,117:129] *} -## Verificar los `scopes` +## Verificar los `scopes` { #verify-the-scopes } Ahora verificamos que todos los scopes requeridos, por esta dependencia y todos los dependientes (incluyendo *path operations*), estén incluidos en los scopes proporcionados en el token recibido, de lo contrario, lanzamos una `HTTPException`. Para esto, usamos `security_scopes.scopes`, que contiene una `list` con todos estos scopes como `str`. -{* ../../docs_src/security/tutorial005_an_py310.py hl[129:135] *} +{* ../../docs_src/security/tutorial005_an_py310.py hl[130:136] *} -## Árbol de dependencias y scopes +## Árbol de dependencias y scopes { #dependency-tree-and-scopes } Revisemos de nuevo este árbol de dependencias y los scopes. @@ -223,7 +223,7 @@ Todo depende de los `scopes` declarados en cada *path operation* y cada dependen /// -## Más detalles sobre `SecurityScopes` +## Más detalles sobre `SecurityScopes` { #more-details-about-securityscopes } Puedes usar `SecurityScopes` en cualquier punto, y en múltiples lugares, no tiene que ser en la dependencia "raíz". @@ -233,7 +233,7 @@ Debido a que `SecurityScopes` tendrá todos los scopes declarados por dependient Serán verificados independientemente para cada *path operation*. -## Revisa +## Revisa { #check-it } Si abres la documentación de la API, puedes autenticarte y especificar qué scopes deseas autorizar. @@ -245,7 +245,7 @@ Y si seleccionas el scope `me` pero no el scope `items`, podrás acceder a `/use Eso es lo que pasaría a una aplicación de terceros que intentara acceder a una de estas *path operations* con un token proporcionado por un usuario, dependiendo de cuántos permisos el usuario otorgó a la aplicación. -## Acerca de las integraciones de terceros +## Acerca de las integraciones de terceros { #about-third-party-integrations } En este ejemplo estamos usando el flujo de OAuth2 "password". @@ -269,6 +269,6 @@ Pero al final, están implementando el mismo estándar OAuth2. **FastAPI** incluye utilidades para todos estos flujos de autenticación OAuth2 en `fastapi.security.oauth2`. -## `Security` en `dependencies` del decorador +## `Security` en `dependencies` del decorador { #security-in-decorator-dependencies } De la misma manera que puedes definir una `list` de `Depends` en el parámetro `dependencies` del decorador (como se explica en [Dependencias en decoradores de path operation](../../tutorial/dependencies/dependencies-in-path-operation-decorators.md){.internal-link target=_blank}), también podrías usar `Security` con `scopes` allí. diff --git a/docs/es/docs/advanced/templates.md b/docs/es/docs/advanced/templates.md index 1018197370..f6b7eb6368 100644 --- a/docs/es/docs/advanced/templates.md +++ b/docs/es/docs/advanced/templates.md @@ -1,4 +1,4 @@ -# Plantillas +# Plantillas { #templates } Puedes usar cualquier motor de plantillas que desees con **FastAPI**. @@ -6,7 +6,7 @@ Una elección común es Jinja2, el mismo que usa Flask y otras herramientas. Hay utilidades para configurarlo fácilmente que puedes usar directamente en tu aplicación de **FastAPI** (proporcionadas por Starlette). -## Instalar dependencias +## Instala dependencias { #install-dependencies } Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo e instalar `jinja2`: @@ -20,7 +20,7 @@ $ pip install jinja2 -## Usando `Jinja2Templates` +## Usando `Jinja2Templates` { #using-jinja2templates } * Importa `Jinja2Templates`. * Crea un objeto `templates` que puedas reutilizar más tarde. @@ -51,7 +51,7 @@ También podrías usar `from starlette.templating import Jinja2Templates`. /// -## Escribiendo plantillas +## Escribiendo plantillas { #writing-templates } Luego puedes escribir una plantilla en `templates/item.html` con, por ejemplo: @@ -59,7 +59,7 @@ Luego puedes escribir una plantilla en `templates/item.html` con, por ejemplo: {!../../docs_src/templates/templates/item.html!} ``` -### Valores de Contexto de la Plantilla +### Valores de Contexto de la Plantilla { #template-context-values } En el HTML que contiene: @@ -83,7 +83,7 @@ Por ejemplo, con un ID de `42`, esto se renderizaría como: Item ID: 42 ``` -### Argumentos de la Plantilla `url_for` +### Argumentos de la Plantilla `url_for` { #template-url-for-arguments } También puedes usar `url_for()` dentro de la plantilla, toma como argumentos los mismos que usaría tu *path operation function*. @@ -105,7 +105,7 @@ Por ejemplo, con un ID de `42`, esto se renderizaría como: ``` -## Plantillas y archivos estáticos +## Plantillas y archivos estáticos { #templates-and-static-files } También puedes usar `url_for()` dentro de la plantilla, y usarlo, por ejemplo, con los `StaticFiles` que montaste con el `name="static"`. @@ -121,6 +121,6 @@ En este ejemplo, enlazaría a un archivo CSS en `static/styles.css` con: Y porque estás usando `StaticFiles`, ese archivo CSS sería servido automáticamente por tu aplicación de **FastAPI** en la URL `/static/styles.css`. -## Más detalles +## Más detalles { #more-details } Para más detalles, incluyendo cómo testear plantillas, revisa la documentación de Starlette sobre plantillas. diff --git a/docs/es/docs/advanced/testing-websockets.md b/docs/es/docs/advanced/testing-websockets.md index 190e3a2242..092c75f571 100644 --- a/docs/es/docs/advanced/testing-websockets.md +++ b/docs/es/docs/advanced/testing-websockets.md @@ -1,4 +1,4 @@ -# Probando WebSockets +# Probando WebSockets { #testing-websockets } Puedes usar el mismo `TestClient` para probar WebSockets. @@ -8,6 +8,6 @@ Para esto, usas el `TestClient` en un statement `with`, conectándote al WebSock /// note | Nota -Para más detalles, revisa la documentación de Starlette sobre probando sesiones WebSocket. +Para más detalles, revisa la documentación de Starlette sobre probar WebSockets. /// diff --git a/docs/es/docs/advanced/using-request-directly.md b/docs/es/docs/advanced/using-request-directly.md index f61e498496..74426f302b 100644 --- a/docs/es/docs/advanced/using-request-directly.md +++ b/docs/es/docs/advanced/using-request-directly.md @@ -1,4 +1,4 @@ -# Usar el Request Directamente +# Usar el Request Directamente { #using-the-request-directly } Hasta ahora, has estado declarando las partes del request que necesitas con sus tipos. @@ -13,7 +13,7 @@ Y al hacerlo, **FastAPI** está validando esos datos, convirtiéndolos y generan Pero hay situaciones donde podrías necesitar acceder al objeto `Request` directamente. -## Detalles sobre el objeto `Request` +## Detalles sobre el objeto `Request` { #details-about-the-request-object } Como **FastAPI** es en realidad **Starlette** por debajo, con una capa de varias herramientas encima, puedes usar el objeto `Request` de Starlette directamente cuando lo necesites. @@ -23,7 +23,7 @@ Aunque cualquier otro parámetro declarado normalmente (por ejemplo, el cuerpo c Pero hay casos específicos donde es útil obtener el objeto `Request`. -## Usa el objeto `Request` directamente +## Usa el objeto `Request` directamente { #use-the-request-object-directly } Imaginemos que quieres obtener la dirección IP/host del cliente dentro de tu *path operation function*. @@ -43,7 +43,7 @@ De la misma manera, puedes declarar cualquier otro parámetro como normalmente, /// -## Documentación de `Request` +## Documentación de `Request` { #request-documentation } Puedes leer más detalles sobre el objeto `Request` en el sitio de documentación oficial de Starlette. diff --git a/docs/es/docs/advanced/websockets.md b/docs/es/docs/advanced/websockets.md index 1320f8bb79..6b70e02b1f 100644 --- a/docs/es/docs/advanced/websockets.md +++ b/docs/es/docs/advanced/websockets.md @@ -1,10 +1,10 @@ -# WebSockets +# WebSockets { #websockets } Puedes usar WebSockets con **FastAPI**. -## Instalar `WebSockets` +## Instalar `websockets` { #install-websockets } -Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo e instalar `websockets`: +Asegúrate de crear un [entorno virtual](../virtual-environments.md){.internal-link target=_blank}, activarlo e instalar `websockets` (un paquete de Python que facilita usar el protocolo "WebSocket"):
@@ -16,9 +16,9 @@ $ pip install websockets
-## Cliente WebSockets +## Cliente WebSockets { #websockets-client } -### En producción +### En producción { #in-production } En tu sistema de producción, probablemente tengas un frontend creado con un framework moderno como React, Vue.js o Angular. @@ -40,7 +40,7 @@ Pero es la forma más sencilla de enfocarse en el lado del servidor de WebSocket {* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} -## Crear un `websocket` +## Crear un `websocket` { #create-a-websocket } En tu aplicación de **FastAPI**, crea un `websocket`: @@ -54,7 +54,7 @@ También podrías usar `from starlette.websockets import WebSocket`. /// -## Esperar mensajes y enviar mensajes +## Esperar mensajes y enviar mensajes { #await-for-messages-and-send-messages } En tu ruta de WebSocket puedes `await` para recibir mensajes y enviar mensajes. @@ -62,7 +62,7 @@ En tu ruta de WebSocket puedes `await` para recibir mensajes y enviar mensajes. Puedes recibir y enviar datos binarios, de texto y JSON. -## Pruébalo +## Pruébalo { #try-it } Si tu archivo se llama `main.py`, ejecuta tu aplicación con: @@ -96,7 +96,7 @@ Puedes enviar (y recibir) muchos mensajes: Y todos usarán la misma conexión WebSocket. -## Usando `Depends` y otros +## Usando `Depends` y otros { #using-depends-and-others } En endpoints de WebSocket puedes importar desde `fastapi` y usar: @@ -119,7 +119,7 @@ Puedes usar un código de cierre de los -## Manejar desconexiones y múltiples clientes +## Manejar desconexiones y múltiples clientes { #handling-disconnections-and-multiple-clients } Cuando una conexión de WebSocket se cierra, el `await websocket.receive_text()` lanzará una excepción `WebSocketDisconnect`, que puedes capturar y manejar como en este ejemplo. @@ -178,7 +178,7 @@ Si necesitas algo fácil de integrar con FastAPI pero que sea más robusto, sopo /// -## Más información +## Más información { #more-info } Para aprender más sobre las opciones, revisa la documentación de Starlette para: diff --git a/docs/es/docs/alternatives.md b/docs/es/docs/alternatives.md index 6605b7bb08..6dd5933b03 100644 --- a/docs/es/docs/alternatives.md +++ b/docs/es/docs/alternatives.md @@ -1,8 +1,8 @@ -# Alternativas, Inspiración y Comparaciones +# Alternativas, Inspiración y Comparaciones { #alternatives-inspiration-and-comparisons } Lo que inspiró a **FastAPI**, cómo se compara con las alternativas y lo que aprendió de ellas. -## Introducción +## Introducción { #intro } **FastAPI** no existiría si no fuera por el trabajo previo de otros. @@ -12,17 +12,17 @@ He estado evitando la creación de un nuevo framework durante varios años. Prim Pero en algún punto, no hubo otra opción que crear algo que proporcionara todas estas funcionalidades, tomando las mejores ideas de herramientas previas y combinándolas de la mejor manera posible, usando funcionalidades del lenguaje que ni siquiera estaban disponibles antes (anotaciones de tipos de Python 3.6+). -## Herramientas previas +## Herramientas previas { #previous-tools } -### Django +### Django { #django } Es el framework más popular de Python y es ampliamente confiable. Se utiliza para construir sistemas como Instagram. Está relativamente acoplado con bases de datos relacionales (como MySQL o PostgreSQL), por lo que tener una base de datos NoSQL (como Couchbase, MongoDB, Cassandra, etc) como motor de almacenamiento principal no es muy fácil. -Fue creado para generar el HTML en el backend, no para crear APIs utilizadas por un frontend moderno (como React, Vue.js y Angular) o por otros sistemas (como dispositivos del IoT) comunicándose con él. +Fue creado para generar el HTML en el backend, no para crear APIs utilizadas por un frontend moderno (como React, Vue.js y Angular) o por otros sistemas (como dispositivos del IoT) comunicándose con él. -### Django REST Framework +### Django REST Framework { #django-rest-framework } El framework Django REST fue creado para ser un kit de herramientas flexible para construir APIs Web utilizando Django, mejorando sus capacidades API. @@ -42,7 +42,7 @@ Tener una interfaz de usuario web de documentación automática de APIs. /// -### Flask +### Flask { #flask } Flask es un "microframework", no incluye integraciones de bases de datos ni muchas de las cosas que vienen por defecto en Django. @@ -64,7 +64,7 @@ Tener un sistema de routing simple y fácil de usar. /// -### Requests +### Requests { #requests } **FastAPI** no es en realidad una alternativa a **Requests**. Su ámbito es muy diferente. @@ -106,7 +106,7 @@ Mira las similitudes entre `requests.get(...)` y `@app.get(...)`. /// -### Swagger / OpenAPI +### Swagger / OpenAPI { #swagger-openapi } La principal funcionalidad que quería de Django REST Framework era la documentación automática de la API. @@ -131,11 +131,11 @@ Estas dos fueron elegidas por ser bastante populares y estables, pero haciendo u /// -### Frameworks REST para Flask +### Frameworks REST para Flask { #flask-rest-frameworks } Existen varios frameworks REST para Flask, pero después de invertir tiempo y trabajo investigándolos, encontré que muchos son descontinuados o abandonados, con varios problemas existentes que los hacían inadecuados. -### Marshmallow +### Marshmallow { #marshmallow } Una de las principales funcionalidades necesitadas por los sistemas API es la "serialización" de datos, que consiste en tomar datos del código (Python) y convertirlos en algo que pueda ser enviado a través de la red. Por ejemplo, convertir un objeto que contiene datos de una base de datos en un objeto JSON. Convertir objetos `datetime` en strings, etc. @@ -153,7 +153,7 @@ Usar código para definir "esquemas" que proporcionen tipos de datos y validaci /// -### Webargs +### Webargs { #webargs } Otra gran funcionalidad requerida por las APIs es el parse de datos de las requests entrantes. @@ -175,7 +175,7 @@ Tener validación automática de datos entrantes en una request. /// -### APISpec +### APISpec { #apispec } Marshmallow y Webargs proporcionan validación, parse y serialización como plug-ins. @@ -205,7 +205,7 @@ Soportar el estándar abierto para APIs, OpenAPI. /// -### Flask-apispec +### Flask-apispec { #flask-apispec } Es un plug-in de Flask, que conecta juntos Webargs, Marshmallow y APISpec. @@ -237,7 +237,7 @@ Generar el esquema OpenAPI automáticamente, desde el mismo código que define l /// -### NestJS (y Angular) +### NestJS (y Angular) { #nestjs-and-angular } Esto ni siquiera es Python, NestJS es un framework de JavaScript (TypeScript) NodeJS inspirado por Angular. @@ -259,7 +259,7 @@ Tener un poderoso sistema de inyección de dependencias. Encontrar una forma de /// -### Sanic +### Sanic { #sanic } Fue uno de los primeros frameworks de Python extremadamente rápidos basados en `asyncio`. Fue hecho para ser muy similar a Flask. @@ -279,7 +279,7 @@ Por eso **FastAPI** se basa en Starlette, ya que es el framework más rápido di /// -### Falcon +### Falcon { #falcon } Falcon es otro framework de Python de alto rendimiento, está diseñado para ser minimalista y funcionar como la base de otros frameworks como Hug. @@ -297,7 +297,7 @@ Aunque en FastAPI es opcional, y se utiliza principalmente para configurar heade /// -### Molten +### Molten { #molten } Descubrí Molten en las primeras etapas de construcción de **FastAPI**. Y tiene ideas bastante similares: @@ -321,7 +321,7 @@ Esto en realidad inspiró la actualización de partes de Pydantic, para soportar /// -### Hug +### Hug { #hug } Hug fue uno de los primeros frameworks en implementar la declaración de tipos de parámetros API usando las anotaciones de tipos de Python. Esta fue una gran idea que inspiró a otras herramientas a hacer lo mismo. @@ -351,7 +351,7 @@ Hug inspiró a **FastAPI** a declarar un parámetro `response` en funciones para /// -### APIStar (<= 0.5) +### APIStar (<= 0.5) { #apistar-0-5 } Justo antes de decidir construir **FastAPI** encontré **APIStar** server. Tenía casi todo lo que estaba buscando y tenía un gran diseño. @@ -399,9 +399,9 @@ Considero a **FastAPI** un "sucesor espiritual" de APIStar, mientras mejora y au /// -## Usado por **FastAPI** +## Usado por **FastAPI** { #used-by-fastapi } -### Pydantic +### Pydantic { #pydantic } Pydantic es un paquete para definir validación de datos, serialización y documentación (usando JSON Schema) basándose en las anotaciones de tipos de Python. @@ -417,9 +417,9 @@ Manejar toda la validación de datos, serialización de datos y documentación a /// -### Starlette +### Starlette { #starlette } -Starlette es un framework/toolkit ASGI liviano, ideal para construir servicios asyncio de alto rendimiento. +Starlette es un framework/toolkit ASGI liviano, ideal para construir servicios asyncio de alto rendimiento. Es muy simple e intuitivo. Está diseñado para ser fácilmente extensible y tener componentes modulares. @@ -462,7 +462,7 @@ Por lo tanto, cualquier cosa que puedas hacer con Starlette, puedes hacerlo dire /// -### Uvicorn +### Uvicorn { #uvicorn } Uvicorn es un servidor ASGI extremadamente rápido, construido sobre uvloop y httptools. @@ -480,6 +480,6 @@ Revisa más detalles en la sección [Despliegue](deployment/index.md){.internal- /// -## Benchmarks y velocidad +## Benchmarks y velocidad { #benchmarks-and-speed } Para entender, comparar, y ver la diferencia entre Uvicorn, Starlette y FastAPI, revisa la sección sobre [Benchmarks](benchmarks.md){.internal-link target=_blank}. diff --git a/docs/es/docs/async.md b/docs/es/docs/async.md index 92a46ba9b5..a1c8f0fb9d 100644 --- a/docs/es/docs/async.md +++ b/docs/es/docs/async.md @@ -1,8 +1,8 @@ -# Concurrencia y async / await +# Concurrencia y async / await { #concurrency-and-async-await } Detalles sobre la sintaxis `async def` para *path operation functions* y algunos antecedentes sobre el código asíncrono, la concurrencia y el paralelismo. -## ¿Con prisa? +## ¿Con prisa? { #in-a-hurry } TL;DR: @@ -40,7 +40,7 @@ def results(): --- -Si tu aplicación (de alguna manera) no tiene que comunicarse con nada más y esperar a que responda, usa `async def`. +Si tu aplicación (de alguna manera) no tiene que comunicarse con nada más y esperar a que responda, usa `async def`, incluso si no necesitas usar `await` dentro. --- @@ -54,7 +54,7 @@ De todos modos, en cualquiera de los casos anteriores, FastAPI seguirá funciona Pero al seguir los pasos anteriores, podrá hacer algunas optimizaciones de rendimiento. -## Detalles Técnicos +## Detalles Técnicos { #technical-details } Las versiones modernas de Python tienen soporte para **"código asíncrono"** utilizando algo llamado **"coroutines"**, con la sintaxis **`async` y `await`**. @@ -64,7 +64,7 @@ Veamos esa frase por partes en las secciones a continuación: * **`async` y `await`** * **Coroutines** -## Código Asíncrono +## Código Asíncrono { #asynchronous-code } El código asíncrono simplemente significa que el lenguaje 💬 tiene una forma de decirle a la computadora / programa 🤖 que en algún momento del código, tendrá que esperar que *otra cosa* termine en otro lugar. Digamos que esa *otra cosa* se llama "archivo-lento" 📝. @@ -74,7 +74,7 @@ Luego la computadora / programa 🤖 volverá cada vez que tenga una oportunidad Después, 🤖 toma la primera tarea que termine (digamos, nuestro "archivo-lento" 📝) y continúa con lo que tenía que hacer con ella. -Ese "esperar otra cosa" normalmente se refiere a las operaciones de I/O que son relativamente "lentas" (comparadas con la velocidad del procesador y la memoria RAM), como esperar: +Ese "esperar otra cosa" normalmente se refiere a las operaciones de I/O que son relativamente "lentas" (comparadas con la velocidad del procesador y la memoria RAM), como esperar: * que los datos del cliente se envíen a través de la red * que los datos enviados por tu programa sean recibidos por el cliente a través de la red @@ -85,7 +85,7 @@ Ese "esperar otra cosa" normalmente se refiere a las operaciones de I/O, las llaman operaciones "I/O bound". +Como el tiempo de ejecución se consume principalmente esperando operaciones de I/O, las llaman operaciones "I/O bound". Se llama "asíncrono" porque la computadora / programa no tiene que estar "sincronizado" con la tarea lenta, esperando el momento exacto en que la tarea termine, sin hacer nada, para poder tomar el resultado de la tarea y continuar el trabajo. @@ -93,7 +93,7 @@ En lugar de eso, al ser un sistema "asíncrono", una vez terminado, la tarea pue Para el "sincrónico" (contrario al "asíncrono") comúnmente también usan el término "secuencial", porque la computadora / programa sigue todos los pasos en secuencia antes de cambiar a una tarea diferente, incluso si esos pasos implican esperar. -### Concurrencia y Hamburguesas +### Concurrencia y Hamburguesas { #concurrency-and-burgers } Esta idea de código **asíncrono** descrita anteriormente a veces también se llama **"concurrencia"**. Es diferente del **"paralelismo"**. @@ -103,7 +103,7 @@ Pero los detalles entre *concurrencia* y *paralelismo* son bastante diferentes. Para ver la diferencia, imagina la siguiente historia sobre hamburguesas: -### Hamburguesas Concurrentes +### Hamburguesas Concurrentes { #concurrent-burgers } Vas con tu crush a conseguir comida rápida, te pones en fila mientras el cajero toma los pedidos de las personas frente a ti. 😍 @@ -163,7 +163,7 @@ Así que esperas a que tu crush termine la historia (termine el trabajo ⏯ / ta Luego vas al mostrador 🔀, a la tarea inicial que ahora está terminada ⏯, recoges las hamburguesas, das las gracias y las llevas a la mesa. Eso termina ese paso / tarea de interacción con el mostrador ⏹. Eso a su vez, crea una nueva tarea, de "comer hamburguesas" 🔀 ⏯, pero la anterior de "obtener hamburguesas" ha terminado ⏹. -### Hamburguesas Paralelas +### Hamburguesas Paralelas { #parallel-burgers } Ahora imaginemos que estas no son "Hamburguesas Concurrentes", sino "Hamburguesas Paralelas". @@ -233,7 +233,7 @@ Y tienes que esperar 🕙 en la fila por mucho tiempo o pierdes tu turno. Probablemente no querrías llevar a tu crush 😍 contigo a hacer trámites en el banco 🏦. -### Conclusión de las Hamburguesas +### Conclusión de las Hamburguesas { #burger-conclusion } En este escenario de "hamburguesas de comida rápida con tu crush", como hay mucha espera 🕙, tiene mucho más sentido tener un sistema concurrente ⏸🔀⏯. @@ -253,7 +253,7 @@ Y ese es el mismo nivel de rendimiento que obtienes con **FastAPI**. Y como puedes tener paralelismo y asincronía al mismo tiempo, obtienes un mayor rendimiento que la mayoría de los frameworks de NodeJS probados y a la par con Go, que es un lenguaje compilado más cercano a C (todo gracias a Starlette). -### ¿Es la concurrencia mejor que el paralelismo? +### ¿Es la concurrencia mejor que el paralelismo? { #is-concurrency-better-than-parallelism } ¡No! Esa no es la moraleja de la historia. @@ -277,7 +277,7 @@ Pero en este caso, si pudieras traer a los 8 ex-cajeros/cocineros/ahora-limpiado En este escenario, cada uno de los limpiadores (incluyéndote) sería un procesador, haciendo su parte del trabajo. -Y como la mayor parte del tiempo de ejecución se dedica al trabajo real (en lugar de esperar), y el trabajo en una computadora lo realiza una CPU, llaman a estos problemas "CPU bound". +Y como la mayor parte del tiempo de ejecución se dedica al trabajo real (en lugar de esperar), y el trabajo en una computadora lo realiza una CPU, llaman a estos problemas "CPU bound". --- @@ -290,7 +290,7 @@ Por ejemplo: * **Machine Learning**: normalmente requiere muchas multiplicaciones de "matrices" y "vectores". Piensa en una enorme hoja de cálculo con números y multiplicando todos juntos al mismo tiempo. * **Deep Learning**: este es un subcampo de Machine Learning, por lo tanto, se aplica lo mismo. Es solo que no hay una sola hoja de cálculo de números para multiplicar, sino un enorme conjunto de ellas, y en muchos casos, usas un procesador especial para construir y / o usar esos modelos. -### Concurrencia + Paralelismo: Web + Machine Learning +### Concurrencia + Paralelismo: Web + Machine Learning { #concurrency-parallelism-web-machine-learning } Con **FastAPI** puedes aprovechar la concurrencia que es muy común para el desarrollo web (la misma atracción principal de NodeJS). @@ -300,7 +300,7 @@ Eso, más el simple hecho de que Python es el lenguaje principal para **Data Sci Para ver cómo lograr este paralelismo en producción, consulta la sección sobre [Deployment](deployment/index.md){.internal-link target=_blank}. -## `async` y `await` +## `async` y `await` { #async-and-await } Las versiones modernas de Python tienen una forma muy intuitiva de definir código asíncrono. Esto hace que se vea igual que el código "secuencial" normal y hace el "wait" por ti en los momentos adecuados. @@ -349,7 +349,7 @@ async def read_burgers(): return burgers ``` -### Más detalles técnicos +### Más detalles técnicos { #more-technical-details } Podrías haber notado que `await` solo se puede usar dentro de funciones definidas con `async def`. @@ -361,9 +361,9 @@ Si estás trabajando con **FastAPI** no tienes que preocuparte por eso, porque e Pero si deseas usar `async` / `await` sin FastAPI, también puedes hacerlo. -### Escribe tu propio código async +### Escribe tu propio código async { #write-your-own-async-code } -Starlette (y **FastAPI**) están basados en AnyIO, lo que lo hace compatible tanto con la librería estándar de Python asyncio como con Trio. +Starlette (y **FastAPI**) están basados en AnyIO, lo que lo hace compatible tanto con el asyncio del paquete estándar de Python como con Trio. En particular, puedes usar directamente AnyIO para tus casos de uso avanzados de concurrencia que requieran patrones más avanzados en tu propio código. @@ -371,7 +371,7 @@ E incluso si no estuvieras usando FastAPI, también podrías escribir tus propia Creé otro paquete sobre AnyIO, como una capa delgada, para mejorar un poco las anotaciones de tipos y obtener mejor **autocompletado**, **errores en línea**, etc. También tiene una introducción amigable y tutorial para ayudarte a **entender** y escribir **tu propio código async**: Asyncer. Sería particularmente útil si necesitas **combinar código async con regular** (bloqueante/sincrónico). -### Otras formas de código asíncrono +### Otras formas de código asíncrono { #other-forms-of-asynchronous-code } Este estilo de usar `async` y `await` es relativamente nuevo en el lenguaje. @@ -385,13 +385,13 @@ En versiones previas de Python, podrías haber usado hilos o I/O
de bloqueo. +Si vienes de otro framework async que no funciona de la manera descrita anteriormente y estás acostumbrado a definir funciones de *path operation* solo de cómputo trivial con `def` normal para una pequeña ganancia de rendimiento (alrededor de 100 nanosegundos), ten en cuenta que en **FastAPI** el efecto sería bastante opuesto. En estos casos, es mejor usar `async def` a menos que tus *path operation functions* usen código que realice I/O de bloqueo. Aun así, en ambas situaciones, es probable que **FastAPI** [siga siendo más rápida](index.md#performance){.internal-link target=_blank} que (o al menos comparable a) tu framework anterior. -### Dependencias +### Dependencias { #dependencies } Lo mismo aplica para las [dependencias](tutorial/dependencies/index.md){.internal-link target=_blank}. Si una dependencia es una función estándar `def` en lugar de `async def`, se ejecuta en el threadpool externo. -### Sub-dependencias +### Sub-dependencias { #sub-dependencies } Puedes tener múltiples dependencias y [sub-dependencias](tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} requiriéndose mutuamente (como parámetros de las definiciones de funciones), algunas de ellas podrían ser creadas con `async def` y algunas con `def` normal. Aun funcionará, y las que fueron creadas con `def` normal serían llamadas en un hilo externo (del threadpool) en lugar de ser "awaited". -### Otras funciones de utilidad +### Otras funciones de utilidad { #other-utility-functions } Cualquier otra función de utilidad que llames directamente puede ser creada con `def` normal o `async def` y FastAPI no afectará la forma en que la llames. diff --git a/docs/es/docs/deployment/manually.md b/docs/es/docs/deployment/manually.md index b56cf9514a..50ba79c228 100644 --- a/docs/es/docs/deployment/manually.md +++ b/docs/es/docs/deployment/manually.md @@ -1,51 +1,39 @@ -# Ejecutar un Servidor Manualmente +# Ejecutar un Servidor Manualmente { #run-a-server-manually } -## Usa el Comando `fastapi run` +## Usa el Comando `fastapi run` { #use-the-fastapi-run-command } En resumen, usa `fastapi run` para servir tu aplicación FastAPI:
```console -$ fastapi run main.py -INFO Usando path main.py -INFO Path absoluto resuelto /home/user/code/awesomeapp/main.py -INFO Buscando una estructura de archivos de paquete desde directorios con archivos __init__.py -INFO Importando desde /home/user/code/awesomeapp +$ fastapi run main.py - ╭─ Archivo de módulo de Python ─╮ - │ │ - │ 🐍 main.py │ - │ │ - ╰──────────────────────╯ + FastAPI Starting production server 🚀 -INFO Importando módulo main -INFO Encontrada aplicación FastAPI importable + Searching for package file structure from directories + with __init__.py files + Importing from /home/user/code/awesomeapp - ╭─ Aplicación FastAPI importable ─╮ - │ │ - │ from main import app │ - │ │ - ╰──────────────────────────╯ + module 🐍 main.py -INFO Usando la cadena de import main:app + code Importing the FastAPI app object from the module with + the following code: - ╭─────────── CLI de FastAPI - Modo Producción ───────────╮ - │ │ - │ Sirviendo en: http://0.0.0.0:8000 │ - │ │ - │ Docs de API: http://0.0.0.0:8000/docs │ - │ │ - │ Corriendo en modo producción, para desarrollo usa: │ - │ │ - fastapi dev - │ │ - ╰─────────────────────────────────────────────────────╯ + from main import app -INFO: Iniciado el proceso del servidor [2306215] -INFO: Esperando el inicio de la aplicación. -INFO: Inicio de la aplicación completado. -INFO: Uvicorn corriendo en http://0.0.0.0:8000 (Presiona CTRL+C para salir) + app Using import string: main:app + + server Server started at http://0.0.0.0:8000 + server Documentation at http://0.0.0.0:8000/docs + + Logs: + + INFO Started server process [2306215] + INFO Waiting for application startup. + INFO Application startup complete. + INFO Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C + to quit) ```
@@ -54,11 +42,11 @@ Eso funcionaría para la mayoría de los casos. 😎 Podrías usar ese comando, por ejemplo, para iniciar tu app **FastAPI** en un contenedor, en un servidor, etc. -## Servidores ASGI +## Servidores ASGI { #asgi-servers } Vamos a profundizar un poquito en los detalles. -FastAPI usa un estándar para construir frameworks de web y servidores de Python llamado ASGI. FastAPI es un framework web ASGI. +FastAPI usa un estándar para construir frameworks de web y servidores de Python llamado ASGI. FastAPI es un framework web ASGI. Lo principal que necesitas para ejecutar una aplicación **FastAPI** (o cualquier otra aplicación ASGI) en una máquina de servidor remota es un programa de servidor ASGI como **Uvicorn**, que es el que viene por defecto en el comando `fastapi`. @@ -70,7 +58,7 @@ Hay varias alternativas, incluyendo: *
Granian: Un servidor HTTP Rust para aplicaciones en Python. * NGINX Unit: NGINX Unit es un runtime para aplicaciones web ligero y versátil. -## Máquina Servidor y Programa Servidor +## Máquina Servidor y Programa Servidor { #server-machine-and-server-program } Hay un pequeño detalle sobre los nombres que hay que tener en cuenta. 💡 @@ -80,7 +68,7 @@ Solo ten en cuenta que cuando leas "servidor" en general, podría referirse a un Al referirse a la máquina remota, es común llamarla **servidor**, pero también **máquina**, **VM** (máquina virtual), **nodo**. Todos esos se refieren a algún tipo de máquina remota, generalmente con Linux, donde ejecutas programas. -## Instala el Programa del Servidor +## Instala el Programa del Servidor { #install-the-server-program } Cuando instalas FastAPI, viene con un servidor de producción, Uvicorn, y puedes iniciarlo con el comando `fastapi run`. @@ -112,7 +100,7 @@ Cuando instalas FastAPI con algo como `pip install "fastapi[standard]"` ya obtie /// -## Ejecuta el Programa del Servidor +## Ejecuta el Programa del Servidor { #run-the-server-program } Si instalaste un servidor ASGI manualmente, normalmente necesitarías pasar una cadena de import en un formato especial para que importe tu aplicación FastAPI: @@ -121,7 +109,7 @@ Si instalaste un servidor ASGI manualmente, normalmente necesitarías pasar una ```console $ uvicorn main:app --host 0.0.0.0 --port 80 -INFO: Uvicorn corriendo en http://0.0.0.0:80 (Presiona CTRL+C para salir) +INFO: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit) ``` @@ -153,7 +141,7 @@ Ayuda mucho durante el **desarrollo**, pero **no** deberías usarla en **producc /// -## Conceptos de Despliegue +## Conceptos de Despliegue { #deployment-concepts } Estos ejemplos ejecutan el programa del servidor (por ejemplo, Uvicorn), iniciando **un solo proceso**, escuchando en todas las IPs (`0.0.0.0`) en un puerto predefinido (por ejemplo, `80`). diff --git a/docs/es/docs/fastapi-cli.md b/docs/es/docs/fastapi-cli.md index 34b18ee3df..7866254223 100644 --- a/docs/es/docs/fastapi-cli.md +++ b/docs/es/docs/fastapi-cli.md @@ -1,4 +1,4 @@ -# FastAPI CLI +# FastAPI CLI { #fastapi-cli } **FastAPI CLI** es un programa de línea de comandos que puedes usar para servir tu aplicación FastAPI, gestionar tu proyecto FastAPI, y más. @@ -54,13 +54,13 @@ Para producción usarías `fastapi run` en su lugar. 🚀 Internamente, **FastAPI CLI** usa Uvicorn, un servidor ASGI de alto rendimiento y listo para producción. 😎 -## `fastapi dev` +## `fastapi dev` { #fastapi-dev } Ejecutar `fastapi dev` inicia el modo de desarrollo. Por defecto, **auto-reload** está habilitado, recargando automáticamente el servidor cuando realizas cambios en tu código. Esto consume muchos recursos y podría ser menos estable que cuando está deshabilitado. Deberías usarlo solo para desarrollo. También escucha en la dirección IP `127.0.0.1`, que es la IP para que tu máquina se comunique solo consigo misma (`localhost`). -## `fastapi run` +## `fastapi run` { #fastapi-run } Ejecutar `fastapi run` inicia FastAPI en modo de producción por defecto. diff --git a/docs/es/docs/features.md b/docs/es/docs/features.md index ac73bee164..9902b21fa5 100644 --- a/docs/es/docs/features.md +++ b/docs/es/docs/features.md @@ -1,17 +1,17 @@ -# Funcionalidades +# Funcionalidades { #features } -## Funcionalidades de FastAPI +## Funcionalidades de FastAPI { #fastapi-features } **FastAPI** te ofrece lo siguiente: -### Basado en estándares abiertos +### Basado en estándares abiertos { #based-on-open-standards } * OpenAPI para la creación de APIs, incluyendo declaraciones de path operations, parámetros, request bodies, seguridad, etc. * Documentación automática de modelos de datos con JSON Schema (ya que OpenAPI en sí mismo está basado en JSON Schema). * Diseñado alrededor de estos estándares, tras un estudio meticuloso. En lugar de ser una capa adicional. * Esto también permite el uso de **generación de código cliente automática** en muchos idiomas. -### Documentación automática +### Documentación automática { #automatic-docs } Interfaces web de documentación y exploración de APIs interactivas. Como el framework está basado en OpenAPI, hay múltiples opciones, 2 incluidas por defecto. @@ -23,7 +23,7 @@ Interfaces web de documentación y exploración de APIs interactivas. Como el fr ![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) -### Solo Python moderno +### Solo Python moderno { #just-modern-python } Todo está basado en declaraciones estándar de **tipos en Python** (gracias a Pydantic). Sin nueva sintaxis que aprender. Solo Python moderno estándar. @@ -71,7 +71,7 @@ Pasa las claves y valores del dict `second_user_data` directamente como argument /// -### Soporte del editor +### Soporte del editor { #editor-support } Todo el framework fue diseñado para ser fácil e intuitivo de usar, todas las decisiones fueron probadas en múltiples editores incluso antes de comenzar el desarrollo, para asegurar la mejor experiencia de desarrollo. @@ -95,13 +95,13 @@ Obtendrás autocompletado en código que podrías considerar imposible antes. Po No más escribir nombres de claves incorrectos, yendo de un lado a otro entre la documentación, o desplazándote hacia arriba y abajo para encontrar si finalmente usaste `username` o `user_name`. -### Breve +### Breve { #short } -Tiene **valores predeterminados** sensatos para todo, con configuraciones opcionales en todas partes. Todos los parámetros se pueden ajustar finamente para hacer lo que necesitas y para definir el API que necesitas. +Tiene **valores por defecto** sensatos para todo, con configuraciones opcionales en todas partes. Todos los parámetros se pueden ajustar finamente para hacer lo que necesitas y para definir el API que necesitas. Pero por defecto, todo **"simplemente funciona"**. -### Validación +### Validación { #validation } * Validación para la mayoría (¿o todas?) de los **tipos de datos** de Python, incluyendo: * Objetos JSON (`dict`). @@ -117,7 +117,7 @@ Pero por defecto, todo **"simplemente funciona"**. Toda la validación es manejada por **Pydantic**, una herramienta bien establecida y robusta. -### Seguridad y autenticación +### Seguridad y autenticación { #security-and-authentication } Seguridad y autenticación integradas. Sin ningún compromiso con bases de datos o modelos de datos. @@ -134,30 +134,30 @@ Además de todas las características de seguridad de Starlette (incluyendo **co Todo construido como herramientas y componentes reutilizables que son fáciles de integrar con tus sistemas, almacenes de datos, bases de datos relacionales y NoSQL, etc. -### Inyección de dependencias +### Inyección de dependencias { #dependency-injection } FastAPI incluye un sistema de Inyección de Dependencias extremadamente fácil de usar, pero extremadamente potente. -* Incluso las dependencias pueden tener dependencias, creando una jerarquía o **"gráfico de dependencias"**. +* Incluso las dependencias pueden tener dependencias, creando una jerarquía o **"grafo de dependencias"**. * Todo **manejado automáticamente** por el framework. * Todas las dependencias pueden requerir datos de los requests y **aumentar las restricciones de la path operation** y la documentación automática. * **Validación automática** incluso para los parámetros de *path operation* definidos en las dependencias. * Soporte para sistemas de autenticación de usuario complejos, **conexiones a bases de datos**, etc. * **Sin compromisos** con bases de datos, frontends, etc. Pero fácil integración con todos ellos. -### "Plug-ins" ilimitados +### "Plug-ins" ilimitados { #unlimited-plug-ins } O de otra manera, no hay necesidad de ellos, importa y usa el código que necesitas. Cualquier integración está diseñada para ser tan simple de usar (con dependencias) que puedes crear un "plug-in" para tu aplicación en 2 líneas de código usando la misma estructura y sintaxis utilizada para tus *path operations*. -### Probado +### Probado { #tested } * 100% de cobertura de tests. -* Código completamente anotado con tipos. +* 100% anotada con tipos code base. * Usado en aplicaciones en producción. -## Funcionalidades de Starlette +## Funcionalidades de Starlette { #starlette-features } **FastAPI** es totalmente compatible con (y está basado en) Starlette. Así que, cualquier código adicional de Starlette que tengas, también funcionará. @@ -173,13 +173,13 @@ Con **FastAPI** obtienes todas las funcionalidades de **Starlette** (ya que Fast * **CORS**, GZip, archivos estáticos, responses en streaming. * Soporte para **Session y Cookie**. * Cobertura de tests del 100%. -* Código completamente anotado con tipos. +* code base completamente anotada con tipos. -## Funcionalidades de Pydantic +## Funcionalidades de Pydantic { #pydantic-features } **FastAPI** es totalmente compatible con (y está basado en) Pydantic. Por lo tanto, cualquier código adicional de Pydantic que tengas, también funcionará. -Incluyendo paquetes externos también basados en Pydantic, como ORMs, ODMs para bases de datos. +Incluyendo paquetes externos también basados en Pydantic, como ORMs, ODMs para bases de datos. Esto también significa que, en muchos casos, puedes pasar el mismo objeto que obtienes de un request **directamente a la base de datos**, ya que todo se valida automáticamente. @@ -190,7 +190,7 @@ Con **FastAPI** obtienes todas las funcionalidades de **Pydantic** (ya que FastA * **Sin complicaciones**: * Sin micro-lenguaje de definición de esquemas nuevo que aprender. * Si conoces los tipos en Python sabes cómo usar Pydantic. -* Se lleva bien con tu **IDE/linter/cerebro**: +* Se lleva bien con tu **IDE/linter/cerebro**: * Porque las estructuras de datos de pydantic son solo instances de clases que defines; autocompletado, linting, mypy y tu intuición deberían funcionar correctamente con tus datos validados. * Valida **estructuras complejas**: * Uso de modelos jerárquicos de Pydantic, `List` y `Dict` de `typing` de Python, etc. diff --git a/docs/es/docs/help-fastapi.md b/docs/es/docs/help-fastapi.md index fa559f490e..cef9562249 100644 --- a/docs/es/docs/help-fastapi.md +++ b/docs/es/docs/help-fastapi.md @@ -1,4 +1,4 @@ -# Ayuda a FastAPI - Consigue Ayuda +# Ayuda a FastAPI - Consigue Ayuda { #help-fastapi-get-help } ¿Te gusta **FastAPI**? @@ -10,7 +10,7 @@ Hay formas muy sencillas de ayudar (varias implican solo uno o dos clics). Y también hay varias formas de conseguir ayuda. -## Suscríbete al boletín +## Suscríbete al boletín { #subscribe-to-the-newsletter } Puedes suscribirte al (esporádico) boletín [**FastAPI and friends**](newsletter.md){.internal-link target=_blank} para mantenerte al día sobre: @@ -20,17 +20,17 @@ Puedes suscribirte al (esporádico) boletín [**FastAPI and friends**](newslette * Cambios importantes 🚨 * Consejos y trucos ✅ -## Sigue a FastAPI en X (Twitter) +## Sigue a FastAPI en X (Twitter) { #follow-fastapi-on-x-twitter } Sigue a @fastapi en **X (Twitter)** para obtener las últimas noticias sobre **FastAPI**. 🐦 -## Dale una estrella a **FastAPI** en GitHub +## Dale una estrella a **FastAPI** en GitHub { #star-fastapi-in-github } Puedes "darle una estrella" a FastAPI en GitHub (haciendo clic en el botón de estrella en la parte superior derecha): https://github.com/fastapi/fastapi. ⭐️ Al agregar una estrella, otros usuarios podrán encontrarlo más fácilmente y ver que ya ha sido útil para otros. -## Observa el repositorio de GitHub para lanzamientos +## Observa el repositorio de GitHub para lanzamientos { #watch-the-github-repository-for-releases } Puedes "observar" FastAPI en GitHub (haciendo clic en el botón "watch" en la parte superior derecha): https://github.com/fastapi/fastapi. 👀 @@ -38,7 +38,7 @@ Allí puedes seleccionar "Releases only". Al hacerlo, recibirás notificaciones (en tu email) cada vez que haya un nuevo lanzamiento (una nueva versión) de **FastAPI** con correcciones de bugs y nuevas funcionalidades. -## Conéctate con el autor +## Conéctate con el autor { #connect-with-the-author } Puedes conectar conmigo (Sebastián Ramírez / `tiangolo`), el autor. @@ -57,19 +57,19 @@ Puedes: * Leer otras ideas, artículos, y leer sobre las herramientas que he creado. * Seguirme para leer lo que publico nuevo. -## Twittea sobre **FastAPI** +## Twittea sobre **FastAPI** { #tweet-about-fastapi } Twittea sobre **FastAPI** y dime a mí y a otros por qué te gusta. 🎉 Me encanta escuchar cómo se está utilizando **FastAPI**, qué te ha gustado, en qué proyecto/empresa lo estás usando, etc. -## Vota por FastAPI +## Vota por FastAPI { #vote-for-fastapi } * Vota por **FastAPI** en Slant. * Vota por **FastAPI** en AlternativeTo. * Di que usas **FastAPI** en StackShare. -## Ayuda a otros con preguntas en GitHub +## Ayuda a otros con preguntas en GitHub { #help-others-with-questions-in-github } Puedes intentar ayudar a otros con sus preguntas en: @@ -88,7 +88,7 @@ La idea es que la comunidad de **FastAPI** sea amable y acogedora. Al mismo tiem Aquí te explico cómo ayudar a otros con preguntas (en discusiones o issues): -### Entiende la pregunta +### Entiende la pregunta { #understand-the-question } * Revisa si puedes entender cuál es el **propósito** y el caso de uso de la persona que pregunta. @@ -98,7 +98,7 @@ Aquí te explico cómo ayudar a otros con preguntas (en discusiones o issues): * Si no puedes entender la pregunta, pide más **detalles**. -### Reproduce el problema +### Reproduce el problema { #reproduce-the-problem } En la mayoría de los casos y preguntas hay algo relacionado con el **código original** de la persona. @@ -108,13 +108,13 @@ En muchos casos solo copiarán un fragmento del código, pero eso no es suficien * Si te sientes muy generoso, puedes intentar **crear un ejemplo** así tú mismo, solo basado en la descripción del problema. Solo ten en cuenta que esto podría llevar mucho tiempo y podría ser mejor pedirles que aclaren el problema primero. -### Sugerir soluciones +### Sugerir soluciones { #suggest-solutions } * Después de poder entender la pregunta, puedes darles un posible **respuesta**. * En muchos casos, es mejor entender su **problema subyacente o caso de uso**, porque podría haber una mejor manera de resolverlo que lo que están intentando hacer. -### Pide cerrar +### Pide cerrar { #ask-to-close } Si responden, hay una alta probabilidad de que hayas resuelto su problema, felicidades, ¡**eres un héroe**! 🦸 @@ -123,7 +123,7 @@ Si responden, hay una alta probabilidad de que hayas resuelto su problema, felic * En GitHub Discussions: marquen el comentario como la **respuesta**. * En GitHub Issues: **cierren** el issue. -## Observa el repositorio de GitHub +## Observa el repositorio de GitHub { #watch-the-github-repository } Puedes "observar" FastAPI en GitHub (haciendo clic en el botón "watch" en la parte superior derecha): https://github.com/fastapi/fastapi. 👀 @@ -131,7 +131,7 @@ Si seleccionas "Watching" en lugar de "Releases only", recibirás notificaciones Luego puedes intentar ayudarlos a resolver esas preguntas. -## Haz preguntas +## Haz preguntas { #ask-questions } Puedes crear una nueva pregunta en el repositorio de GitHub, por ejemplo, para: @@ -140,7 +140,7 @@ Puedes Discord 👥 y charla con otros en la comunidad de FastAPI. @@ -237,7 +237,7 @@ Usa el chat solo para otras conversaciones generales. /// -### No uses el chat para preguntas +### No uses el chat para preguntas { #dont-use-the-chat-for-questions } Ten en cuenta que dado que los chats permiten una "conversación más libre", es fácil hacer preguntas que son demasiado generales y más difíciles de responder, por lo que es posible que no recibas respuestas. @@ -247,22 +247,9 @@ Las conversaciones en los sistemas de chat tampoco son tan fácilmente buscables Por otro lado, hay miles de usuarios en los sistemas de chat, por lo que hay muchas posibilidades de que encuentres a alguien con quien hablar allí, casi todo el tiempo. 😄 -## Patrocina al autor +## Patrocina al autor { #sponsor-the-author } -También puedes apoyar financieramente al autor (a mí) a través de GitHub sponsors. - -Allí podrías comprarme un café ☕️ para decir gracias. 😄 - -Y también puedes convertirte en un sponsor de Plata o de Oro para FastAPI. 🏅🎉 - -## Patrocina las herramientas que impulsan FastAPI - -Como habrás visto en la documentación, FastAPI se apoya en los hombros de gigantes, Starlette y Pydantic. - -También puedes patrocinar: - -* Samuel Colvin (Pydantic) -* Encode (Starlette, Uvicorn) +Si tu **producto/empresa** depende de o está relacionado con **FastAPI** y quieres llegar a sus usuarios, puedes patrocinar al autor (a mí) a través de GitHub sponsors. Según el nivel, podrías obtener algunos beneficios extra, como una insignia en la documentación. 🎁 --- diff --git a/docs/es/docs/history-design-future.md b/docs/es/docs/history-design-future.md index 7ed4fdf056..79835440bb 100644 --- a/docs/es/docs/history-design-future.md +++ b/docs/es/docs/history-design-future.md @@ -1,4 +1,4 @@ -# Historia, Diseño y Futuro +# Historia, Diseño y Futuro { #history-design-and-future } Hace algún tiempo, un usuario de **FastAPI** preguntó: @@ -6,7 +6,7 @@ Hace algún tiempo, **Pydantic** por sus ventajas. @@ -60,11 +60,11 @@ Luego contribuí a este, para hacerlo totalmente compatible con JSON Schema, par Durante el desarrollo, también contribuí a **Starlette**, el otro requisito clave. -## Desarrollo +## Desarrollo { #development } Para cuando comencé a crear el propio **FastAPI**, la mayoría de las piezas ya estaban en su lugar, el diseño estaba definido, los requisitos y herramientas estaban listos, y el conocimiento sobre los estándares y especificaciones estaba claro y fresco. -## Futuro +## Futuro { #future } A este punto, ya está claro que **FastAPI** con sus ideas está siendo útil para muchas personas. diff --git a/docs/es/docs/project-generation.md b/docs/es/docs/project-generation.md index 414c498246..b4aa11d0dd 100644 --- a/docs/es/docs/project-generation.md +++ b/docs/es/docs/project-generation.md @@ -13,7 +13,7 @@ Repositorio de GitHub: `starlette.background`. @@ -71,7 +71,7 @@ Todavía es posible usar `BackgroundTask` solo en FastAPI, pero debes crear el o Puedes ver más detalles en la documentación oficial de Starlette sobre Background Tasks. -## Advertencia +## Advertencia { #caveat } Si necesitas realizar una computación intensa en segundo plano y no necesariamente necesitas que se ejecute por el mismo proceso (por ejemplo, no necesitas compartir memoria, variables, etc.), podrías beneficiarte del uso de otras herramientas más grandes como Celery. @@ -79,6 +79,6 @@ Tienden a requerir configuraciones más complejas, un gestor de cola de mensajes Pero si necesitas acceder a variables y objetos de la misma app de **FastAPI**, o necesitas realizar pequeñas tareas en segundo plano (como enviar una notificación por email), simplemente puedes usar `BackgroundTasks`. -## Resumen +## Resumen { #recap } Importa y usa `BackgroundTasks` con parámetros en *path operation functions* y dependencias para agregar tareas en segundo plano. diff --git a/docs/es/docs/tutorial/middleware.md b/docs/es/docs/tutorial/middleware.md index c42e4eaa54..0f943ec08c 100644 --- a/docs/es/docs/tutorial/middleware.md +++ b/docs/es/docs/tutorial/middleware.md @@ -1,4 +1,4 @@ -# Middleware +# Middleware { #middleware } Puedes añadir middleware a las aplicaciones de **FastAPI**. @@ -15,11 +15,11 @@ Un "middleware" es una función que trabaja con cada **request** antes de que se Si tienes dependencias con `yield`, el código de salida se ejecutará *después* del middleware. -Si hubiera alguna tarea en segundo plano (documentada más adelante), se ejecutará *después* de todo el middleware. +Si hubiera tareas en segundo plano (cubiertas en la sección [Tareas en segundo plano](background-tasks.md){.internal-link target=_blank}, lo verás más adelante), se ejecutarán *después* de todo el middleware. /// -## Crear un middleware +## Crear un middleware { #create-a-middleware } Para crear un middleware usas el decorador `@app.middleware("http")` encima de una función. @@ -35,7 +35,7 @@ La función middleware recibe: /// tip | Consejo -Ten en cuenta que los custom proprietary headers se pueden añadir usando el prefijo 'X-'. +Ten en cuenta que los custom proprietary headers se pueden añadir usando el prefijo `X-`. Pero si tienes custom headers que deseas que un cliente en un navegador pueda ver, necesitas añadirlos a tus configuraciones de CORS ([CORS (Cross-Origin Resource Sharing)](cors.md){.internal-link target=_blank}) usando el parámetro `expose_headers` documentado en la documentación de CORS de Starlette. @@ -49,7 +49,7 @@ También podrías usar `from starlette.requests import Request`. /// -### Antes y después de la `response` +### Antes y después de la `response` { #before-and-after-the-response } Puedes añadir código que se ejecute con la `request`, antes de que cualquier *path operation* la reciba. @@ -65,7 +65,30 @@ Aquí usamos gt
y no solo ge. Ya que con esto puedes requerir, por ejemplo, que un valor sea mayor que `0`, incluso si es menor que `1`. +Aquí es donde se convierte en importante poder declarar gt y no solo ge. Ya que con esto puedes requerir, por ejemplo, que un valor sea mayor que `0`, incluso si es menor que `1`. Así, `0.5` sería un valor válido. Pero `0.0` o `0` no lo serían. -Y lo mismo para lt. +Y lo mismo para lt. {* ../../docs_src/path_params_numeric_validations/tutorial006_an_py39.py hl[13] *} -## Resumen +## Resumen { #recap } Con `Query`, `Path` (y otros que aún no has visto) puedes declarar metadatos y validaciones de string de las mismas maneras que con [Parámetros de Query y Validaciones de String](query-params-str-validations.md){.internal-link target=_blank}. @@ -139,7 +139,7 @@ Todas ellas comparten los mismos parámetros para validación adicional y metada /// -/// note | Nota técnica +/// note | Detalles técnicos Cuando importas `Query`, `Path` y otros de `fastapi`, en realidad son funciones. diff --git a/docs/es/docs/tutorial/path-params.md b/docs/es/docs/tutorial/path-params.md index 4262809026..c49b31c44e 100644 --- a/docs/es/docs/tutorial/path-params.md +++ b/docs/es/docs/tutorial/path-params.md @@ -1,4 +1,4 @@ -# Parámetros de Path +# Parámetros de Path { #path-parameters } Puedes declarar "parámetros" o "variables" de path con la misma sintaxis que se usa en los format strings de Python: @@ -12,7 +12,7 @@ Así que, si ejecutas este ejemplo y vas a Conversión
de datos { #data-conversion } Si ejecutas este ejemplo y abres tu navegador en http://127.0.0.1:8000/items/3, verás un response de: @@ -38,11 +38,11 @@ Si ejecutas este ejemplo y abres tu navegador en "parsing"
automático de requests. +Entonces, con esa declaración de tipo, **FastAPI** te ofrece "parsing" automático de request. /// -## Validación de datos +## Validación de datos { #data-validation } Pero si vas al navegador en http://127.0.0.1:8000/items/foo, verás un bonito error HTTP de: @@ -76,7 +76,7 @@ Esto es increíblemente útil mientras desarrollas y depuras código que interac /// -## Documentación +## Documentación { #documentation } Y cuando abras tu navegador en http://127.0.0.1:8000/docs, verás una documentación de API automática e interactiva como: @@ -90,7 +90,7 @@ Nota que el parámetro de path está declarado como un entero. /// -## Beneficios basados en estándares, documentación alternativa +## Beneficios basados en estándares, documentación alternativa { #standards-based-benefits-alternative-documentation } Y porque el esquema generado es del estándar OpenAPI, hay muchas herramientas compatibles. @@ -100,7 +100,7 @@ Debido a esto, el propio **FastAPI** proporciona una documentación de API alter De la misma manera, hay muchas herramientas compatibles. Incluyendo herramientas de generación de código para muchos lenguajes. -## Pydantic +## Pydantic { #pydantic } Toda la validación de datos se realiza internamente con Pydantic, así que obtienes todos los beneficios de esta. Y sabes que estás en buenas manos. @@ -108,7 +108,7 @@ Puedes usar las mismas declaraciones de tipo con `str`, `float`, `bool` y muchos Varios de estos se exploran en los siguientes capítulos del tutorial. -## El orden importa +## El orden importa { #order-matters } Al crear *path operations*, puedes encontrarte en situaciones donde tienes un path fijo. @@ -128,11 +128,11 @@ De manera similar, no puedes redefinir una path operation: La primera siempre será utilizada ya que el path coincide primero. -## Valores predefinidos +## Valores predefinidos { #predefined-values } -Si tienes una *path operation* que recibe un *path parameter*, pero quieres que los valores posibles válidos del *path parameter* estén predefinidos, puedes usar un `Enum` estándar de Python. +Si tienes una *path operation* que recibe un *path parameter*, pero quieres que los valores posibles válidos del *path parameter* estén predefinidos, puedes usar un `Enum` estándar de Python. -### Crear una clase `Enum` +### Crear una clase `Enum` { #create-an-enum-class } Importa `Enum` y crea una subclase que herede de `str` y de `Enum`. @@ -154,29 +154,29 @@ Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de -### Trabajando con *enumeraciones* de Python +### Trabajando con *enumeraciones* de Python { #working-with-python-enumerations } El valor del *path parameter* será un *miembro* de enumeración. -#### Comparar *miembros* de enumeraciones +#### Comparar *miembros* de enumeraciones { #compare-enumeration-members } Puedes compararlo con el *miembro* de enumeración en tu enum creada `ModelName`: {* ../../docs_src/path_params/tutorial005.py hl[17] *} -#### Obtener el valor de *enumeración* +#### Obtener el valor de *enumeración* { #get-the-enumeration-value } Puedes obtener el valor actual (un `str` en este caso) usando `model_name.value`, o en general, `your_enum_member.value`: @@ -188,7 +188,7 @@ También podrías acceder al valor `"lenet"` con `ModelName.lenet.value`. /// -#### Devolver *miembros* de enumeración +#### Devolver *miembros* de enumeración { #return-enumeration-members } Puedes devolver *miembros de enum* desde tu *path operation*, incluso anidados en un cuerpo JSON (por ejemplo, un `dict`). @@ -205,7 +205,7 @@ En tu cliente recibirás un response JSON como: } ``` -## Parámetros de path conteniendo paths +## Parámetros de path conteniendo paths { #path-parameters-containing-paths } Imaginemos que tienes una *path operation* con un path `/files/{file_path}`. @@ -213,7 +213,7 @@ Pero necesitas que `file_path` en sí mismo contenga un *path*, como `home/johnd Entonces, la URL para ese archivo sería algo como: `/files/home/johndoe/myfile.txt`. -### Soporte de OpenAPI +### Soporte de OpenAPI { #openapi-support } OpenAPI no soporta una manera de declarar un *path parameter* para que contenga un *path* dentro, ya que eso podría llevar a escenarios que son difíciles de probar y definir. @@ -221,7 +221,7 @@ Sin embargo, todavía puedes hacerlo en **FastAPI**, usando una de las herramien Y la documentación seguiría funcionando, aunque no agregue ninguna documentación indicando que el parámetro debe contener un path. -### Convertidor de Path +### Convertidor de Path { #path-convertor } Usando una opción directamente de Starlette puedes declarar un *path parameter* conteniendo un *path* usando una URL como: @@ -243,12 +243,12 @@ En ese caso, la URL sería: `/files//home/johndoe/myfile.txt`, con una doble bar /// -## Resumen +## Resumen { #recap } Con **FastAPI**, al usar declaraciones de tipo estándar de Python, cortas e intuitivas, obtienes: * Soporte del editor: chequeo de errores, autocompletado, etc. -* "parsing" de datos +* "parsing" de datos * Validación de datos * Anotación de API y documentación automática diff --git a/docs/es/docs/tutorial/query-params.md b/docs/es/docs/tutorial/query-params.md index 9bd47f8714..520134b1c3 100644 --- a/docs/es/docs/tutorial/query-params.md +++ b/docs/es/docs/tutorial/query-params.md @@ -1,4 +1,4 @@ -# Parámetros de Query +# Parámetros de Query { #query-parameters } Cuando declaras otros parámetros de función que no son parte de los parámetros de path, son automáticamente interpretados como parámetros de "query". @@ -24,11 +24,11 @@ Pero cuando los declaras con tipos de Python (en el ejemplo anterior, como `int` Todo el mismo proceso que se aplica para los parámetros de path también se aplica para los parámetros de query: * Soporte del editor (obviamente) -* "Parsing" de datos +* "parsing" de datos * Validación de datos * Documentación automática -## Valores por defecto +## Valores por defecto { #defaults } Como los parámetros de query no son una parte fija de un path, pueden ser opcionales y pueden tener valores por defecto. @@ -57,19 +57,21 @@ Los valores de los parámetros en tu función serán: * `skip=20`: porque lo configuraste en la URL * `limit=10`: porque ese era el valor por defecto -## Parámetros opcionales +## Parámetros opcionales { #optional-parameters } De la misma manera, puedes declarar parámetros de query opcionales, estableciendo su valor por defecto en `None`: {* ../../docs_src/query_params/tutorial002_py310.py hl[7] *} +En este caso, el parámetro de función `q` será opcional y será `None` por defecto. + /// check | Revisa Además, nota que **FastAPI** es lo suficientemente inteligente para notar que el parámetro de path `item_id` es un parámetro de path y `q` no lo es, por lo tanto, es un parámetro de query. /// -## Conversión de tipos en parámetros de query +## Conversión de tipos en parámetros de query { #query-parameter-type-conversion } También puedes declarar tipos `bool`, y serán convertidos: @@ -107,7 +109,7 @@ http://127.0.0.1:8000/items/foo?short=yes o cualquier otra variación (mayúsculas, primera letra en mayúscula, etc.), tu función verá el parámetro `short` con un valor `bool` de `True`. De lo contrario, será `False`. -## Múltiples parámetros de path y de query +## Múltiples parámetros de path y de query { #multiple-path-and-query-parameters } Puedes declarar múltiples parámetros de path y de query al mismo tiempo, **FastAPI** sabe cuál es cuál. @@ -117,7 +119,7 @@ Serán detectados por nombre: {* ../../docs_src/query_params/tutorial004_py310.py hl[6,8] *} -## Parámetros de query requeridos +## Parámetros de query requeridos { #required-query-parameters } Cuando declaras un valor por defecto para parámetros que no son de path (por ahora, solo hemos visto parámetros de query), entonces no es requerido. diff --git a/docs/es/docs/tutorial/security/index.md b/docs/es/docs/tutorial/security/index.md index e4d75d8a48..aecc6b4aac 100644 --- a/docs/es/docs/tutorial/security/index.md +++ b/docs/es/docs/tutorial/security/index.md @@ -1,4 +1,4 @@ -# Seguridad +# Seguridad { #security } Hay muchas formas de manejar la seguridad, autenticación y autorización. @@ -10,11 +10,11 @@ En muchos frameworks y sistemas, solo manejar la seguridad y autenticación requ Pero primero, vamos a revisar algunos pequeños conceptos. -## ¿Con prisa? +## ¿Con prisa? { #in-a-hurry } Si no te importan ninguno de estos términos y solo necesitas agregar seguridad con autenticación basada en nombre de usuario y contraseña *ahora mismo*, salta a los siguientes capítulos. -## OAuth2 +## OAuth2 { #oauth2 } OAuth2 es una especificación que define varias maneras de manejar la autenticación y autorización. @@ -24,7 +24,7 @@ Incluye formas de autenticarse usando un "tercero". Eso es lo que todos los sistemas con "iniciar sesión con Facebook, Google, X (Twitter), GitHub" utilizan internamente. -### OAuth 1 +### OAuth 1 { #oauth-1 } Hubo un OAuth 1, que es muy diferente de OAuth2, y más complejo, ya que incluía especificaciones directas sobre cómo encriptar la comunicación. @@ -38,7 +38,7 @@ En la sección sobre **deployment** verás cómo configurar HTTPS de forma gratu /// -## OpenID Connect +## OpenID Connect { #openid-connect } OpenID Connect es otra especificación, basada en **OAuth2**. @@ -48,7 +48,7 @@ Por ejemplo, el login de Google usa OpenID Connect (que internamente usa OAuth2) Pero el login de Facebook no soporta OpenID Connect. Tiene su propia versión de OAuth2. -### OpenID (no "OpenID Connect") +### OpenID (no "OpenID Connect") { #openid-not-openid-connect } Hubo también una especificación "OpenID". Que intentaba resolver lo mismo que **OpenID Connect**, pero no estaba basada en OAuth2. @@ -56,7 +56,7 @@ Entonces, era un sistema completo adicional. No es muy popular o usado hoy en día. -## OpenAPI +## OpenAPI { #openapi } OpenAPI (anteriormente conocido como Swagger) es la especificación abierta para construir APIs (ahora parte de la Linux Foundation). @@ -96,7 +96,7 @@ El problema más complejo es construir un proveedor de autenticación/autorizaci /// -## Utilidades de **FastAPI** +## Utilidades de **FastAPI** { #fastapi-utilities } FastAPI proporciona varias herramientas para cada uno de estos esquemas de seguridad en el módulo `fastapi.security` que simplifican el uso de estos mecanismos de seguridad. diff --git a/docs/es/docs/tutorial/static-files.md b/docs/es/docs/tutorial/static-files.md index 8c5855d86a..964009ae67 100644 --- a/docs/es/docs/tutorial/static-files.md +++ b/docs/es/docs/tutorial/static-files.md @@ -1,8 +1,8 @@ -# Archivos Estáticos +# Archivos Estáticos { #static-files } Puedes servir archivos estáticos automáticamente desde un directorio utilizando `StaticFiles`. -## Usa `StaticFiles` +## Usa `StaticFiles` { #use-staticfiles } * Importa `StaticFiles`. * "Monta" una instance de `StaticFiles()` en un path específico. @@ -17,7 +17,7 @@ También podrías usar `from starlette.staticfiles import StaticFiles`. /// -### Qué es "Montar" +### Qué es "Montar" { #what-is-mounting } "Montar" significa agregar una aplicación completa "independiente" en un path específico, que luego se encargará de manejar todos los sub-paths. @@ -25,7 +25,7 @@ Esto es diferente a usar un `APIRouter`, ya que una aplicación montada es compl Puedes leer más sobre esto en la [Guía de Usuario Avanzada](../advanced/index.md){.internal-link target=_blank}. -## Detalles +## Detalles { #details } El primer `"/static"` se refiere al sub-path en el que esta "sub-aplicación" será "montada". Por lo tanto, cualquier path que comience con `"/static"` será manejado por ella. @@ -35,6 +35,6 @@ El `name="static"` le da un nombre que puede ser utilizado internamente por **Fa Todos estos parámetros pueden ser diferentes a "`static`", ajústalos según las necesidades y detalles específicos de tu propia aplicación. -## Más info +## Más info { #more-info } Para más detalles y opciones revisa la documentación de Starlette sobre Archivos Estáticos. From d68892ab02c1faede898ba055eb53c4c5f0a19da Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 10:15:27 +0000 Subject: [PATCH 095/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ce69bf9163..1f83dbfbed 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Sync Spanish docs (outdated pages found with script). PR [#14553](https://github.com/fastapi/fastapi/pull/14553) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync German docs. PR [#14519](https://github.com/fastapi/fastapi/pull/14519) by [@nilslindemann](https://github.com/nilslindemann). * 🔥 Remove inactive/scarce translations to Vietnamese. PR [#14543](https://github.com/fastapi/fastapi/pull/14543) by [@tiangolo](https://github.com/tiangolo). * 🔥 Remove inactive/scarce translations to Persian. PR [#14542](https://github.com/fastapi/fastapi/pull/14542) by [@tiangolo](https://github.com/tiangolo). From 6400e5ee291408e45defb9c50eb1c747c390c565 Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:17:03 +0100 Subject: [PATCH 096/140] =?UTF-8?q?=F0=9F=8C=90=20Sync=20Portuguese=20docs?= =?UTF-8?q?=20(pages=20found=20with=20script)=20(#14554)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/pt/docs/_llm-test.md | 4 ++-- docs/pt/docs/project-generation.md | 2 +- docs/pt/docs/tutorial/handling-errors.md | 4 ++-- docs/pt/docs/tutorial/security/index.md | 16 ++++++++-------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/pt/docs/_llm-test.md b/docs/pt/docs/_llm-test.md index 8d85dd670b..3da5e8a71d 100644 --- a/docs/pt/docs/_llm-test.md +++ b/docs/pt/docs/_llm-test.md @@ -15,7 +15,7 @@ Use da seguinte forma: Os testes: -## Trechos de código { #code-snippets} +## Trechos de código { #code-snippets } //// tab | Teste @@ -53,7 +53,7 @@ Veja, por exemplo, a seção `### Quotes` em `docs/de/llm-prompt.md`. //// -## Citações em trechos de código { #quotes-in-code-snippets} +## Citações em trechos de código { #quotes-in-code-snippets } //// tab | Teste diff --git a/docs/pt/docs/project-generation.md b/docs/pt/docs/project-generation.md index 9c4f1f1ecd..419a39879f 100644 --- a/docs/pt/docs/project-generation.md +++ b/docs/pt/docs/project-generation.md @@ -13,7 +13,7 @@ Repositório GitHub: Date: Wed, 17 Dec 2025 10:17:30 +0000 Subject: [PATCH 097/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 1f83dbfbed..ded7416e6f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Sync Portuguese docs (pages found with script). PR [#14554](https://github.com/fastapi/fastapi/pull/14554) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync Spanish docs (outdated pages found with script). PR [#14553](https://github.com/fastapi/fastapi/pull/14553) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync German docs. PR [#14519](https://github.com/fastapi/fastapi/pull/14519) by [@nilslindemann](https://github.com/nilslindemann). * 🔥 Remove inactive/scarce translations to Vietnamese. PR [#14543](https://github.com/fastapi/fastapi/pull/14543) by [@tiangolo](https://github.com/tiangolo). From 1a1c29e57d98d3989ba8aad5578c2d0ae78bdc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 02:41:43 -0800 Subject: [PATCH 098/140] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20French,=20generated=20from=20the=20existing=20French?= =?UTF-8?q?=20docs=20(#14544)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/fr/llm-prompt.md | 128 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 docs/fr/llm-prompt.md diff --git a/docs/fr/llm-prompt.md b/docs/fr/llm-prompt.md new file mode 100644 index 0000000000..5ff18c4e8b --- /dev/null +++ b/docs/fr/llm-prompt.md @@ -0,0 +1,128 @@ +### Target language + +Translate to French (français). + +Language code: fr. + +### Grammar to use when talking to the reader + +Use the formal grammar (use «vous» instead of «tu»). + +### Quotes + +1) Convert neutral double quotes («"») and English double typographic quotes («“» and «”») to French guillemets (««» and «»»). + +2) In the French docs, guillemets are written without extra spaces: use «texte», not « texte ». + +3) Do not convert quotes inside code blocks, inline code, paths, URLs, or anything wrapped in backticks. + +Examples: + + Source (English): + + ««« + "Hello world" + “Hello Universe” + "He said: 'Hello'" + "The module is `__main__`" + »»» + + Result (French): + + ««« + «Hello world» + «Hello Universe» + «He said: 'Hello'» + «The module is `__main__`» + »»» + +### Ellipsis + +1) Make sure there is a space between an ellipsis and a word following or preceding the ellipsis. + +Examples: + + Source (English): + + ««« + ...as we intended. + ...this would work: + ...etc. + others... + More to come... + »»» + + Result (French): + + ««« + ... comme prévu. + ... cela fonctionnerait : + ... etc. + D'autres ... + La suite ... + »»» + +2) This does not apply in URLs, code blocks, and code snippets. Do not remove or add spaces there. + +### Headings + +1) Prefer translating headings using the infinitive form (as is common in the existing French docs): «Créer…», «Utiliser…», «Ajouter…». + +2) For headings that are instructions written in imperative in English (e.g. “Go check …”), keep them in imperative in French, using the formal grammar (e.g. «Allez voir …»). + +3) Keep heading punctuation as in the source. In particular, keep occurrences of literal « - » (space-hyphen-space) as « - » (the existing French docs use a hyphen here). + +### French instructions about technical terms + +Do not try to translate everything. In particular, keep common programming terms when that is the established usage in the French docs (e.g. «framework», «endpoint», «plug-in», «payload»). Use French where the existing docs already consistently use French (e.g. «requête», «réponse»). + +Keep class names, function names, modules, file names, and CLI commands unchanged. + +### List of English terms and their preferred French translations + +Below is a list of English terms and their preferred French translations, separated by a colon («:»). Use these translations, do not use your own. If an existing translation does not use these terms, update it to use them. + +* «/// note | Technical Details»: «/// note | Détails techniques» +* «/// note»: «/// note | Remarque» +* «/// tip»: «/// tip | Astuce» +* «/// warning»: «/// warning | Attention» +* «/// check»: «/// check | vérifier» +* «/// info»: «/// info» + +* «the docs»: «les documents» +* «the documentation»: «la documentation» + +* «framework»: «framework» (do not translate to «cadre») +* «performance»: «performance» + +* «type hints»: «annotations de type» +* «type annotations»: «annotations de type» + +* «autocomplete»: «autocomplétion» +* «autocompletion»: «autocomplétion» + +* «the request» (what the client sends to the server): «la requête» +* «the response» (what the server sends back to the client): «la réponse» + +* «the request body»: «le corps de la requête» +* «the response body»: «le corps de la réponse» + +* «path operation»: «opération de chemin» +* «path operations» (plural): «opérations de chemin» +* «path operation function»: «fonction de chemin» +* «path operation decorator»: «décorateur d'opération de chemin» + +* «path parameter»: «paramètre de chemin» +* «query parameter»: «paramètre de requête» + +* «the `Request`»: «`Request`» (keep as code identifier) +* «the `Response`»: «`Response`» (keep as code identifier) + +* «deployment»: «déploiement» +* «to upgrade»: «mettre à niveau» + +* «deprecated»: «déprécié» +* «to deprecate»: «déprécier» + +* «cheat sheet»: «aide-mémoire» +* «plug-in»: «plug-in» From 493ff37fc0d5e2c0157988174cd7a3a708208f97 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 10:42:14 +0000 Subject: [PATCH 099/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ded7416e6f..5914b4f2e3 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🔧 Add LLM prompt file for French, generated from the existing French docs. PR [#14544](https://github.com/fastapi/fastapi/pull/14544) by [@tiangolo](https://github.com/tiangolo). * 🌐 Sync Portuguese docs (pages found with script). PR [#14554](https://github.com/fastapi/fastapi/pull/14554) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync Spanish docs (outdated pages found with script). PR [#14553](https://github.com/fastapi/fastapi/pull/14553) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync German docs. PR [#14519](https://github.com/fastapi/fastapi/pull/14519) by [@nilslindemann](https://github.com/nilslindemann). From ed5c5bef5e085260473c8a46c3ab92ebd17aacfd Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:44:55 +0100 Subject: [PATCH 100/140] =?UTF-8?q?=F0=9F=94=A7=20Temporarily=20disable=20?= =?UTF-8?q?translations=20still=20in=20progress,=20being=20migrated=20to?= =?UTF-8?q?=20the=20new=20LLM=20setup=20(#14555)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/en/mkdocs.yml | 14 -------------- scripts/docs.py | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 60d2f977e5..66094c81e4 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -317,24 +317,10 @@ extra: name: de - Deutsch - link: /es/ name: es - español - - link: /fr/ - name: fr - français - - link: /ja/ - name: ja - 日本語 - - link: /ko/ - name: ko - 한국어 - link: /pt/ name: pt - português - link: /ru/ name: ru - русский язык - - link: /tr/ - name: tr - Türkçe - - link: /uk/ - name: uk - українська мова - - link: /zh/ - name: zh - 简体中文 - - link: /zh-hant/ - name: zh-hant - 繁體中文 extra_css: - css/termynal.css - css/custom.css diff --git a/scripts/docs.py b/scripts/docs.py index 75583a1cb2..b35bb3627f 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -19,6 +19,9 @@ from slugify import slugify as py_slugify logging.basicConfig(level=logging.INFO) +SUPPORTED_LANGS = {"en", "de", "es", "pt", "ru"} + + app = typer.Typer() mkdocs_name = "mkdocs.yml" @@ -260,7 +263,11 @@ def build_all() -> None: """ update_languages() shutil.rmtree(site_path, ignore_errors=True) - langs = [lang.name for lang in get_lang_paths() if lang.is_dir()] + langs = [ + lang.name + for lang in get_lang_paths() + if (lang.is_dir() and lang.name in SUPPORTED_LANGS) + ] cpu_count = os.cpu_count() or 1 process_pool_size = cpu_count * 4 typer.echo(f"Using process pool size: {process_pool_size}") @@ -340,6 +347,9 @@ def get_updated_config_content() -> Dict[str, Any]: for lang_path in get_lang_paths(): if lang_path.name in {"en", "em"} or not lang_path.is_dir(): continue + if lang_path.name not in SUPPORTED_LANGS: + # Skip languages that are not yet ready + continue code = lang_path.name languages.append({code: f"/{code}/"}) for lang_dict in languages: @@ -418,7 +428,7 @@ def verify_docs(): def langs_json(): langs = [] for lang_path in get_lang_paths(): - if lang_path.is_dir(): + if lang_path.is_dir() and lang_path.name in SUPPORTED_LANGS: langs.append(lang_path.name) print(json.dumps(langs)) From a2997a8f7dd9faaf528c9397cae41d59d0af526c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 10:45:18 +0000 Subject: [PATCH 101/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5914b4f2e3..48fd5f5d8a 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -23,6 +23,7 @@ hide: ### Internal +* 🔧 Temporarily disable translations still in progress, being migrated to the new LLM setup. PR [#14555](https://github.com/fastapi/fastapi/pull/14555) by [@YuriiMotov](https://github.com/YuriiMotov). * 🔧 Update test workflow config, remove commented code. PR [#14540](https://github.com/fastapi/fastapi/pull/14540) by [@tiangolo](https://github.com/tiangolo). * 👷 Configure coverage, error on main tests, don't wait for Smokeshow. PR [#14536](https://github.com/fastapi/fastapi/pull/14536) by [@tiangolo](https://github.com/tiangolo). * 👷 Run Smokeshow always, even on test failures. PR [#14538](https://github.com/fastapi/fastapi/pull/14538) by [@tiangolo](https://github.com/tiangolo). From 06273e48c8d8f4aaf213950261438de54c6c759a Mon Sep 17 00:00:00 2001 From: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:39:10 +0100 Subject: [PATCH 102/140] =?UTF-8?q?=E2=AC=86=20Bump=20`markdown-include-va?= =?UTF-8?q?riants`=20from=200.0.7=20to=200.0.8=20(#14556)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index 4f1863a4a7..beefc654f2 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -17,5 +17,5 @@ griffe-warnings-deprecated==1.1.0 # For griffe, it formats with black black==25.1.0 mkdocs-macros-plugin==1.4.1 -markdown-include-variants==0.0.7 +markdown-include-variants==0.0.8 python-slugify==8.0.4 From 330f0ba571d279771f9a087b6a36e94e9a15c75c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 14:39:37 +0000 Subject: [PATCH 103/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 48fd5f5d8a..bb72f4001e 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -23,6 +23,7 @@ hide: ### Internal +* ⬆ Bump `markdown-include-variants` from 0.0.7 to 0.0.8. PR [#14556](https://github.com/fastapi/fastapi/pull/14556) by [@YuriiMotov](https://github.com/YuriiMotov). * 🔧 Temporarily disable translations still in progress, being migrated to the new LLM setup. PR [#14555](https://github.com/fastapi/fastapi/pull/14555) by [@YuriiMotov](https://github.com/YuriiMotov). * 🔧 Update test workflow config, remove commented code. PR [#14540](https://github.com/fastapi/fastapi/pull/14540) by [@tiangolo](https://github.com/tiangolo). * 👷 Configure coverage, error on main tests, don't wait for Smokeshow. PR [#14536](https://github.com/fastapi/fastapi/pull/14536) by [@tiangolo](https://github.com/tiangolo). From 56a549b4d56c970acdcfcd6cb62ce2fd28810871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 11:59:04 -0800 Subject: [PATCH 104/140] =?UTF-8?q?=F0=9F=8C=90=20Update=20translations=20?= =?UTF-8?q?for=20pt=20(add-missing)=20(#14539)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- docs/pt/docs/deployment/fastapicloud.md | 65 +++++++++++++++++++ .../authentication-error-status-code.md | 17 +++++ 2 files changed, 82 insertions(+) create mode 100644 docs/pt/docs/deployment/fastapicloud.md create mode 100644 docs/pt/docs/how-to/authentication-error-status-code.md diff --git a/docs/pt/docs/deployment/fastapicloud.md b/docs/pt/docs/deployment/fastapicloud.md new file mode 100644 index 0000000000..03d3bd03be --- /dev/null +++ b/docs/pt/docs/deployment/fastapicloud.md @@ -0,0 +1,65 @@ +# FastAPI Cloud { #fastapi-cloud } + +Você pode implantar sua aplicação FastAPI no FastAPI Cloud com um **único comando**; entre na lista de espera, caso ainda não tenha feito isso. 🚀 + +## Login { #login } + +Certifique-se de que você já tem uma conta no **FastAPI Cloud** (nós convidamos você a partir da lista de espera 😉). + +Depois, faça login: + +
+ +```console +$ fastapi login + +You are logged in to FastAPI Cloud 🚀 +``` + +
+ +## Implantar { #deploy } + +Agora, implante sua aplicação, com **um único comando**: + +
+ +```console +$ fastapi deploy + +Deploying to FastAPI Cloud... + +✅ Deployment successful! + +🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev +``` + +
+ +É isso! Agora você pode acessar sua aplicação nesse URL. ✨ + +## Sobre o FastAPI Cloud { #about-fastapi-cloud } + +O **FastAPI Cloud** é desenvolvido pelo mesmo autor e equipe por trás do **FastAPI**. + +Ele simplifica o processo de **criar**, **implantar** e **acessar** uma API com esforço mínimo. + +Traz a mesma **experiência do desenvolvedor** de criar aplicações com FastAPI para **implantá-las** na nuvem. 🎉 + +Ele também cuidará da maioria das coisas de que você precisaria ao implantar uma aplicação, como: + +* HTTPS +* Replicação, com dimensionamento automático baseado em requests +* etc. + +O FastAPI Cloud é o patrocinador principal e provedor de financiamento dos projetos open source do ecossistema *FastAPI and friends*. ✨ + +## Implantar em outros provedores de nuvem { #deploy-to-other-cloud-providers } + +FastAPI é open source e baseado em padrões. Você pode implantar aplicações FastAPI em qualquer provedor de nuvem que escolher. + +Siga os tutoriais do seu provedor de nuvem para implantar aplicações FastAPI com esse provedor. 🤓 + +## Implantar no seu próprio servidor { #deploy-your-own-server } + +Também vou lhe ensinar, mais adiante neste guia de **Implantação**, todos os detalhes, para que você possa entender o que está acontecendo, o que precisa acontecer, ou como implantar aplicações FastAPI por conta própria, inclusive nos seus próprios servidores. 🤓 diff --git a/docs/pt/docs/how-to/authentication-error-status-code.md b/docs/pt/docs/how-to/authentication-error-status-code.md new file mode 100644 index 0000000000..878a1ca1ac --- /dev/null +++ b/docs/pt/docs/how-to/authentication-error-status-code.md @@ -0,0 +1,17 @@ +# Usar antigos códigos de status de erro de autenticação 403 { #use-old-403-authentication-error-status-codes } + +Antes da versão `0.122.0` do FastAPI, quando os utilitários de segurança integrados retornavam um erro ao cliente após uma falha na autenticação, eles usavam o código de status HTTP `403 Forbidden`. + +A partir da versão `0.122.0` do FastAPI, eles usam o código de status HTTP `401 Unauthorized`, mais apropriado, e retornam um cabeçalho `WWW-Authenticate` adequado na response, seguindo as especificações HTTP, RFC 7235, RFC 9110. + +Mas, se por algum motivo seus clientes dependem do comportamento antigo, você pode voltar a ele sobrescrevendo o método `make_not_authenticated_error` nas suas classes de segurança. + +Por exemplo, você pode criar uma subclasse de `HTTPBearer` que retorne um erro `403 Forbidden` em vez do erro padrão `401 Unauthorized`: + +{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} + +/// tip | Dica + +Perceba que a função retorna a instância da exceção, ela não a lança. O lançamento é feito no restante do código interno. + +/// From 99ef3833981c55e34c8aee28124991224189d895 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 19:59:35 +0000 Subject: [PATCH 105/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index bb72f4001e..53e28fce1f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🌐 Update translations for pt (add-missing). PR [#14539](https://github.com/fastapi/fastapi/pull/14539) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add LLM prompt file for French, generated from the existing French docs. PR [#14544](https://github.com/fastapi/fastapi/pull/14544) by [@tiangolo](https://github.com/tiangolo). * 🌐 Sync Portuguese docs (pages found with script). PR [#14554](https://github.com/fastapi/fastapi/pull/14554) by [@YuriiMotov](https://github.com/YuriiMotov). * 🌐 Sync Spanish docs (outdated pages found with script). PR [#14553](https://github.com/fastapi/fastapi/pull/14553) by [@YuriiMotov](https://github.com/YuriiMotov). From ed97d9dc0c23651eaef1181ac530a6992f5059c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 12:41:43 -0800 Subject: [PATCH 106/140] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove=20Python=20?= =?UTF-8?q?3.8=20from=20CI=20and=20remove=20Python=203.8=20examples=20from?= =?UTF-8?q?=20source=20docs=20(#14559)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yurii Motov Co-authored-by: Motov Yurii <109919500+YuriiMotov@users.noreply.github.com> --- .github/workflows/test.yml | 13 +- docs/de/docs/advanced/additional-responses.md | 4 +- docs/de/docs/advanced/async-tests.md | 8 +- docs/de/docs/advanced/behind-a-proxy.md | 12 +- docs/de/docs/advanced/custom-response.md | 32 ++-- docs/de/docs/advanced/events.md | 12 +- docs/de/docs/advanced/generate-clients.md | 2 +- docs/de/docs/advanced/middleware.md | 6 +- docs/de/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 10 +- .../advanced/response-change-status-code.md | 2 +- docs/de/docs/advanced/response-cookies.md | 4 +- docs/de/docs/advanced/response-directly.md | 2 +- docs/de/docs/advanced/response-headers.md | 4 +- docs/de/docs/advanced/settings.md | 10 +- docs/de/docs/advanced/sub-applications.md | 6 +- docs/de/docs/advanced/templates.md | 2 +- docs/de/docs/advanced/testing-events.md | 4 +- docs/de/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/de/docs/advanced/websockets.md | 6 +- docs/de/docs/advanced/wsgi.md | 2 +- docs/de/docs/how-to/conditional-openapi.md | 2 +- docs/de/docs/how-to/configure-swagger-ui.md | 6 +- docs/de/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/de/docs/how-to/extending-openapi.md | 10 +- docs/de/docs/how-to/graphql.md | 2 +- docs/de/docs/project-generation.md | 2 +- docs/de/docs/python-types.md | 172 +++-------------- docs/de/docs/tutorial/background-tasks.md | 6 +- docs/de/docs/tutorial/body-nested-models.md | 29 +-- docs/de/docs/tutorial/body.md | 2 +- docs/de/docs/tutorial/cors.md | 2 +- docs/de/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- .../dependencies/dependencies-with-yield.md | 10 +- .../dependencies/global-dependencies.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 4 +- docs/de/docs/tutorial/first-steps.md | 14 +- docs/de/docs/tutorial/handling-errors.md | 16 +- docs/de/docs/tutorial/metadata.md | 12 +- docs/de/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 4 +- .../path-params-numeric-validations.md | 4 +- docs/de/docs/tutorial/path-params.md | 25 +-- .../tutorial/query-params-str-validations.md | 4 +- docs/de/docs/tutorial/query-params.md | 4 +- docs/de/docs/tutorial/response-model.md | 4 +- docs/de/docs/tutorial/response-status-code.md | 6 +- docs/de/docs/tutorial/static-files.md | 2 +- docs/de/docs/tutorial/testing.md | 6 +- docs/en/docs/advanced/additional-responses.md | 4 +- docs/en/docs/advanced/async-tests.md | 8 +- docs/en/docs/advanced/behind-a-proxy.md | 12 +- docs/en/docs/advanced/custom-response.md | 32 ++-- docs/en/docs/advanced/events.md | 12 +- docs/en/docs/advanced/generate-clients.md | 2 +- docs/en/docs/advanced/middleware.md | 6 +- docs/en/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 10 +- .../advanced/response-change-status-code.md | 2 +- docs/en/docs/advanced/response-cookies.md | 4 +- docs/en/docs/advanced/response-directly.md | 2 +- docs/en/docs/advanced/response-headers.md | 4 +- docs/en/docs/advanced/settings.md | 10 +- docs/en/docs/advanced/sub-applications.md | 6 +- docs/en/docs/advanced/templates.md | 2 +- docs/en/docs/advanced/testing-events.md | 4 +- docs/en/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/en/docs/advanced/websockets.md | 6 +- docs/en/docs/advanced/wsgi.md | 2 +- docs/en/docs/how-to/conditional-openapi.md | 2 +- docs/en/docs/how-to/configure-swagger-ui.md | 6 +- docs/en/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/en/docs/how-to/extending-openapi.md | 10 +- docs/en/docs/how-to/graphql.md | 2 +- docs/en/docs/management-tasks.md | 2 +- docs/en/docs/python-types.md | 164 +++------------- docs/en/docs/tutorial/background-tasks.md | 6 +- docs/en/docs/tutorial/body-nested-models.md | 30 +-- docs/en/docs/tutorial/body.md | 2 +- docs/en/docs/tutorial/cors.md | 2 +- docs/en/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- .../dependencies/dependencies-with-yield.md | 10 +- .../dependencies/global-dependencies.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 4 +- docs/en/docs/tutorial/first-steps.md | 14 +- docs/en/docs/tutorial/handling-errors.md | 16 +- docs/en/docs/tutorial/metadata.md | 12 +- docs/en/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 4 +- .../path-params-numeric-validations.md | 4 +- docs/en/docs/tutorial/path-params.md | 26 +-- .../tutorial/query-params-str-validations.md | 4 +- docs/en/docs/tutorial/query-params.md | 4 +- docs/en/docs/tutorial/response-model.md | 4 +- docs/en/docs/tutorial/response-status-code.md | 6 +- docs/en/docs/tutorial/static-files.md | 2 +- docs/en/docs/tutorial/testing.md | 6 +- docs/es/docs/advanced/additional-responses.md | 4 +- docs/es/docs/advanced/async-tests.md | 8 +- docs/es/docs/advanced/behind-a-proxy.md | 12 +- docs/es/docs/advanced/custom-response.md | 32 ++-- docs/es/docs/advanced/events.md | 12 +- docs/es/docs/advanced/generate-clients.md | 2 +- docs/es/docs/advanced/middleware.md | 6 +- docs/es/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 10 +- .../advanced/response-change-status-code.md | 2 +- docs/es/docs/advanced/response-cookies.md | 4 +- docs/es/docs/advanced/response-directly.md | 2 +- docs/es/docs/advanced/response-headers.md | 4 +- docs/es/docs/advanced/settings.md | 10 +- docs/es/docs/advanced/sub-applications.md | 6 +- docs/es/docs/advanced/templates.md | 2 +- docs/es/docs/advanced/testing-events.md | 4 +- docs/es/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/es/docs/advanced/websockets.md | 6 +- docs/es/docs/advanced/wsgi.md | 2 +- docs/es/docs/how-to/conditional-openapi.md | 2 +- docs/es/docs/how-to/configure-swagger-ui.md | 6 +- docs/es/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/es/docs/how-to/extending-openapi.md | 10 +- docs/es/docs/how-to/graphql.md | 2 +- docs/es/docs/python-types.md | 164 +++------------- docs/es/docs/tutorial/background-tasks.md | 6 +- docs/es/docs/tutorial/body-nested-models.md | 30 +-- docs/es/docs/tutorial/body.md | 2 +- docs/es/docs/tutorial/cors.md | 2 +- docs/es/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- .../dependencies/dependencies-with-yield.md | 10 +- .../dependencies/global-dependencies.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 4 +- docs/es/docs/tutorial/first-steps.md | 14 +- docs/es/docs/tutorial/handling-errors.md | 16 +- docs/es/docs/tutorial/metadata.md | 12 +- docs/es/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 4 +- .../path-params-numeric-validations.md | 4 +- docs/es/docs/tutorial/path-params.md | 26 +-- .../tutorial/query-params-str-validations.md | 4 +- docs/es/docs/tutorial/query-params.md | 4 +- docs/es/docs/tutorial/response-model.md | 4 +- docs/es/docs/tutorial/response-status-code.md | 6 +- docs/es/docs/tutorial/static-files.md | 2 +- docs/es/docs/tutorial/testing.md | 6 +- docs/pt/docs/advanced/additional-responses.md | 4 +- docs/pt/docs/advanced/async-tests.md | 8 +- docs/pt/docs/advanced/behind-a-proxy.md | 12 +- docs/pt/docs/advanced/custom-response.md | 32 ++-- docs/pt/docs/advanced/events.md | 12 +- docs/pt/docs/advanced/generate-clients.md | 2 +- docs/pt/docs/advanced/middleware.md | 6 +- docs/pt/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 10 +- .../advanced/response-change-status-code.md | 2 +- docs/pt/docs/advanced/response-cookies.md | 4 +- docs/pt/docs/advanced/response-directly.md | 2 +- docs/pt/docs/advanced/response-headers.md | 4 +- docs/pt/docs/advanced/settings.md | 10 +- docs/pt/docs/advanced/sub-applications.md | 6 +- docs/pt/docs/advanced/templates.md | 2 +- docs/pt/docs/advanced/testing-events.md | 4 +- docs/pt/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/pt/docs/advanced/websockets.md | 6 +- docs/pt/docs/advanced/wsgi.md | 2 +- docs/pt/docs/how-to/conditional-openapi.md | 2 +- docs/pt/docs/how-to/configure-swagger-ui.md | 6 +- docs/pt/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/pt/docs/how-to/extending-openapi.md | 10 +- docs/pt/docs/how-to/graphql.md | 2 +- docs/pt/docs/python-types.md | 174 +++-------------- docs/pt/docs/tutorial/background-tasks.md | 6 +- docs/pt/docs/tutorial/body-nested-models.md | 30 +-- docs/pt/docs/tutorial/body.md | 2 +- docs/pt/docs/tutorial/cors.md | 2 +- docs/pt/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- .../dependencies/dependencies-with-yield.md | 10 +- .../dependencies/global-dependencies.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 4 +- docs/pt/docs/tutorial/first-steps.md | 14 +- docs/pt/docs/tutorial/handling-errors.md | 16 +- docs/pt/docs/tutorial/metadata.md | 12 +- docs/pt/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 4 +- .../path-params-numeric-validations.md | 4 +- docs/pt/docs/tutorial/path-params.md | 24 +-- .../tutorial/query-params-str-validations.md | 4 +- docs/pt/docs/tutorial/query-params.md | 4 +- docs/pt/docs/tutorial/response-model.md | 4 +- docs/pt/docs/tutorial/response-status-code.md | 6 +- docs/pt/docs/tutorial/static-files.md | 2 +- docs/pt/docs/tutorial/testing.md | 6 +- docs/pt/llm-prompt.md | 2 +- docs/ru/docs/advanced/additional-responses.md | 4 +- docs/ru/docs/advanced/async-tests.md | 8 +- docs/ru/docs/advanced/behind-a-proxy.md | 12 +- docs/ru/docs/advanced/custom-response.md | 32 ++-- docs/ru/docs/advanced/events.md | 12 +- docs/ru/docs/advanced/generate-clients.md | 2 +- docs/ru/docs/advanced/middleware.md | 6 +- docs/ru/docs/advanced/openapi-webhooks.md | 2 +- .../path-operation-advanced-configuration.md | 10 +- .../advanced/response-change-status-code.md | 2 +- docs/ru/docs/advanced/response-cookies.md | 4 +- docs/ru/docs/advanced/response-directly.md | 2 +- docs/ru/docs/advanced/response-headers.md | 4 +- docs/ru/docs/advanced/settings.md | 10 +- docs/ru/docs/advanced/sub-applications.md | 6 +- docs/ru/docs/advanced/templates.md | 2 +- docs/ru/docs/advanced/testing-events.md | 4 +- docs/ru/docs/advanced/testing-websockets.md | 2 +- .../docs/advanced/using-request-directly.md | 2 +- docs/ru/docs/advanced/websockets.md | 6 +- docs/ru/docs/advanced/wsgi.md | 2 +- docs/ru/docs/how-to/conditional-openapi.md | 2 +- docs/ru/docs/how-to/configure-swagger-ui.md | 6 +- docs/ru/docs/how-to/custom-docs-ui-assets.md | 14 +- docs/ru/docs/how-to/extending-openapi.md | 10 +- docs/ru/docs/how-to/graphql.md | 2 +- docs/ru/docs/python-types.md | 166 +++------------- docs/ru/docs/tutorial/background-tasks.md | 6 +- docs/ru/docs/tutorial/body-nested-models.md | 31 +-- docs/ru/docs/tutorial/body.md | 2 +- docs/ru/docs/tutorial/cors.md | 2 +- docs/ru/docs/tutorial/debugging.md | 2 +- .../dependencies/classes-as-dependencies.md | 24 +-- .../dependencies/dependencies-with-yield.md | 10 +- .../dependencies/global-dependencies.md | 2 +- .../tutorial/dependencies/sub-dependencies.md | 4 +- docs/ru/docs/tutorial/first-steps.md | 14 +- docs/ru/docs/tutorial/handling-errors.md | 16 +- docs/ru/docs/tutorial/metadata.md | 12 +- docs/ru/docs/tutorial/middleware.md | 4 +- .../tutorial/path-operation-configuration.md | 4 +- .../path-params-numeric-validations.md | 4 +- docs/ru/docs/tutorial/path-params.md | 26 +-- .../tutorial/query-params-str-validations.md | 4 +- docs/ru/docs/tutorial/query-params.md | 4 +- docs/ru/docs/tutorial/response-model.md | 4 +- docs/ru/docs/tutorial/response-status-code.md | 6 +- docs/ru/docs/tutorial/static-files.md | 2 +- docs/ru/docs/tutorial/testing.md | 6 +- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../additional_status_codes/tutorial001_an.py | 26 --- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{app_b => app_a_py39}/__init__.py | 0 docs_src/app_testing/{ => app_a_py39}/main.py | 0 .../app_testing/{ => app_a_py39}/test_main.py | 0 docs_src/app_testing/app_b_an/main.py | 39 ---- docs_src/app_testing/app_b_an/test_main.py | 65 ------- .../{app_b_an => app_b_py39}/__init__.py | 0 .../app_testing/{app_b => app_b_py39}/main.py | 0 .../{app_b => app_b_py39}/test_main.py | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../app_a_py39}/__init__.py | 0 docs_src/async_tests/{ => app_a_py39}/main.py | 0 .../async_tests/{ => app_a_py39}/test_main.py | 0 .../tutorial001_an.py | 20 -- .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/background_tasks/tutorial002_an.py | 27 --- .../{tutorial002.py => tutorial002_py39.py} | 0 ...torial001_01.py => tutorial001_01_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../app_an/dependencies.py | 12 -- .../app_an/internal/admin.py | 8 - docs_src/bigger_applications/app_an/main.py | 23 --- .../app_an/routers/items.py | 38 ---- .../app_an/routers/users.py | 18 -- .../{app/internal => app_py39}/__init__.py | 0 .../{app => app_py39}/dependencies.py | 0 .../routers => app_py39/internal}/__init__.py | 0 .../{app => app_py39}/internal/admin.py | 0 .../{app => app_py39}/main.py | 0 .../{app_an => app_py39/routers}/__init__.py | 0 .../{app => app_py39}/routers/items.py | 0 .../{app => app_py39}/routers/users.py | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 docs_src/body_fields/tutorial001_an.py | 22 --- .../{tutorial001.py => tutorial001_py39.py} | 0 .../body_multiple_params/tutorial001_an.py | 28 --- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../body_multiple_params/tutorial003_an.py | 27 --- .../{tutorial003.py => tutorial003_py39.py} | 0 .../body_multiple_params/tutorial004_an.py | 34 ---- .../{tutorial004.py => tutorial004_py39.py} | 0 .../body_multiple_params/tutorial005_an.py | 20 -- .../{tutorial005.py => tutorial005_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/body_nested_models/tutorial002.py | 20 -- docs_src/body_nested_models/tutorial003.py | 20 -- docs_src/body_nested_models/tutorial004.py | 26 --- docs_src/body_nested_models/tutorial005.py | 26 --- docs_src/body_nested_models/tutorial006.py | 26 --- docs_src/body_nested_models/tutorial007.py | 32 ---- docs_src/body_nested_models/tutorial008.py | 16 -- docs_src/body_nested_models/tutorial009.py | 10 - docs_src/body_updates/tutorial001.py | 34 ---- docs_src/body_updates/tutorial002.py | 37 ---- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../cookie_param_models/tutorial001_an.py | 18 -- .../{tutorial001.py => tutorial001_py39.py} | 0 .../cookie_param_models/tutorial002_an.py | 20 -- .../cookie_param_models/tutorial002_pv1_an.py | 21 -- ...rial002_pv1.py => tutorial002_pv1_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/cookie_params/tutorial001_an.py | 11 -- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../custom_request_and_route/tutorial001.py | 35 ---- .../tutorial001_an.py | 36 ---- .../custom_request_and_route/tutorial002.py | 29 --- .../tutorial002_an.py | 30 --- .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001b.py => tutorial001b_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial005.py => tutorial005_py39.py} | 0 .../{tutorial006.py => tutorial006_py39.py} | 0 .../{tutorial006b.py => tutorial006b_py39.py} | 0 .../{tutorial006c.py => tutorial006c_py39.py} | 0 .../{tutorial007.py => tutorial007_py39.py} | 0 .../{tutorial008.py => tutorial008_py39.py} | 0 .../{tutorial009.py => tutorial009_py39.py} | 0 .../{tutorial009b.py => tutorial009b_py39.py} | 0 .../{tutorial009c.py => tutorial009c_py39.py} | 0 .../{tutorial010.py => tutorial010_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/dataclasses/tutorial002.py | 26 --- docs_src/dataclasses/tutorial003.py | 55 ------ .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/dependencies/tutorial001_02_an.py | 25 --- docs_src/dependencies/tutorial001_an.py | 22 --- .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/dependencies/tutorial002_an.py | 26 --- .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/dependencies/tutorial003_an.py | 26 --- .../{tutorial003.py => tutorial003_py39.py} | 0 docs_src/dependencies/tutorial004_an.py | 26 --- .../{tutorial004.py => tutorial004_py39.py} | 0 docs_src/dependencies/tutorial005_an.py | 26 --- .../{tutorial005.py => tutorial005_py39.py} | 0 docs_src/dependencies/tutorial006_an.py | 20 -- .../{tutorial006.py => tutorial006_py39.py} | 0 .../{tutorial007.py => tutorial007_py39.py} | 0 docs_src/dependencies/tutorial008_an.py | 26 --- .../{tutorial008.py => tutorial008_py39.py} | 0 docs_src/dependencies/tutorial008b_an.py | 31 --- .../{tutorial008b.py => tutorial008b_py39.py} | 0 docs_src/dependencies/tutorial008c_an.py | 28 --- .../{tutorial008c.py => tutorial008c_py39.py} | 0 docs_src/dependencies/tutorial008d_an.py | 29 --- .../{tutorial008d.py => tutorial008d_py39.py} | 0 docs_src/dependencies/tutorial008e_an.py | 16 -- .../{tutorial008e.py => tutorial008e_py39.py} | 0 docs_src/dependencies/tutorial009.py | 25 --- .../{tutorial010.py => tutorial010_py39.py} | 0 docs_src/dependencies/tutorial011_an.py | 22 --- .../{tutorial011.py => tutorial011_py39.py} | 0 docs_src/dependencies/tutorial012_an.py | 26 --- docs_src/dependencies/tutorial012_an_py39.py | 3 +- .../{tutorial012.py => tutorial012_py39.py} | 0 docs_src/dependency_testing/tutorial001_an.py | 60 ------ .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/extra_data_types/tutorial001_an.py | 29 --- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 docs_src/extra_models/tutorial004.py | 22 --- docs_src/extra_models/tutorial005.py | 10 - .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/first_steps/tutorial002.py | 8 - .../{tutorial003.py => tutorial003_py39.py} | 0 docs_src/generate_clients/tutorial001.py | 28 --- docs_src/generate_clients/tutorial002.py | 38 ---- docs_src/generate_clients/tutorial003.py | 44 ----- .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial005.py => tutorial005_py39.py} | 0 .../{tutorial006.py => tutorial006_py39.py} | 0 docs_src/header_param_models/tutorial001.py | 19 -- .../header_param_models/tutorial001_an.py | 20 -- docs_src/header_param_models/tutorial002.py | 21 -- .../header_param_models/tutorial002_an.py | 22 --- .../header_param_models/tutorial002_pv1.py | 22 --- .../header_param_models/tutorial002_pv1_an.py | 23 --- docs_src/header_param_models/tutorial003.py | 19 -- .../header_param_models/tutorial003_an.py | 22 --- docs_src/header_params/tutorial001_an.py | 11 -- .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/header_params/tutorial002_an.py | 15 -- .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/header_params/tutorial003.py | 10 - docs_src/header_params/tutorial003_an.py | 11 -- ...tutorial001_1.py => tutorial001_1_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../tutorial004.py | 30 --- .../{tutorial005.py => tutorial005_py39.py} | 0 .../{tutorial006.py => tutorial006_py39.py} | 0 .../tutorial007.py | 34 ---- .../tutorial007_pv1.py | 34 ---- .../tutorial001.py | 19 -- .../tutorial002.py | 29 --- .../{tutorial002b.py => tutorial002b_py39.py} | 0 .../tutorial003.py | 24 --- .../tutorial004.py | 28 --- .../tutorial005.py | 33 ---- .../{tutorial006.py => tutorial006_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial003b.py => tutorial003b_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial005.py => tutorial005_py39.py} | 0 .../tutorial001_an.py | 17 -- .../{tutorial001.py => tutorial001_py39.py} | 0 .../tutorial002_an.py | 14 -- .../{tutorial002.py => tutorial002_py39.py} | 0 .../tutorial003_an.py | 14 -- .../{tutorial003.py => tutorial003_py39.py} | 0 .../tutorial004_an.py | 14 -- .../{tutorial004.py => tutorial004_py39.py} | 0 .../tutorial005_an.py | 15 -- .../{tutorial005.py => tutorial005_py39.py} | 0 .../tutorial006_an.py | 19 -- .../{tutorial006.py => tutorial006_py39.py} | 0 ...torial001_an.py => tutorial001_an_py39.py} | 0 ...torial002_an.py => tutorial002_an_py39.py} | 0 ...torial003_an.py => tutorial003_an_py39.py} | 0 docs_src/pydantic_v1_in_v2/tutorial004_an.py | 20 -- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial005.py => tutorial005_py39.py} | 0 docs_src/python_types/tutorial006.py | 6 - docs_src/python_types/tutorial007.py | 5 - docs_src/python_types/tutorial008.py | 7 - .../{tutorial008b.py => tutorial008b_py39.py} | 0 .../{tutorial009.py => tutorial009_py39.py} | 0 .../{tutorial009b.py => tutorial009b_py39.py} | 0 .../{tutorial009c.py => tutorial009c_py39.py} | 0 .../{tutorial010.py => tutorial010_py39.py} | 0 docs_src/python_types/tutorial011.py | 23 --- .../{tutorial012.py => tutorial012_py39.py} | 0 docs_src/python_types/tutorial013.py | 5 - docs_src/query_param_models/tutorial001.py | 19 -- docs_src/query_param_models/tutorial001_an.py | 19 -- .../query_param_models/tutorial001_an_py39.py | 3 +- .../query_param_models/tutorial001_py39.py | 3 +- docs_src/query_param_models/tutorial002.py | 21 -- docs_src/query_param_models/tutorial002_an.py | 21 -- .../query_param_models/tutorial002_an_py39.py | 3 +- .../query_param_models/tutorial002_pv1.py | 22 --- .../query_param_models/tutorial002_pv1_an.py | 22 --- .../tutorial002_pv1_an_py39.py | 3 +- .../tutorial002_pv1_py39.py | 3 +- .../query_param_models/tutorial002_py39.py | 3 +- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 .../{tutorial004.py => tutorial004_py39.py} | 0 .../{tutorial005.py => tutorial005_py39.py} | 0 .../{tutorial006.py => tutorial006_py39.py} | 0 docs_src/query_params/tutorial006b.py | 13 -- .../{tutorial001.py => tutorial001_py39.py} | 0 ...torial002_an.py => tutorial002_an_py39.py} | 3 +- .../{tutorial002.py => tutorial002_py39.py} | 0 .../tutorial003_an.py | 16 -- .../{tutorial003.py => tutorial003_py39.py} | 0 .../tutorial004_an.py | 18 -- .../{tutorial004.py => tutorial004_py39.py} | 0 .../tutorial005_an.py | 12 -- .../{tutorial005.py => tutorial005_py39.py} | 0 .../tutorial006_an.py | 12 -- .../{tutorial006.py => tutorial006_py39.py} | 0 .../tutorial006c_an.py | 14 -- .../{tutorial006c.py => tutorial006c_py39.py} | 0 .../tutorial007_an.py | 16 -- .../{tutorial007.py => tutorial007_py39.py} | 0 .../tutorial008_an.py | 23 --- .../{tutorial008.py => tutorial008_py39.py} | 0 .../tutorial009_an.py | 14 -- .../{tutorial009.py => tutorial009_py39.py} | 0 .../tutorial010_an.py | 27 --- .../{tutorial010.py => tutorial010_py39.py} | 0 .../tutorial011.py | 11 -- .../tutorial011_an.py | 12 -- .../tutorial012.py | 11 -- .../tutorial012_an.py | 12 -- .../tutorial013_an.py | 10 - .../{tutorial013.py => tutorial013_py39.py} | 0 .../tutorial014_an.py | 16 -- .../{tutorial014.py => tutorial014_py39.py} | 0 .../tutorial015_an.py | 31 --- docs_src/request_files/tutorial001_02_an.py | 22 --- ...torial001_02.py => tutorial001_02_py39.py} | 0 docs_src/request_files/tutorial001_03_an.py | 16 -- ...torial001_03.py => tutorial001_03_py39.py} | 0 docs_src/request_files/tutorial001_an.py | 14 -- .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/request_files/tutorial002.py | 33 ---- docs_src/request_files/tutorial002_an.py | 34 ---- docs_src/request_files/tutorial003.py | 37 ---- docs_src/request_files/tutorial003_an.py | 40 ---- .../request_form_models/tutorial001_an.py | 15 -- .../{tutorial001.py => tutorial001_py39.py} | 0 .../request_form_models/tutorial002_an.py | 16 -- .../request_form_models/tutorial002_pv1_an.py | 18 -- ...rial002_pv1.py => tutorial002_pv1_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/request_forms/tutorial001_an.py | 9 - .../{tutorial001.py => tutorial001_py39.py} | 0 .../request_forms_and_files/tutorial001_an.py | 17 -- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/response_model/tutorial001.py | 27 --- docs_src/response_model/tutorial001_01.py | 27 --- .../{tutorial002.py => tutorial002_py39.py} | 0 ...torial003_01.py => tutorial003_01_py39.py} | 0 ...torial003_02.py => tutorial003_02_py39.py} | 0 ...torial003_03.py => tutorial003_03_py39.py} | 0 ...torial003_04.py => tutorial003_04_py39.py} | 0 ...torial003_05.py => tutorial003_05_py39.py} | 0 .../{tutorial003.py => tutorial003_py39.py} | 0 docs_src/response_model/tutorial004.py | 26 --- .../{tutorial005.py => tutorial005_py39.py} | 0 .../{tutorial006.py => tutorial006_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 ...rial001_pv1.py => tutorial001_pv1_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial002.py => tutorial002_py39.py} | 0 .../schema_extra_example/tutorial003_an.py | 35 ---- .../{tutorial003.py => tutorial003_py39.py} | 0 .../schema_extra_example/tutorial004_an.py | 44 ----- .../{tutorial004.py => tutorial004_py39.py} | 0 .../schema_extra_example/tutorial005_an.py | 55 ------ .../{tutorial005.py => tutorial005_py39.py} | 0 docs_src/security/tutorial001_an.py | 12 -- .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/security/tutorial002_an.py | 33 ---- .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/security/tutorial003_an.py | 95 --------- .../{tutorial003.py => tutorial003_py39.py} | 0 docs_src/security/tutorial004_an.py | 148 -------------- .../{tutorial004.py => tutorial004_py39.py} | 0 docs_src/security/tutorial005.py | 177 ----------------- docs_src/security/tutorial005_an.py | 180 ------------------ docs_src/security/tutorial006_an.py | 12 -- .../{tutorial006.py => tutorial006_py39.py} | 0 docs_src/security/tutorial007_an.py | 36 ---- .../{tutorial007.py => tutorial007_py39.py} | 0 .../separate_openapi_schemas/tutorial001.py | 28 --- .../separate_openapi_schemas/tutorial002.py | 28 --- .../app01_py39}/__init__.py | 0 .../settings/{app01 => app01_py39}/config.py | 0 .../settings/{app01 => app01_py39}/main.py | 0 docs_src/settings/app02/__init__.py | 0 docs_src/settings/app02_an/__init__.py | 0 docs_src/settings/app02_an/config.py | 7 - docs_src/settings/app02_an/main.py | 22 --- docs_src/settings/app02_an/test_main.py | 23 --- .../app02_py39}/__init__.py | 0 .../settings/{app02 => app02_py39}/config.py | 0 .../settings/{app02 => app02_py39}/main.py | 0 .../{app02 => app02_py39}/test_main.py | 0 docs_src/settings/app03/__init__.py | 0 docs_src/settings/app03_an/__init__.py | 0 docs_src/settings/app03_an/config.py | 9 - docs_src/settings/app03_an/config_pv1.py | 10 - docs_src/settings/app03_an/main.py | 22 --- .../{app01 => app03_py39}/__init__.py | 0 .../settings/{app03 => app03_py39}/config.py | 0 .../{app03 => app03_py39}/config_pv1.py | 0 .../settings/{app03 => app03_py39}/main.py | 0 ...rial001_pv1.py => tutorial001_pv1_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/sql_databases/tutorial001.py | 71 ------- docs_src/sql_databases/tutorial001_an.py | 74 ------- docs_src/sql_databases/tutorial002.py | 104 ---------- docs_src/sql_databases/tutorial002_an.py | 104 ---------- .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 .../{tutorial001.py => tutorial001_py39.py} | 0 docs_src/websockets/tutorial002_an.py | 93 --------- .../{tutorial002.py => tutorial002_py39.py} | 0 docs_src/websockets/tutorial003.py | 83 -------- .../{tutorial001.py => tutorial001_py39.py} | 0 fastapi/_compat/shared.py | 8 +- pyproject.toml | 31 +-- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 2 +- .../test_tutorial001.py | 7 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../{test_main.py => test_main_a.py} | 2 +- .../test_tutorial001.py | 5 +- .../test_background_tasks/test_tutorial001.py | 2 +- .../test_background_tasks/test_tutorial002.py | 7 +- .../test_behind_a_proxy/test_tutorial001.py | 2 +- .../test_tutorial001_01.py | 2 +- .../test_behind_a_proxy/test_tutorial002.py | 2 +- .../test_behind_a_proxy/test_tutorial003.py | 2 +- .../test_behind_a_proxy/test_tutorial004.py | 2 +- .../test_bigger_applications/test_main.py | 7 +- .../test_body/test_tutorial001.py | 2 +- .../test_body_fields/test_tutorial001.py | 7 +- .../test_tutorial001.py | 7 +- .../test_tutorial003.py | 7 +- .../test_tutorial009.py | 5 +- .../test_body_updates/test_tutorial001.py | 5 +- .../test_tutorial001.py | 6 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial001.py | 7 +- .../test_tutorial002.py | 11 +- .../test_cookie_params/test_tutorial001.py | 7 +- .../test_cors/test_tutorial001.py | 2 +- .../test_custom_docs_ui/test_tutorial001.py | 2 +- .../test_custom_docs_ui/test_tutorial002.py | 2 +- .../test_tutorial001.py | 8 +- .../test_tutorial002.py | 8 +- .../test_tutorial003.py | 2 +- .../test_custom_response/test_tutorial001.py | 2 +- .../test_custom_response/test_tutorial001b.py | 2 +- .../test_custom_response/test_tutorial004.py | 2 +- .../test_custom_response/test_tutorial005.py | 2 +- .../test_custom_response/test_tutorial006.py | 2 +- .../test_custom_response/test_tutorial006b.py | 2 +- .../test_custom_response/test_tutorial006c.py | 2 +- .../test_custom_response/test_tutorial007.py | 2 +- .../test_custom_response/test_tutorial008.py | 6 +- .../test_custom_response/test_tutorial009.py | 6 +- .../test_custom_response/test_tutorial009b.py | 6 +- .../test_custom_response/test_tutorial009c.py | 2 +- .../test_dataclasses/test_tutorial001.py | 2 +- .../test_dataclasses/test_tutorial002.py | 5 +- .../test_dataclasses/test_tutorial003.py | 5 +- .../test_dependencies/test_tutorial001.py | 7 +- .../test_dependencies/test_tutorial004.py | 7 +- .../test_dependencies/test_tutorial006.py | 7 +- .../test_dependencies/test_tutorial008b.py | 7 +- .../test_dependencies/test_tutorial008c.py | 7 +- .../test_dependencies/test_tutorial008d.py | 7 +- .../test_dependencies/test_tutorial008e.py | 7 +- .../test_dependencies/test_tutorial012.py | 7 +- .../test_events/test_tutorial001.py | 2 +- .../test_events/test_tutorial002.py | 2 +- .../test_events/test_tutorial003.py | 2 +- .../test_tutorial001.py | 2 +- .../test_extra_data_types/test_tutorial001.py | 7 +- .../test_extra_models/test_tutorial003.py | 2 +- .../test_extra_models/test_tutorial004.py | 5 +- .../test_extra_models/test_tutorial005.py | 5 +- .../test_first_steps/test_tutorial001.py | 2 +- .../test_generate_clients/test_tutorial003.py | 2 +- .../test_handling_errors/test_tutorial001.py | 2 +- .../test_handling_errors/test_tutorial002.py | 2 +- .../test_handling_errors/test_tutorial003.py | 2 +- .../test_handling_errors/test_tutorial004.py | 2 +- .../test_handling_errors/test_tutorial005.py | 2 +- .../test_handling_errors/test_tutorial006.py | 2 +- .../test_tutorial001.py | 8 +- .../test_tutorial002.py | 12 +- .../test_tutorial003.py | 8 +- .../test_header_params/test_tutorial001.py | 4 +- .../test_header_params/test_tutorial002.py | 7 +- .../test_header_params/test_tutorial003.py | 7 +- .../test_metadata/test_tutorial001.py | 2 +- .../test_metadata/test_tutorial001_1.py | 2 +- .../test_metadata/test_tutorial004.py | 2 +- .../test_tutorial001.py | 2 +- .../test_openapi_webhooks/test_tutorial001.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 5 +- .../test_tutorial005.py | 2 +- .../test_tutorial006.py | 2 +- .../test_tutorial007.py | 5 +- .../test_tutorial007_pv1.py | 5 +- .../test_tutorial002b.py | 2 +- .../test_tutorial005.py | 5 +- .../test_tutorial006.py | 2 +- .../test_path_params/test_tutorial004.py | 2 +- .../test_path_params/test_tutorial005.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial002.py | 2 +- .../test_tutorial003.py | 2 +- .../test_tutorial004.py | 5 +- .../test_tutorial001.py | 8 +- .../test_tutorial002.py | 14 +- .../test_query_params/test_tutorial005.py | 2 +- .../test_query_params/test_tutorial006.py | 2 +- .../test_tutorial010.py | 7 +- .../test_tutorial011.py | 8 +- .../test_tutorial012.py | 8 +- .../test_tutorial013.py | 7 +- .../test_tutorial014.py | 7 +- .../test_tutorial015.py | 5 +- .../test_request_files/test_tutorial001.py | 7 +- .../test_request_files/test_tutorial001_02.py | 7 +- .../test_request_files/test_tutorial001_03.py | 7 +- .../test_request_files/test_tutorial002.py | 8 +- .../test_request_files/test_tutorial003.py | 8 +- .../test_tutorial001.py | 7 +- .../test_tutorial002.py | 7 +- .../test_tutorial002_pv1.py | 7 +- .../test_request_forms/test_tutorial001.py | 7 +- .../test_tutorial001.py | 7 +- .../test_tutorial001.py | 2 +- .../test_response_cookies/test_tutorial001.py | 2 +- .../test_response_cookies/test_tutorial002.py | 2 +- .../test_tutorial001.py | 2 +- .../test_response_headers/test_tutorial001.py | 2 +- .../test_response_headers/test_tutorial002.py | 2 +- .../test_response_model/test_tutorial003.py | 2 +- .../test_tutorial003_01.py | 2 +- .../test_tutorial003_02.py | 2 +- .../test_tutorial003_03.py | 2 +- .../test_tutorial003_04.py | 2 +- .../test_tutorial003_05.py | 2 +- .../test_response_model/test_tutorial004.py | 5 +- .../test_response_model/test_tutorial005.py | 2 +- .../test_response_model/test_tutorial006.py | 2 +- .../test_tutorial001.py | 2 +- .../test_tutorial001_pv1.py | 2 +- .../test_tutorial004.py | 7 +- .../test_tutorial005.py | 7 +- .../test_security/test_tutorial001.py | 7 +- .../test_security/test_tutorial003.py | 7 +- .../test_security/test_tutorial005.py | 8 +- .../test_security/test_tutorial006.py | 7 +- .../test_tutorial001.py | 5 +- .../test_tutorial002.py | 5 +- .../test_tutorial/test_settings/test_app02.py | 7 +- .../test_tutorial/test_settings/test_app03.py | 7 +- .../test_settings/test_tutorial001.py | 4 +- .../test_sql_databases/test_tutorial001.py | 8 +- .../test_sql_databases/test_tutorial002.py | 8 +- .../test_sub_applications/test_tutorial001.py | 2 +- .../test_templates/test_tutorial001.py | 2 +- .../{test_main.py => test_main_a.py} | 2 +- .../test_tutorial/test_testing/test_main_b.py | 7 +- .../test_testing/test_tutorial001.py | 2 +- .../test_testing/test_tutorial002.py | 2 +- .../test_testing/test_tutorial003.py | 2 +- .../test_testing/test_tutorial004.py | 2 +- .../test_tutorial001.py | 7 +- .../test_tutorial001.py | 2 +- .../test_websockets/test_tutorial001.py | 2 +- .../test_websockets/test_tutorial002.py | 7 +- .../test_websockets/test_tutorial003.py | 7 +- .../test_wsgi/test_tutorial001.py | 2 +- 815 files changed, 1266 insertions(+), 6546 deletions(-) rename docs_src/additional_responses/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/additional_responses/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/additional_responses/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/additional_responses/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/additional_status_codes/tutorial001_an.py rename docs_src/additional_status_codes/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/advanced_middleware/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/advanced_middleware/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/advanced_middleware/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/app_testing/{app_b => app_a_py39}/__init__.py (100%) rename docs_src/app_testing/{ => app_a_py39}/main.py (100%) rename docs_src/app_testing/{ => app_a_py39}/test_main.py (100%) delete mode 100644 docs_src/app_testing/app_b_an/main.py delete mode 100644 docs_src/app_testing/app_b_an/test_main.py rename docs_src/app_testing/{app_b_an => app_b_py39}/__init__.py (100%) rename docs_src/app_testing/{app_b => app_b_py39}/main.py (100%) rename docs_src/app_testing/{app_b => app_b_py39}/test_main.py (100%) rename docs_src/app_testing/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/app_testing/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/app_testing/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/app_testing/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/{bigger_applications/app => async_tests/app_a_py39}/__init__.py (100%) rename docs_src/async_tests/{ => app_a_py39}/main.py (100%) rename docs_src/async_tests/{ => app_a_py39}/test_main.py (100%) delete mode 100644 docs_src/authentication_error_status_code/tutorial001_an.py rename docs_src/background_tasks/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/background_tasks/tutorial002_an.py rename docs_src/background_tasks/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/behind_a_proxy/{tutorial001_01.py => tutorial001_01_py39.py} (100%) rename docs_src/behind_a_proxy/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/behind_a_proxy/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/behind_a_proxy/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/behind_a_proxy/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/bigger_applications/app_an/dependencies.py delete mode 100644 docs_src/bigger_applications/app_an/internal/admin.py delete mode 100644 docs_src/bigger_applications/app_an/main.py delete mode 100644 docs_src/bigger_applications/app_an/routers/items.py delete mode 100644 docs_src/bigger_applications/app_an/routers/users.py rename docs_src/bigger_applications/{app/internal => app_py39}/__init__.py (100%) rename docs_src/bigger_applications/{app => app_py39}/dependencies.py (100%) rename docs_src/bigger_applications/{app/routers => app_py39/internal}/__init__.py (100%) rename docs_src/bigger_applications/{app => app_py39}/internal/admin.py (100%) rename docs_src/bigger_applications/{app => app_py39}/main.py (100%) rename docs_src/bigger_applications/{app_an => app_py39/routers}/__init__.py (100%) rename docs_src/bigger_applications/{app => app_py39}/routers/items.py (100%) rename docs_src/bigger_applications/{app => app_py39}/routers/users.py (100%) rename docs_src/body/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/body/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/body/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/body/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/body_fields/tutorial001_an.py rename docs_src/body_fields/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/body_multiple_params/tutorial001_an.py rename docs_src/body_multiple_params/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/body_multiple_params/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/body_multiple_params/tutorial003_an.py rename docs_src/body_multiple_params/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/body_multiple_params/tutorial004_an.py rename docs_src/body_multiple_params/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/body_multiple_params/tutorial005_an.py rename docs_src/body_multiple_params/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/body_nested_models/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/body_nested_models/tutorial002.py delete mode 100644 docs_src/body_nested_models/tutorial003.py delete mode 100644 docs_src/body_nested_models/tutorial004.py delete mode 100644 docs_src/body_nested_models/tutorial005.py delete mode 100644 docs_src/body_nested_models/tutorial006.py delete mode 100644 docs_src/body_nested_models/tutorial007.py delete mode 100644 docs_src/body_nested_models/tutorial008.py delete mode 100644 docs_src/body_nested_models/tutorial009.py delete mode 100644 docs_src/body_updates/tutorial001.py delete mode 100644 docs_src/body_updates/tutorial002.py rename docs_src/conditional_openapi/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/configure_swagger_ui/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/configure_swagger_ui/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/configure_swagger_ui/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/cookie_param_models/tutorial001_an.py rename docs_src/cookie_param_models/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/cookie_param_models/tutorial002_an.py delete mode 100644 docs_src/cookie_param_models/tutorial002_pv1_an.py rename docs_src/cookie_param_models/{tutorial002_pv1.py => tutorial002_pv1_py39.py} (100%) rename docs_src/cookie_param_models/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/cookie_params/tutorial001_an.py rename docs_src/cookie_params/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/cors/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/custom_docs_ui/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/custom_docs_ui/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/custom_request_and_route/tutorial001.py delete mode 100644 docs_src/custom_request_and_route/tutorial001_an.py delete mode 100644 docs_src/custom_request_and_route/tutorial002.py delete mode 100644 docs_src/custom_request_and_route/tutorial002_an.py rename docs_src/custom_request_and_route/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/custom_response/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/custom_response/{tutorial001b.py => tutorial001b_py39.py} (100%) rename docs_src/custom_response/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/custom_response/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/custom_response/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/custom_response/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/custom_response/{tutorial006.py => tutorial006_py39.py} (100%) rename docs_src/custom_response/{tutorial006b.py => tutorial006b_py39.py} (100%) rename docs_src/custom_response/{tutorial006c.py => tutorial006c_py39.py} (100%) rename docs_src/custom_response/{tutorial007.py => tutorial007_py39.py} (100%) rename docs_src/custom_response/{tutorial008.py => tutorial008_py39.py} (100%) rename docs_src/custom_response/{tutorial009.py => tutorial009_py39.py} (100%) rename docs_src/custom_response/{tutorial009b.py => tutorial009b_py39.py} (100%) rename docs_src/custom_response/{tutorial009c.py => tutorial009c_py39.py} (100%) rename docs_src/custom_response/{tutorial010.py => tutorial010_py39.py} (100%) rename docs_src/dataclasses/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/dataclasses/tutorial002.py delete mode 100644 docs_src/dataclasses/tutorial003.py rename docs_src/debugging/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial001_02_an.py delete mode 100644 docs_src/dependencies/tutorial001_an.py rename docs_src/dependencies/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial002_an.py rename docs_src/dependencies/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial003_an.py rename docs_src/dependencies/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial004_an.py rename docs_src/dependencies/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial005_an.py rename docs_src/dependencies/{tutorial005.py => tutorial005_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial006_an.py rename docs_src/dependencies/{tutorial006.py => tutorial006_py39.py} (100%) rename docs_src/dependencies/{tutorial007.py => tutorial007_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial008_an.py rename docs_src/dependencies/{tutorial008.py => tutorial008_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial008b_an.py rename docs_src/dependencies/{tutorial008b.py => tutorial008b_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial008c_an.py rename docs_src/dependencies/{tutorial008c.py => tutorial008c_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial008d_an.py rename docs_src/dependencies/{tutorial008d.py => tutorial008d_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial008e_an.py rename docs_src/dependencies/{tutorial008e.py => tutorial008e_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial009.py rename docs_src/dependencies/{tutorial010.py => tutorial010_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial011_an.py rename docs_src/dependencies/{tutorial011.py => tutorial011_py39.py} (100%) delete mode 100644 docs_src/dependencies/tutorial012_an.py rename docs_src/dependencies/{tutorial012.py => tutorial012_py39.py} (100%) delete mode 100644 docs_src/dependency_testing/tutorial001_an.py rename docs_src/dependency_testing/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/encoder/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/events/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/events/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/events/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/extending_openapi/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/extra_data_types/tutorial001_an.py rename docs_src/extra_data_types/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/extra_models/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/extra_models/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/extra_models/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/extra_models/tutorial004.py delete mode 100644 docs_src/extra_models/tutorial005.py rename docs_src/first_steps/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/first_steps/tutorial002.py rename docs_src/first_steps/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/generate_clients/tutorial001.py delete mode 100644 docs_src/generate_clients/tutorial002.py delete mode 100644 docs_src/generate_clients/tutorial003.py rename docs_src/generate_clients/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/graphql/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/handling_errors/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/handling_errors/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/handling_errors/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/handling_errors/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/handling_errors/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/handling_errors/{tutorial006.py => tutorial006_py39.py} (100%) delete mode 100644 docs_src/header_param_models/tutorial001.py delete mode 100644 docs_src/header_param_models/tutorial001_an.py delete mode 100644 docs_src/header_param_models/tutorial002.py delete mode 100644 docs_src/header_param_models/tutorial002_an.py delete mode 100644 docs_src/header_param_models/tutorial002_pv1.py delete mode 100644 docs_src/header_param_models/tutorial002_pv1_an.py delete mode 100644 docs_src/header_param_models/tutorial003.py delete mode 100644 docs_src/header_param_models/tutorial003_an.py delete mode 100644 docs_src/header_params/tutorial001_an.py rename docs_src/header_params/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/header_params/tutorial002_an.py rename docs_src/header_params/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/header_params/tutorial003.py delete mode 100644 docs_src/header_params/tutorial003_an.py rename docs_src/metadata/{tutorial001_1.py => tutorial001_1_py39.py} (100%) rename docs_src/metadata/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/metadata/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/metadata/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/metadata/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/middleware/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/openapi_callbacks/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/openapi_webhooks/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/path_operation_advanced_configuration/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/path_operation_advanced_configuration/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/path_operation_advanced_configuration/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial004.py rename docs_src/path_operation_advanced_configuration/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/path_operation_advanced_configuration/{tutorial006.py => tutorial006_py39.py} (100%) delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial007.py delete mode 100644 docs_src/path_operation_advanced_configuration/tutorial007_pv1.py delete mode 100644 docs_src/path_operation_configuration/tutorial001.py delete mode 100644 docs_src/path_operation_configuration/tutorial002.py rename docs_src/path_operation_configuration/{tutorial002b.py => tutorial002b_py39.py} (100%) delete mode 100644 docs_src/path_operation_configuration/tutorial003.py delete mode 100644 docs_src/path_operation_configuration/tutorial004.py delete mode 100644 docs_src/path_operation_configuration/tutorial005.py rename docs_src/path_operation_configuration/{tutorial006.py => tutorial006_py39.py} (100%) rename docs_src/path_params/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/path_params/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/path_params/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/path_params/{tutorial003b.py => tutorial003b_py39.py} (100%) rename docs_src/path_params/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/path_params/{tutorial005.py => tutorial005_py39.py} (100%) delete mode 100644 docs_src/path_params_numeric_validations/tutorial001_an.py rename docs_src/path_params_numeric_validations/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/path_params_numeric_validations/tutorial002_an.py rename docs_src/path_params_numeric_validations/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/path_params_numeric_validations/tutorial003_an.py rename docs_src/path_params_numeric_validations/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/path_params_numeric_validations/tutorial004_an.py rename docs_src/path_params_numeric_validations/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/path_params_numeric_validations/tutorial005_an.py rename docs_src/path_params_numeric_validations/{tutorial005.py => tutorial005_py39.py} (100%) delete mode 100644 docs_src/path_params_numeric_validations/tutorial006_an.py rename docs_src/path_params_numeric_validations/{tutorial006.py => tutorial006_py39.py} (100%) rename docs_src/pydantic_v1_in_v2/{tutorial001_an.py => tutorial001_an_py39.py} (100%) rename docs_src/pydantic_v1_in_v2/{tutorial002_an.py => tutorial002_an_py39.py} (100%) rename docs_src/pydantic_v1_in_v2/{tutorial003_an.py => tutorial003_an_py39.py} (100%) delete mode 100644 docs_src/pydantic_v1_in_v2/tutorial004_an.py rename docs_src/python_types/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/python_types/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/python_types/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/python_types/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/python_types/{tutorial005.py => tutorial005_py39.py} (100%) delete mode 100644 docs_src/python_types/tutorial006.py delete mode 100644 docs_src/python_types/tutorial007.py delete mode 100644 docs_src/python_types/tutorial008.py rename docs_src/python_types/{tutorial008b.py => tutorial008b_py39.py} (100%) rename docs_src/python_types/{tutorial009.py => tutorial009_py39.py} (100%) rename docs_src/python_types/{tutorial009b.py => tutorial009b_py39.py} (100%) rename docs_src/python_types/{tutorial009c.py => tutorial009c_py39.py} (100%) rename docs_src/python_types/{tutorial010.py => tutorial010_py39.py} (100%) delete mode 100644 docs_src/python_types/tutorial011.py rename docs_src/python_types/{tutorial012.py => tutorial012_py39.py} (100%) delete mode 100644 docs_src/python_types/tutorial013.py delete mode 100644 docs_src/query_param_models/tutorial001.py delete mode 100644 docs_src/query_param_models/tutorial001_an.py delete mode 100644 docs_src/query_param_models/tutorial002.py delete mode 100644 docs_src/query_param_models/tutorial002_an.py delete mode 100644 docs_src/query_param_models/tutorial002_pv1.py delete mode 100644 docs_src/query_param_models/tutorial002_pv1_an.py rename docs_src/query_params/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/query_params/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/query_params/{tutorial003.py => tutorial003_py39.py} (100%) rename docs_src/query_params/{tutorial004.py => tutorial004_py39.py} (100%) rename docs_src/query_params/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/query_params/{tutorial006.py => tutorial006_py39.py} (100%) delete mode 100644 docs_src/query_params/tutorial006b.py rename docs_src/query_params_str_validations/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/query_params_str_validations/{tutorial002_an.py => tutorial002_an_py39.py} (81%) rename docs_src/query_params_str_validations/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial003_an.py rename docs_src/query_params_str_validations/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial004_an.py rename docs_src/query_params_str_validations/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial005_an.py rename docs_src/query_params_str_validations/{tutorial005.py => tutorial005_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial006_an.py rename docs_src/query_params_str_validations/{tutorial006.py => tutorial006_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial006c_an.py rename docs_src/query_params_str_validations/{tutorial006c.py => tutorial006c_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial007_an.py rename docs_src/query_params_str_validations/{tutorial007.py => tutorial007_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial008_an.py rename docs_src/query_params_str_validations/{tutorial008.py => tutorial008_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial009_an.py rename docs_src/query_params_str_validations/{tutorial009.py => tutorial009_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial010_an.py rename docs_src/query_params_str_validations/{tutorial010.py => tutorial010_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial011.py delete mode 100644 docs_src/query_params_str_validations/tutorial011_an.py delete mode 100644 docs_src/query_params_str_validations/tutorial012.py delete mode 100644 docs_src/query_params_str_validations/tutorial012_an.py delete mode 100644 docs_src/query_params_str_validations/tutorial013_an.py rename docs_src/query_params_str_validations/{tutorial013.py => tutorial013_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial014_an.py rename docs_src/query_params_str_validations/{tutorial014.py => tutorial014_py39.py} (100%) delete mode 100644 docs_src/query_params_str_validations/tutorial015_an.py delete mode 100644 docs_src/request_files/tutorial001_02_an.py rename docs_src/request_files/{tutorial001_02.py => tutorial001_02_py39.py} (100%) delete mode 100644 docs_src/request_files/tutorial001_03_an.py rename docs_src/request_files/{tutorial001_03.py => tutorial001_03_py39.py} (100%) delete mode 100644 docs_src/request_files/tutorial001_an.py rename docs_src/request_files/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/request_files/tutorial002.py delete mode 100644 docs_src/request_files/tutorial002_an.py delete mode 100644 docs_src/request_files/tutorial003.py delete mode 100644 docs_src/request_files/tutorial003_an.py delete mode 100644 docs_src/request_form_models/tutorial001_an.py rename docs_src/request_form_models/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/request_form_models/tutorial002_an.py delete mode 100644 docs_src/request_form_models/tutorial002_pv1_an.py rename docs_src/request_form_models/{tutorial002_pv1.py => tutorial002_pv1_py39.py} (100%) rename docs_src/request_form_models/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/request_forms/tutorial001_an.py rename docs_src/request_forms/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/request_forms_and_files/tutorial001_an.py rename docs_src/request_forms_and_files/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/response_change_status_code/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/response_cookies/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/response_cookies/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/response_directly/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/response_directly/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/response_headers/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/response_headers/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/response_model/tutorial001.py delete mode 100644 docs_src/response_model/tutorial001_01.py rename docs_src/response_model/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/response_model/{tutorial003_01.py => tutorial003_01_py39.py} (100%) rename docs_src/response_model/{tutorial003_02.py => tutorial003_02_py39.py} (100%) rename docs_src/response_model/{tutorial003_03.py => tutorial003_03_py39.py} (100%) rename docs_src/response_model/{tutorial003_04.py => tutorial003_04_py39.py} (100%) rename docs_src/response_model/{tutorial003_05.py => tutorial003_05_py39.py} (100%) rename docs_src/response_model/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/response_model/tutorial004.py rename docs_src/response_model/{tutorial005.py => tutorial005_py39.py} (100%) rename docs_src/response_model/{tutorial006.py => tutorial006_py39.py} (100%) rename docs_src/response_status_code/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/response_status_code/{tutorial002.py => tutorial002_py39.py} (100%) rename docs_src/schema_extra_example/{tutorial001_pv1.py => tutorial001_pv1_py39.py} (100%) rename docs_src/schema_extra_example/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/schema_extra_example/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/schema_extra_example/tutorial003_an.py rename docs_src/schema_extra_example/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/schema_extra_example/tutorial004_an.py rename docs_src/schema_extra_example/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/schema_extra_example/tutorial005_an.py rename docs_src/schema_extra_example/{tutorial005.py => tutorial005_py39.py} (100%) delete mode 100644 docs_src/security/tutorial001_an.py rename docs_src/security/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/security/tutorial002_an.py rename docs_src/security/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/security/tutorial003_an.py rename docs_src/security/{tutorial003.py => tutorial003_py39.py} (100%) delete mode 100644 docs_src/security/tutorial004_an.py rename docs_src/security/{tutorial004.py => tutorial004_py39.py} (100%) delete mode 100644 docs_src/security/tutorial005.py delete mode 100644 docs_src/security/tutorial005_an.py delete mode 100644 docs_src/security/tutorial006_an.py rename docs_src/security/{tutorial006.py => tutorial006_py39.py} (100%) delete mode 100644 docs_src/security/tutorial007_an.py rename docs_src/security/{tutorial007.py => tutorial007_py39.py} (100%) delete mode 100644 docs_src/separate_openapi_schemas/tutorial001.py delete mode 100644 docs_src/separate_openapi_schemas/tutorial002.py rename docs_src/{bigger_applications/app_an/internal => settings/app01_py39}/__init__.py (100%) rename docs_src/settings/{app01 => app01_py39}/config.py (100%) rename docs_src/settings/{app01 => app01_py39}/main.py (100%) delete mode 100644 docs_src/settings/app02/__init__.py delete mode 100644 docs_src/settings/app02_an/__init__.py delete mode 100644 docs_src/settings/app02_an/config.py delete mode 100644 docs_src/settings/app02_an/main.py delete mode 100644 docs_src/settings/app02_an/test_main.py rename docs_src/{bigger_applications/app_an/routers => settings/app02_py39}/__init__.py (100%) rename docs_src/settings/{app02 => app02_py39}/config.py (100%) rename docs_src/settings/{app02 => app02_py39}/main.py (100%) rename docs_src/settings/{app02 => app02_py39}/test_main.py (100%) delete mode 100644 docs_src/settings/app03/__init__.py delete mode 100644 docs_src/settings/app03_an/__init__.py delete mode 100644 docs_src/settings/app03_an/config.py delete mode 100644 docs_src/settings/app03_an/config_pv1.py delete mode 100644 docs_src/settings/app03_an/main.py rename docs_src/settings/{app01 => app03_py39}/__init__.py (100%) rename docs_src/settings/{app03 => app03_py39}/config.py (100%) rename docs_src/settings/{app03 => app03_py39}/config_pv1.py (100%) rename docs_src/settings/{app03 => app03_py39}/main.py (100%) rename docs_src/settings/{tutorial001_pv1.py => tutorial001_pv1_py39.py} (100%) rename docs_src/settings/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/sql_databases/tutorial001.py delete mode 100644 docs_src/sql_databases/tutorial001_an.py delete mode 100644 docs_src/sql_databases/tutorial002.py delete mode 100644 docs_src/sql_databases/tutorial002_an.py rename docs_src/static_files/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/sub_applications/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/templates/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/using_request_directly/{tutorial001.py => tutorial001_py39.py} (100%) rename docs_src/websockets/{tutorial001.py => tutorial001_py39.py} (100%) delete mode 100644 docs_src/websockets/tutorial002_an.py rename docs_src/websockets/{tutorial002.py => tutorial002_py39.py} (100%) delete mode 100644 docs_src/websockets/tutorial003.py rename docs_src/wsgi/{tutorial001.py => tutorial001_py39.py} (100%) rename tests/test_tutorial/test_async_tests/{test_main.py => test_main_a.py} (58%) rename tests/test_tutorial/test_testing/{test_main.py => test_main_a.py} (90%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c430854265..8a839a928a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,13 +46,6 @@ jobs: python-version: [ "3.14" ] pydantic-version: [ "pydantic>=2.0.2,<3.0.0" ] include: - - os: macos-latest - python-version: "3.8" - pydantic-version: "pydantic>=1.10.0,<2.0.0" - - os: windows-latest - python-version: "3.8" - pydantic-version: "pydantic>=2.0.2,<3.0.0" - coverage: coverage - os: ubuntu-latest python-version: "3.9" pydantic-version: "pydantic>=1.10.0,<2.0.0" @@ -101,10 +94,6 @@ jobs: run: uv pip install -r requirements-tests.txt - name: Install Pydantic run: uv pip install "${{ matrix.pydantic-version }}" - # TODO: Remove this once Python 3.8 is no longer supported - - name: Install older AnyIO in Python 3.8 - if: matrix.python-version == '3.8' - run: uv pip install "anyio[trio]<4.0.0" - run: mkdir coverage - name: Test run: bash scripts/test.sh @@ -131,7 +120,7 @@ jobs: - uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: - python-version: '3.8' + python-version: '3.11' - name: Setup uv uses: astral-sh/setup-uv@v7 with: diff --git a/docs/de/docs/advanced/additional-responses.md b/docs/de/docs/advanced/additional-responses.md index 29a0a14777..0f9b122516 100644 --- a/docs/de/docs/advanced/additional-responses.md +++ b/docs/de/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Jedes dieser Response-`dict`s kann einen Schlüssel `model` haben, welcher ein P Um beispielsweise eine weitere Response mit dem Statuscode `404` und einem Pydantic-Modell `Message` zu deklarieren, können Sie schreiben: -{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} /// note | Hinweis @@ -203,7 +203,7 @@ Sie können beispielsweise eine Response mit dem Statuscode `404` deklarieren, d Und eine Response mit dem Statuscode `200`, die Ihr `response_model` verwendet, aber ein benutzerdefiniertes Beispiel (`example`) enthält: -{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} Es wird alles kombiniert und in Ihre OpenAPI eingebunden und in der API-Dokumentation angezeigt: diff --git a/docs/de/docs/advanced/async-tests.md b/docs/de/docs/advanced/async-tests.md index ad82452052..7206f136fd 100644 --- a/docs/de/docs/advanced/async-tests.md +++ b/docs/de/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Betrachten wir als einfaches Beispiel eine Dateistruktur ähnlich der in [Größ Die Datei `main.py` hätte als Inhalt: -{* ../../docs_src/async_tests/main.py *} +{* ../../docs_src/async_tests/app_a_py39/main.py *} Die Datei `test_main.py` hätte die Tests für `main.py`, das könnte jetzt so aussehen: -{* ../../docs_src/async_tests/test_main.py *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py *} ## Es ausführen { #run-it } @@ -56,7 +56,7 @@ $ pytest Der Marker `@pytest.mark.anyio` teilt pytest mit, dass diese Testfunktion asynchron aufgerufen werden soll: -{* ../../docs_src/async_tests/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} /// tip | Tipp @@ -66,7 +66,7 @@ Beachten Sie, dass die Testfunktion jetzt `async def` ist und nicht nur `def` wi Dann können wir einen `AsyncClient` mit der App erstellen und mit `await` asynchrone Requests an ihn senden. -{* ../../docs_src/async_tests/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} Das ist das Äquivalent zu: diff --git a/docs/de/docs/advanced/behind-a-proxy.md b/docs/de/docs/advanced/behind-a-proxy.md index 183d0beeef..1c74590503 100644 --- a/docs/de/docs/advanced/behind-a-proxy.md +++ b/docs/de/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Angenommen, Sie definieren eine *Pfadoperation* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} Wenn der Client versucht, zu `/items` zu gehen, würde er standardmäßig zu `/items/` umgeleitet. @@ -115,7 +115,7 @@ In diesem Fall würde der ursprüngliche Pfad `/app` tatsächlich unter `/api/v1 Auch wenn Ihr gesamter Code unter der Annahme geschrieben ist, dass es nur `/app` gibt. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} Und der Proxy würde das **Pfadpräfix** on-the-fly **„entfernen“**, bevor er den Request an den Anwendungsserver (wahrscheinlich Uvicorn via FastAPI CLI) übermittelt, dafür sorgend, dass Ihre Anwendung davon überzeugt ist, dass sie unter `/app` bereitgestellt wird, sodass Sie nicht Ihren gesamten Code dahingehend aktualisieren müssen, das Präfix `/api/v1` zu verwenden. @@ -193,7 +193,7 @@ Sie können den aktuellen `root_path` abrufen, der von Ihrer Anwendung für jede Hier fügen wir ihn, nur zu Demonstrationszwecken, in die Nachricht ein. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} Wenn Sie Uvicorn dann starten mit: @@ -220,7 +220,7 @@ wäre die Dictionary mit Modellen für maschinelles Lernen einfügen. Dieser Code wird ausgeführt, **bevor** die Anwendung **beginnt, Requests entgegenzunehmen**, während des *Startups*. @@ -48,7 +48,7 @@ Möglicherweise müssen Sie eine neue Version starten, oder Sie haben es einfach Das Erste, was auffällt, ist, dass wir eine asynchrone Funktion mit `yield` definieren. Das ist sehr ähnlich zu Abhängigkeiten mit `yield`. -{* ../../docs_src/events/tutorial003.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} Der erste Teil der Funktion, vor dem `yield`, wird ausgeführt **bevor** die Anwendung startet. @@ -60,7 +60,7 @@ Wie Sie sehen, ist die Funktion mit einem `@asynccontextmanager` versehen. Dadurch wird die Funktion in einen sogenannten „**asynchronen Kontextmanager**“ umgewandelt. -{* ../../docs_src/events/tutorial003.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} Ein **Kontextmanager** in Python ist etwas, das Sie in einer `with`-Anweisung verwenden können, zum Beispiel kann `open()` als Kontextmanager verwendet werden: @@ -82,7 +82,7 @@ In unserem obigen Codebeispiel verwenden wir ihn nicht direkt, sondern übergebe Der Parameter `lifespan` der `FastAPI`-App benötigt einen **asynchronen Kontextmanager**, wir können ihm also unseren neuen asynchronen Kontextmanager `lifespan` übergeben. -{* ../../docs_src/events/tutorial003.py hl[22] *} +{* ../../docs_src/events/tutorial003_py39.py hl[22] *} ## Alternative Events (deprecatet) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ Diese Funktionen können mit `async def` oder normalem `def` deklariert werden. Um eine Funktion hinzuzufügen, die vor dem Start der Anwendung ausgeführt werden soll, deklarieren Sie diese mit dem Event `startup`: -{* ../../docs_src/events/tutorial001.py hl[8] *} +{* ../../docs_src/events/tutorial001_py39.py hl[8] *} In diesem Fall initialisiert die Eventhandler-Funktion `startup` die „Datenbank“ der Items (nur ein `dict`) mit einigen Werten. @@ -116,7 +116,7 @@ Und Ihre Anwendung empfängt erst dann Requests, wenn alle `startup`-Eventhandle Um eine Funktion hinzuzufügen, die beim Shutdown der Anwendung ausgeführt werden soll, deklarieren Sie sie mit dem Event `shutdown`: -{* ../../docs_src/events/tutorial002.py hl[6] *} +{* ../../docs_src/events/tutorial002_py39.py hl[6] *} Hier schreibt die `shutdown`-Eventhandler-Funktion eine Textzeile `"Application shutdown"` in eine Datei `log.txt`. diff --git a/docs/de/docs/advanced/generate-clients.md b/docs/de/docs/advanced/generate-clients.md index d8836295b6..659343f5bc 100644 --- a/docs/de/docs/advanced/generate-clients.md +++ b/docs/de/docs/advanced/generate-clients.md @@ -167,7 +167,7 @@ Aber für den generierten Client könnten wir die OpenAPI-Operation-IDs direkt v Wir könnten das OpenAPI-JSON in eine Datei `openapi.json` herunterladen und dann mit einem Skript wie dem folgenden **den präfixierten Tag entfernen**: -{* ../../docs_src/generate_clients/tutorial004.py *} +{* ../../docs_src/generate_clients/tutorial004_py39.py *} //// tab | Node.js diff --git a/docs/de/docs/advanced/middleware.md b/docs/de/docs/advanced/middleware.md index 8396a626b6..ccc6a64c3b 100644 --- a/docs/de/docs/advanced/middleware.md +++ b/docs/de/docs/advanced/middleware.md @@ -57,13 +57,13 @@ Erzwingt, dass alle eingehenden geparst, sondern direkt als `bytes` gelesen und die Funktion `magic_data_reader()` wäre dafür verantwortlich, ihn in irgendeiner Weise zu parsen. diff --git a/docs/de/docs/advanced/response-change-status-code.md b/docs/de/docs/advanced/response-change-status-code.md index b079e241d1..b209c2d671 100644 --- a/docs/de/docs/advanced/response-change-status-code.md +++ b/docs/de/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion* Anschließend können Sie den `status_code` in diesem *vorübergehenden* Response-Objekt festlegen. -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} Und dann können Sie jedes benötigte Objekt zurückgeben, wie Sie es normalerweise tun würden (ein `dict`, ein Datenbankmodell usw.). diff --git a/docs/de/docs/advanced/response-cookies.md b/docs/de/docs/advanced/response-cookies.md index 02fe99c26a..87e636cfaf 100644 --- a/docs/de/docs/advanced/response-cookies.md +++ b/docs/de/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Sie können einen Parameter vom Typ `Response` in Ihrer *Pfadoperation-Funktion* Und dann können Sie Cookies in diesem *vorübergehenden* Response-Objekt setzen. -{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.). @@ -24,7 +24,7 @@ Dazu können Sie eine Response erstellen, wie unter [Eine Response direkt zurüc Setzen Sie dann Cookies darin und geben Sie sie dann zurück: -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} /// tip | Tipp diff --git a/docs/de/docs/advanced/response-directly.md b/docs/de/docs/advanced/response-directly.md index 06ec2c32ed..0a28a6d0e2 100644 --- a/docs/de/docs/advanced/response-directly.md +++ b/docs/de/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Nehmen wir an, Sie möchten eine Response-Objekt festlegen. -{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} +{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} Anschließend können Sie wie gewohnt jedes gewünschte Objekt zurückgeben (ein `dict`, ein Datenbankmodell, usw.). @@ -22,7 +22,7 @@ Sie können auch Header hinzufügen, wenn Sie eine `Response` direkt zurückgebe Erstellen Sie eine Response wie in [Eine Response direkt zurückgeben](response-directly.md){.internal-link target=_blank} beschrieben und übergeben Sie die Header als zusätzlichen Parameter: -{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} /// note | Technische Details diff --git a/docs/de/docs/advanced/settings.md b/docs/de/docs/advanced/settings.md index 03263a28b0..ebacf76f42 100644 --- a/docs/de/docs/advanced/settings.md +++ b/docs/de/docs/advanced/settings.md @@ -62,7 +62,7 @@ Sie können dieselben Validierungs-Funktionen und -Tools verwenden, die Sie für //// tab | Pydantic v2 -{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} //// @@ -74,7 +74,7 @@ In Pydantic v1 würden Sie `BaseSettings` direkt von `pydantic` statt von `pydan /// -{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} //// @@ -92,7 +92,7 @@ Als Nächstes werden die Daten konvertiert und validiert. Wenn Sie also dieses ` Dann können Sie das neue `settings`-Objekt in Ihrer Anwendung verwenden: -{* ../../docs_src/settings/tutorial001.py hl[18:20] *} +{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} ### Den Server ausführen { #run-the-server } @@ -126,11 +126,11 @@ Sie könnten diese Einstellungen in eine andere Moduldatei einfügen, wie Sie in Sie könnten beispielsweise eine Datei `config.py` haben mit: -{* ../../docs_src/settings/app01/config.py *} +{* ../../docs_src/settings/app01_py39/config.py *} Und dann verwenden Sie diese in einer Datei `main.py`: -{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} /// tip | Tipp diff --git a/docs/de/docs/advanced/sub-applications.md b/docs/de/docs/advanced/sub-applications.md index d634aac23b..081574d0a9 100644 --- a/docs/de/docs/advanced/sub-applications.md +++ b/docs/de/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ Wenn Sie zwei unabhängige FastAPI-Anwendungen mit deren eigenen unabhängigen O Erstellen Sie zunächst die Hauptanwendung **FastAPI** und deren *Pfadoperationen*: -{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} ### Unteranwendung { #sub-application } @@ -18,7 +18,7 @@ Erstellen Sie dann Ihre Unteranwendung und deren *Pfadoperationen*. Diese Unteranwendung ist nur eine weitere Standard-FastAPI-Anwendung, aber diese wird „gemountet“: -{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} ### Die Unteranwendung mounten { #mount-the-sub-application } @@ -26,7 +26,7 @@ Mounten Sie in Ihrer Top-Level-Anwendung `app` die Unteranwendung `subapi`. In diesem Fall wird sie im Pfad `/subapi` gemountet: -{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} ### Die automatische API-Dokumentation testen { #check-the-automatic-api-docs } diff --git a/docs/de/docs/advanced/templates.md b/docs/de/docs/advanced/templates.md index 65c7998b8a..97a45e6126 100644 --- a/docs/de/docs/advanced/templates.md +++ b/docs/de/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 * Deklarieren Sie einen `Request`-Parameter in der *Pfadoperation*, welcher ein Template zurückgibt. * Verwenden Sie die von Ihnen erstellten `templates`, um eine `TemplateResponse` zu rendern und zurückzugeben, übergeben Sie den Namen des Templates, das Requestobjekt und ein „Kontext“-Dictionary mit Schlüssel-Wert-Paaren, die innerhalb des Jinja2-Templates verwendet werden sollen. -{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} /// note | Hinweis diff --git a/docs/de/docs/advanced/testing-events.md b/docs/de/docs/advanced/testing-events.md index 569518c51d..5b12f3f186 100644 --- a/docs/de/docs/advanced/testing-events.md +++ b/docs/de/docs/advanced/testing-events.md @@ -2,11 +2,11 @@ Wenn Sie `lifespan` in Ihren Tests ausführen müssen, können Sie den `TestClient` mit einer `with`-Anweisung verwenden: -{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} Sie können mehr Details unter [„Lifespan in Tests ausführen in der offiziellen Starlette-Dokumentation.“](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) nachlesen. Für die deprecateten Events `startup` und `shutdown` können Sie den `TestClient` wie folgt verwenden: -{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} diff --git a/docs/de/docs/advanced/testing-websockets.md b/docs/de/docs/advanced/testing-websockets.md index f25aa4fd04..9ecca7a4f7 100644 --- a/docs/de/docs/advanced/testing-websockets.md +++ b/docs/de/docs/advanced/testing-websockets.md @@ -4,7 +4,7 @@ Sie können den schon bekannten `TestClient` zum Testen von WebSockets verwenden Dazu verwenden Sie den `TestClient` in einer `with`-Anweisung, eine Verbindung zum WebSocket herstellend: -{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} /// note | Hinweis diff --git a/docs/de/docs/advanced/using-request-directly.md b/docs/de/docs/advanced/using-request-directly.md index 8ec6741d07..36d73b8067 100644 --- a/docs/de/docs/advanced/using-request-directly.md +++ b/docs/de/docs/advanced/using-request-directly.md @@ -29,7 +29,7 @@ Angenommen, Sie möchten auf die IP-Adresse/den Host des Clients in Ihrer *Pfado Dazu müssen Sie direkt auf den Request zugreifen. -{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} Durch die Deklaration eines *Pfadoperation-Funktionsparameters*, dessen Typ der `Request` ist, weiß **FastAPI**, dass es den `Request` diesem Parameter übergeben soll. diff --git a/docs/de/docs/advanced/websockets.md b/docs/de/docs/advanced/websockets.md index 5f662770f0..05ae5a4b31 100644 --- a/docs/de/docs/advanced/websockets.md +++ b/docs/de/docs/advanced/websockets.md @@ -38,13 +38,13 @@ In der Produktion hätten Sie eine der oben genannten Optionen. Aber es ist der einfachste Weg, sich auf die Serverseite von WebSockets zu konzentrieren und ein funktionierendes Beispiel zu haben: -{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} ## Einen `websocket` erstellen { #create-a-websocket } Erstellen Sie in Ihrer **FastAPI**-Anwendung einen `websocket`: -{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} /// note | Technische Details @@ -58,7 +58,7 @@ Sie könnten auch `from starlette.websockets import WebSocket` verwenden. In Ihrer WebSocket-Route können Sie Nachrichten `await`en und Nachrichten senden. -{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} Sie können Binär-, Text- und JSON-Daten empfangen und senden. diff --git a/docs/de/docs/advanced/wsgi.md b/docs/de/docs/advanced/wsgi.md index 1de9739dde..3cd776a6ab 100644 --- a/docs/de/docs/advanced/wsgi.md +++ b/docs/de/docs/advanced/wsgi.md @@ -12,7 +12,7 @@ Wrappen Sie dann die WSGI-Anwendung (z. B. Flask) mit der Middleware. Und dann mounten Sie das auf einem Pfad. -{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} ## Es testen { #check-it } diff --git a/docs/de/docs/how-to/conditional-openapi.md b/docs/de/docs/how-to/conditional-openapi.md index f6a2fad3bc..6e665cc4c0 100644 --- a/docs/de/docs/how-to/conditional-openapi.md +++ b/docs/de/docs/how-to/conditional-openapi.md @@ -29,7 +29,7 @@ Sie können problemlos dieselben Pydantic-Einstellungen verwenden, um Ihre gener Zum Beispiel: -{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} Hier deklarieren wir die Einstellung `openapi_url` mit dem gleichen Defaultwert `"/openapi.json"`. diff --git a/docs/de/docs/how-to/configure-swagger-ui.md b/docs/de/docs/how-to/configure-swagger-ui.md index 3616f03ac4..1c3f5c0c5a 100644 --- a/docs/de/docs/how-to/configure-swagger-ui.md +++ b/docs/de/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ Ohne Änderung der Einstellungen ist die Syntaxhervorhebung standardmäßig akti Sie können sie jedoch deaktivieren, indem Sie `syntaxHighlight` auf `False` setzen: -{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} ... und dann zeigt die Swagger-Oberfläche die Syntaxhervorhebung nicht mehr an: @@ -28,7 +28,7 @@ Sie können sie jedoch deaktivieren, indem Sie `syntaxHighlight` auf `False` set Auf die gleiche Weise könnten Sie das Theme der Syntaxhervorhebung mit dem Schlüssel `"syntaxHighlight.theme"` festlegen (beachten Sie, dass er einen Punkt in der Mitte hat): -{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} Obige Konfiguration würde das Theme für die Farbe der Syntaxhervorhebung ändern: @@ -46,7 +46,7 @@ Sie können jede davon überschreiben, indem Sie im Argument `swagger_ui_paramet Um beispielsweise `deepLinking` zu deaktivieren, könnten Sie folgende Einstellungen an `swagger_ui_parameters` übergeben: -{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} ## Andere Parameter der Swagger-Oberfläche { #other-swagger-ui-parameters } diff --git a/docs/de/docs/how-to/custom-docs-ui-assets.md b/docs/de/docs/how-to/custom-docs-ui-assets.md index 6b8b1a1767..6b1d654adf 100644 --- a/docs/de/docs/how-to/custom-docs-ui-assets.md +++ b/docs/de/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Der erste Schritt besteht darin, die automatischen Dokumentationen zu deaktivier Um diese zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} ### Die benutzerdefinierten Dokumentationen hinzufügen { #include-the-custom-docs } @@ -34,7 +34,7 @@ Sie können die internen Funktionen von FastAPI wiederverwenden, um die HTML-Sei Und ähnlich für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} /// tip | Tipp @@ -50,7 +50,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} ### Es testen { #test-it } @@ -118,7 +118,7 @@ Danach könnte Ihre Dateistruktur wie folgt aussehen: * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} ### Die statischen Dateien testen { #test-the-static-files } @@ -144,7 +144,7 @@ Wie bei der Verwendung eines benutzerdefinierten CDN besteht der erste Schritt d Um sie zu deaktivieren, setzen Sie deren URLs beim Erstellen Ihrer `FastAPI`-App auf `None`: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} ### Die benutzerdefinierten Dokumentationen für statische Dateien hinzufügen { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Auch hier können Sie die internen Funktionen von FastAPI wiederverwenden, um di Und ähnlich für ReDoc ... -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} /// tip | Tipp @@ -176,7 +176,7 @@ Swagger UI erledigt das hinter den Kulissen für Sie, benötigt aber diesen „U Um nun testen zu können, ob alles funktioniert, erstellen Sie eine *Pfadoperation*: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} ### Benutzeroberfläche mit statischen Dateien testen { #test-static-files-ui } diff --git a/docs/de/docs/how-to/extending-openapi.md b/docs/de/docs/how-to/extending-openapi.md index 146ee098bc..c07ed2aa08 100644 --- a/docs/de/docs/how-to/extending-openapi.md +++ b/docs/de/docs/how-to/extending-openapi.md @@ -43,19 +43,19 @@ Fügen wir beispielsweise Requests verwendet. -{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} ### Die Methode überschreiben { #override-the-method } Jetzt können Sie die Methode `.openapi()` durch Ihre neue Funktion ersetzen. -{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} ### Es testen { #check-it } diff --git a/docs/de/docs/how-to/graphql.md b/docs/de/docs/how-to/graphql.md index d2958dcd9e..0583faf4a3 100644 --- a/docs/de/docs/how-to/graphql.md +++ b/docs/de/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Abhängig von Ihrem Anwendungsfall könnten Sie eine andere Bibliothek vorziehen Hier ist eine kleine Vorschau, wie Sie Strawberry mit FastAPI integrieren können: -{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} +{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *} Weitere Informationen zu Strawberry finden Sie in der Strawberry-Dokumentation. diff --git a/docs/de/docs/project-generation.md b/docs/de/docs/project-generation.md index 290f605b38..62702d8529 100644 --- a/docs/de/docs/project-generation.md +++ b/docs/de/docs/project-generation.md @@ -13,7 +13,7 @@ GitHub-Repository: Verkettet sie mit einem Leerzeichen in der Mitte. +* Verkettet sie mit einem Leerzeichen in der Mitte. -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} ### Es bearbeiten { #edit-it } @@ -78,7 +78,7 @@ Das war's. Das sind die „Typhinweise“: -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} Das ist nicht das gleiche wie das Deklarieren von Defaultwerten, wie es hier der Fall ist: @@ -106,7 +106,7 @@ Hier können Sie durch die Optionen blättern, bis Sie diejenige finden, bei der Sehen Sie sich diese Funktion an, sie hat bereits Typhinweise: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervollständigung, sondern auch eine Fehlerprüfung: @@ -114,7 +114,7 @@ Da der Editor die Typen der Variablen kennt, erhalten Sie nicht nur Code-Vervoll Jetzt, da Sie wissen, dass Sie das reparieren müssen, konvertieren Sie `age` mittels `str(age)` in einen String: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} ## Deklarieren von Typen { #declaring-types } @@ -133,7 +133,7 @@ Zum Beispiel diese: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} ### Generische Typen mit Typ-Parametern { #generic-types-with-type-parameters } @@ -161,56 +161,24 @@ Wenn Sie über die **neueste Version von Python** verfügen, verwenden Sie die B Definieren wir zum Beispiel eine Variable, die eine `list` von `str` – eine Liste von Strings – sein soll. -//// tab | Python 3.9+ - Deklarieren Sie die Variable mit der gleichen Doppelpunkt-Syntax (`:`). Als Typ nehmen Sie `list`. Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst: -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -Von `typing` importieren Sie `List` (mit Großbuchstaben `L`): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -Deklarieren Sie die Variable mit der gleichen Doppelpunkt-Syntax (`:`). - -Als Typ nehmen Sie das `List`, das Sie von `typing` importiert haben. - -Da die Liste ein Typ ist, welcher innere Typen enthält, werden diese von eckigen Klammern umfasst: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} /// info | Info Die inneren Typen in den eckigen Klammern werden als „Typ-Parameter“ bezeichnet. -In diesem Fall ist `str` der Typ-Parameter, der an `List` übergeben wird (oder `list` in Python 3.9 und darüber). +In diesem Fall ist `str` der Typ-Parameter, der an `list` übergeben wird. /// Das bedeutet: Die Variable `items` ist eine Liste – `list` – und jedes der Elemente in dieser Liste ist ein String – `str`. -/// tip | Tipp - -Wenn Sie Python 3.9 oder höher verwenden, müssen Sie `List` nicht von `typing` importieren, Sie können stattdessen den regulären `list`-Typ verwenden. - -/// - Auf diese Weise kann Ihr Editor Sie auch bei der Bearbeitung von Einträgen aus der Liste unterstützen: @@ -225,21 +193,7 @@ Und trotzdem weiß der Editor, dass es sich um ein `str` handelt, und bietet ent Das Gleiche gilt für die Deklaration eines Tupels – `tuple` – und einer Menge – `set`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} Das bedeutet: @@ -254,21 +208,7 @@ Der erste Typ-Parameter ist für die Schlüssel des `dict`. Der zweite Typ-Parameter ist für die Werte des `dict`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} Das bedeutet: @@ -282,7 +222,7 @@ Sie können deklarieren, dass eine Variable einer von **verschiedenen Typen** se In Python 3.6 und höher (inklusive Python 3.10) können Sie den `Union`-Typ von `typing` verwenden und die möglichen Typen innerhalb der eckigen Klammern auflisten. -In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die möglichen Typen getrennt von einem vertikalen Balken (`|`) aufzulisten. +In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die möglichen Typen getrennt von einem vertikalen Balken (`|`) aufzulisten. //// tab | Python 3.10+ @@ -292,10 +232,10 @@ In Python 3.10 gibt es zusätzlich eine **neue Syntax**, die es erlaubt, die mö //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b_py39.py!} ``` //// @@ -309,7 +249,7 @@ Sie können deklarieren, dass ein Wert ein `str`, aber vielleicht auch `None` se In Python 3.6 und darüber (inklusive Python 3.10) können Sie das deklarieren, indem Sie `Optional` vom `typing` Modul importieren und verwenden. ```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009_py39.py!} ``` Wenn Sie `Optional[str]` anstelle von nur `str` verwenden, wird Ihr Editor Ihnen dabei helfen, Fehler zu erkennen, bei denen Sie annehmen könnten, dass ein Wert immer eine String (`str`) ist, obwohl er auch `None` sein könnte. @@ -326,18 +266,18 @@ Das bedeutet auch, dass Sie in Python 3.10 `Something | None` verwenden können: //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009_py39.py!} ``` //// -//// tab | Python 3.8+ Alternative +//// tab | Python 3.9+ Alternative ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b_py39.py!} ``` //// @@ -353,11 +293,11 @@ Beide sind äquivalent und im Hintergrund dasselbe, aber ich empfehle `Union` st Ich denke, `Union[SomeType, None]` ist expliziter bezüglich seiner Bedeutung. -Es geht nur um Wörter und Namen. Aber diese Worte können beeinflussen, wie Sie und Ihre Teamkollegen über den Code denken. +Es geht nur um Worte und Namen. Aber diese Worte können beeinflussen, wie Sie und Ihre Teamkollegen über den Code denken. Nehmen wir zum Beispiel diese Funktion: -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} Der Parameter `name` ist definiert als `Optional[str]`, aber er ist **nicht optional**, Sie können die Funktion nicht ohne diesen Parameter aufrufen: @@ -390,13 +330,13 @@ Sie können die eingebauten Typen als Generics verwenden (mit eckigen Klammern u * `set` * `dict` -Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul: +Und ebenso wie bei früheren Python-Versionen, aus dem `typing`-Modul: * `Union` -* `Optional` (so wie unter Python 3.8) +* `Optional` * ... und andere. -In Python 3.10 können Sie als Alternative zu den Generics `Union` und `Optional` den vertikalen Balken (`|`) verwenden, um Vereinigungen von Typen zu deklarieren, das ist besser und einfacher. +In Python 3.10 können Sie als Alternative zu den Generics `Union` und `Optional` den vertikalen Balken (`|`) verwenden, um Vereinigungen von Typen zu deklarieren, das ist besser und einfacher. //// @@ -409,7 +349,7 @@ Sie können die eingebauten Typen als Generics verwenden (mit eckigen Klammern u * `set` * `dict` -Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul: +Und Generics aus dem `typing`-Modul: * `Union` * `Optional` @@ -417,29 +357,17 @@ Verwenden Sie für den Rest, wie unter Python 3.8, das `typing`-Modul: //// -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ... und andere. - -//// - ### Klassen als Typen { #classes-as-types } Sie können auch eine Klasse als Typ einer Variablen deklarieren. Nehmen wir an, Sie haben eine Klasse `Person`, mit einem Namen: -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} Dann können Sie eine Variable vom Typ `Person` deklarieren: -{* ../../docs_src/python_types/tutorial010.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} Und wiederum bekommen Sie die volle Editor-Unterstützung: @@ -463,29 +391,7 @@ Und Sie erhalten volle Editor-Unterstützung für dieses Objekt. Ein Beispiel aus der offiziellen Pydantic Dokumentation: -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial011_py310.py *} /// info | Info @@ -507,27 +413,9 @@ Pydantic verhält sich speziell, wenn Sie `Optional` oder `Union[Something, None Python bietet auch die Möglichkeit, **zusätzliche Metadaten** in Typhinweisen unterzubringen, mittels `Annotated`. -//// tab | Python 3.9+ +Seit Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren. -In Python 3.9 ist `Annotated` ein Teil der Standardbibliothek, Sie können es von `typing` importieren. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -In Versionen niedriger als Python 3.9 importieren Sie `Annotated` von `typing_extensions`. - -Es wird bereits mit **FastAPI** installiert sein. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} Python selbst macht nichts mit `Annotated`. Für Editoren und andere Tools ist der Typ immer noch `str`. diff --git a/docs/de/docs/tutorial/background-tasks.md b/docs/de/docs/tutorial/background-tasks.md index 2c381ccfac..1d34430dc6 100644 --- a/docs/de/docs/tutorial/background-tasks.md +++ b/docs/de/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Hierzu zählen beispielsweise: Importieren Sie zunächst `BackgroundTasks` und definieren Sie einen Parameter in Ihrer *Pfadoperation-Funktion* mit der Typdeklaration `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} **FastAPI** erstellt für Sie das Objekt vom Typ `BackgroundTasks` und übergibt es als diesen Parameter. @@ -31,13 +31,13 @@ In diesem Fall schreibt die Taskfunktion in eine Datei (den Versand einer E-Mail Und da der Schreibvorgang nicht `async` und `await` verwendet, definieren wir die Funktion mit normalem `def`: -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} ## Den Hintergrundtask hinzufügen { #add-the-background-task } Übergeben Sie innerhalb Ihrer *Pfadoperation-Funktion* Ihre Taskfunktion mit der Methode `.add_task()` an das *Hintergrundtasks*-Objekt: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` erhält als Argumente: diff --git a/docs/de/docs/tutorial/body-nested-models.md b/docs/de/docs/tutorial/body-nested-models.md index 324d31928d..65a5d7c1de 100644 --- a/docs/de/docs/tutorial/body-nested-models.md +++ b/docs/de/docs/tutorial/body-nested-models.md @@ -14,35 +14,14 @@ Das bewirkt, dass `tags` eine Liste ist, wenngleich es nichts über den Typ der Aber Python erlaubt es, Listen mit inneren Typen, auch „Typ-Parameter“ genannt, zu deklarieren. -### `List` von `typing` importieren { #import-typings-list } - -In Python 3.9 oder darüber können Sie einfach `list` verwenden, um diese Typannotationen zu deklarieren, wie wir unten sehen werden. 💡 - -In Python-Versionen vor 3.9 (3.6 und darüber), müssen Sie zuerst `List` von Pythons Standardmodul `typing` importieren. - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - ### Eine `list` mit einem Typ-Parameter deklarieren { #declare-a-list-with-a-type-parameter } -Um Typen wie `list`, `dict`, `tuple` mit inneren Typ-Parametern (inneren Typen) zu deklarieren: - -* Wenn Sie eine Python-Version kleiner als 3.9 verwenden, importieren Sie das Äquivalent zum entsprechenden Typ vom `typing`-Modul -* Überreichen Sie den/die inneren Typ(en) von eckigen Klammern umschlossen, `[` und `]`, als „Typ-Parameter“ - -In Python 3.9 wäre das: +Um Typen zu deklarieren, die Typ-Parameter (innere Typen) haben, wie `list`, `dict`, `tuple`, übergeben Sie den/die inneren Typ(en) als „Typ-Parameter“ in eckigen Klammern: `[` und `]` ```Python my_list: list[str] ``` -Und in Python-Versionen vor 3.9: - -```Python -from typing import List - -my_list: List[str] -``` - Das ist alles Standard-Python-Syntax für Typdeklarationen. Verwenden Sie dieselbe Standardsyntax für Modellattribute mit inneren Typen. @@ -178,12 +157,6 @@ Beachten Sie, wie `Offer` eine Liste von `Item`s hat, die ihrerseits eine option Wenn das äußerste Element des JSON-Bodys, das Sie erwarten, ein JSON-`array` (eine Python-`list`) ist, können Sie den Typ im Funktionsparameter deklarieren, mit der gleichen Syntax wie in Pydantic-Modellen: -```Python -images: List[Image] -``` - -oder in Python 3.9 und darüber: - ```Python images: list[Image] ``` diff --git a/docs/de/docs/tutorial/body.md b/docs/de/docs/tutorial/body.md index 1e6382b6f2..0ad95b0386 100644 --- a/docs/de/docs/tutorial/body.md +++ b/docs/de/docs/tutorial/body.md @@ -162,7 +162,7 @@ Die Funktionsparameter werden wie folgt erkannt: FastAPI weiß, dass der Wert von `q` nicht erforderlich ist, aufgrund des definierten Defaultwertes `= None`. -Das `str | None` (Python 3.10+) oder `Union` in `Union[str, None]` (Python 3.8+) wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat. +Das `str | None` (Python 3.10+) oder `Union` in `Union[str, None]` (Python 3.9+) wird von FastAPI nicht verwendet, um zu bestimmen, dass der Wert nicht erforderlich ist. FastAPI weiß, dass er nicht erforderlich ist, weil er einen Defaultwert von `= None` hat. Das Hinzufügen der Typannotationen ermöglicht jedoch Ihrem Editor, Ihnen eine bessere Unterstützung zu bieten und Fehler zu erkennen. diff --git a/docs/de/docs/tutorial/cors.md b/docs/de/docs/tutorial/cors.md index 191a7b4ef3..81f0f36051 100644 --- a/docs/de/docs/tutorial/cors.md +++ b/docs/de/docs/tutorial/cors.md @@ -46,7 +46,7 @@ Sie können auch angeben, ob Ihr Backend erlaubt: * Bestimmte HTTP-Methoden (`POST`, `PUT`) oder alle mit der Wildcard `"*"`. * Bestimmte HTTP-Header oder alle mit der Wildcard `"*"`. -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} Die von der `CORSMiddleware`-Implementierung verwendeten Defaultparameter sind standardmäßig restriktiv, daher müssen Sie bestimmte Origins, Methoden oder Header ausdrücklich aktivieren, damit Browser sie in einem Cross-Domain-Kontext verwenden dürfen. diff --git a/docs/de/docs/tutorial/debugging.md b/docs/de/docs/tutorial/debugging.md index 0a31f86536..0d12877c10 100644 --- a/docs/de/docs/tutorial/debugging.md +++ b/docs/de/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Sie können den Debugger in Ihrem Editor verbinden, zum Beispiel mit Visual Stud Importieren und führen Sie `uvicorn` direkt in Ihrer FastAPI-Anwendung aus: -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} ### Über `__name__ == "__main__"` { #about-name-main } diff --git a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md index 3d4493f353..7df0842eb1 100644 --- a/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/de/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Jetzt können Sie Ihre Abhängigkeit mithilfe dieser Klasse deklarieren. Beachten Sie, wie wir `CommonQueryParams` im obigen Code zweimal schreiben: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ nicht annotiert +//// tab | Python 3.9+ nicht annotiert /// tip | Tipp @@ -137,7 +137,7 @@ Aus diesem extrahiert FastAPI die deklarierten Parameter, und dieses ist es, was In diesem Fall hat das erste `CommonQueryParams` in: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.8+ nicht annotiert +//// tab | Python 3.9+ nicht annotiert /// tip | Tipp @@ -163,7 +163,7 @@ commons: CommonQueryParams ... Sie könnten tatsächlich einfach schreiben: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ nicht annotiert +//// tab | Python 3.9+ nicht annotiert /// tip | Tipp @@ -197,7 +197,7 @@ Es wird jedoch empfohlen, den Typ zu deklarieren, da Ihr Editor so weiß, was al Aber Sie sehen, dass wir hier etwas Codeduplizierung haben, indem wir `CommonQueryParams` zweimal schreiben: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ nicht annotiert +//// tab | Python 3.9+ nicht annotiert /// tip | Tipp @@ -225,7 +225,7 @@ In diesem speziellen Fall können Sie Folgendes tun: Anstatt zu schreiben: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ nicht annotiert +//// tab | Python 3.9+ nicht annotiert /// tip | Tipp @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ... schreiben Sie: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.8 nicht annotiert +//// tab | Python 3.9+ nicht annotiert /// tip | Tipp diff --git a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md index 34db6c6bed..0083e7e7ea 100644 --- a/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/de/docs/tutorial/dependencies/dependencies-with-yield.md @@ -29,15 +29,15 @@ Sie könnten damit beispielsweise eine Datenbanksession erstellen und diese nach Nur der Code vor und einschließlich der `yield`-Anweisung wird ausgeführt, bevor eine Response erzeugt wird: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} Der ge`yield`ete Wert ist das, was in *Pfadoperationen* und andere Abhängigkeiten eingefügt wird: -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} Der auf die `yield`-Anweisung folgende Code wird nach der Response ausgeführt: -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} /// tip | Tipp @@ -57,7 +57,7 @@ Sie können also mit `except SomeException` diese bestimmte Exception innerhalb Auf die gleiche Weise können Sie `finally` verwenden, um sicherzustellen, dass die Exit-Schritte ausgeführt werden, unabhängig davon, ob eine Exception geworfen wurde oder nicht. -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} ## Unterabhängigkeiten mit `yield` { #sub-dependencies-with-yield } @@ -268,7 +268,7 @@ In Python können Sie Kontextmanager erstellen, indem Sie Requests zuständig ist, die an: @@ -320,7 +320,7 @@ Das ist unsere „**Pfadoperation-Funktion**“: * **Operation**: ist `get`. * **Funktion**: ist die Funktion direkt unter dem „Dekorator“ (unter `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} Dies ist eine Python-Funktion. @@ -332,7 +332,7 @@ In diesem Fall handelt es sich um eine `async`-Funktion. Sie könnten sie auch als normale Funktion anstelle von `async def` definieren: -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note | Hinweis @@ -342,7 +342,7 @@ Wenn Sie den Unterschied nicht kennen, lesen Sie [Async: *„In Eile?“*](../as ### Schritt 5: den Inhalt zurückgeben { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} Sie können ein `dict`, eine `list`, einzelne Werte wie `str`, `int`, usw. zurückgeben. diff --git a/docs/de/docs/tutorial/handling-errors.md b/docs/de/docs/tutorial/handling-errors.md index a39c3db37a..d890b44629 100644 --- a/docs/de/docs/tutorial/handling-errors.md +++ b/docs/de/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ Um HTTP-deprecatet kennzeichnen möchten, ohne sie zu entfernen, fügen Sie den Parameter `deprecated` hinzu: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} Sie wird in der interaktiven Dokumentation gut sichtbar als deprecatet markiert werden: diff --git a/docs/de/docs/tutorial/path-params-numeric-validations.md b/docs/de/docs/tutorial/path-params-numeric-validations.md index 5b74749447..8b52e8b42f 100644 --- a/docs/de/docs/tutorial/path-params-numeric-validations.md +++ b/docs/de/docs/tutorial/path-params-numeric-validations.md @@ -54,7 +54,7 @@ Für **FastAPI** spielt es keine Rolle. Es erkennt die Parameter anhand ihrer Na Sie können Ihre Funktion also so deklarieren: -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} Aber bedenken Sie, dass Sie dieses Problem nicht haben, wenn Sie `Annotated` verwenden, da es nicht darauf ankommt, dass Sie keine Funktionsparameter-Defaultwerte für `Query()` oder `Path()` verwenden. @@ -83,7 +83,7 @@ Wenn Sie: Python wird nichts mit diesem `*` machen, aber es wird wissen, dass alle folgenden Parameter als Schlüsselwortargumente (Schlüssel-Wert-Paare) verwendet werden sollen, auch bekannt als kwargs. Selbst wenn diese keinen Defaultwert haben. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} ### Besser mit `Annotated` { #better-with-annotated } diff --git a/docs/de/docs/tutorial/path-params.md b/docs/de/docs/tutorial/path-params.md index 1db288fb87..1de4973155 100644 --- a/docs/de/docs/tutorial/path-params.md +++ b/docs/de/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Sie können Pfad-„Parameter“ oder -„Variablen“ mit der gleichen Syntax deklarieren, welche in Python-Formatstrings verwendet wird: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} Der Wert des Pfad-Parameters `item_id` wird Ihrer Funktion als das Argument `item_id` übergeben. @@ -16,7 +16,7 @@ Wenn Sie dieses Beispiel ausführen und auf Enumerationen (oder Enums) gibt es in Python seit Version 3.4. - -/// /// tip | Tipp @@ -158,7 +153,7 @@ Falls Sie sich fragen, was „AlexNet“, „ResNet“ und „LeNet“ ist, das Dann erstellen Sie einen *Pfad-Parameter*, der als Typ die gerade erstellte Enum-Klasse hat (`ModelName`): -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} ### Die API-Dokumentation testen { #check-the-docs } @@ -174,13 +169,13 @@ Der *Pfad-Parameter* wird ein *Query ist die Menge von Schlüssel-Wert-Paaren, die nach dem `?` in einer URL folgen und durch `&`-Zeichen getrennt sind. @@ -127,7 +127,7 @@ Wenn Sie keinen spezifischen Wert haben wollen, sondern der Parameter einfach op Aber wenn Sie wollen, dass ein Query-Parameter erforderlich ist, vergeben Sie einfach keinen Defaultwert: -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} Hier ist `needy` ein erforderlicher Query-Parameter vom Typ `str`. diff --git a/docs/de/docs/tutorial/response-model.md b/docs/de/docs/tutorial/response-model.md index 7b77125cb5..4c0205b31f 100644 --- a/docs/de/docs/tutorial/response-model.md +++ b/docs/de/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ Es kann Fälle geben, bei denen Sie etwas zurückgeben, das kein gültiges Pydan Der häufigste Anwendungsfall ist, wenn Sie [eine Response direkt zurückgeben, wie es später im Handbuch für fortgeschrittene Benutzer erläutert wird](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} Dieser einfache Anwendungsfall wird automatisch von FastAPI gehandhabt, weil die Annotation des Rückgabetyps die Klasse (oder eine Unterklasse von) `Response` ist. @@ -193,7 +193,7 @@ Und Tools werden auch glücklich sein, weil sowohl `RedirectResponse` als auch ` Sie können auch eine Unterklasse von `Response` in der Typannotation verwenden. -{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} Das wird ebenfalls funktionieren, weil `RedirectResponse` eine Unterklasse von `Response` ist, und FastAPI sich um diesen einfachen Anwendungsfall automatisch kümmert. diff --git a/docs/de/docs/tutorial/response-status-code.md b/docs/de/docs/tutorial/response-status-code.md index 928003c3fc..fd17c99336 100644 --- a/docs/de/docs/tutorial/response-status-code.md +++ b/docs/de/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ Genauso wie Sie ein Responsemodell angeben können, können Sie auch den HTTP-St * `@app.delete()` * usw. -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} /// note | Hinweis @@ -74,7 +74,7 @@ Um mehr über die einzelnen Statuscodes zu erfahren und welcher wofür verwendet Lassen Sie uns das vorherige Beispiel noch einmal anschauen: -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} `201` ist der Statuscode für „Created“ („Erzeugt“). @@ -82,7 +82,7 @@ Aber Sie müssen sich nicht merken, was jeder dieser Codes bedeutet. Sie können die Annehmlichkeit von Variablen aus `fastapi.status` nutzen. -{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} Diese sind nur eine Annehmlichkeit, sie enthalten dieselbe Zahl, aber so können Sie die Autovervollständigung Ihres Editors verwenden, um sie zu finden: diff --git a/docs/de/docs/tutorial/static-files.md b/docs/de/docs/tutorial/static-files.md index 0c4e7c8abd..9ba2501756 100644 --- a/docs/de/docs/tutorial/static-files.md +++ b/docs/de/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ Mit `StaticFiles` können Sie statische Dateien aus einem Verzeichnis automatisc * Importieren Sie `StaticFiles`. * „Mounten“ Sie eine `StaticFiles()`-Instanz in einem bestimmten Pfad. -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} /// note | Technische Details diff --git a/docs/de/docs/tutorial/testing.md b/docs/de/docs/tutorial/testing.md index b18469998d..d889b1e1fb 100644 --- a/docs/de/docs/tutorial/testing.md +++ b/docs/de/docs/tutorial/testing.md @@ -30,7 +30,7 @@ Verwenden Sie das `TestClient`-Objekt auf die gleiche Weise wie `httpx`. Schreiben Sie einfache `assert`-Anweisungen mit den Standard-Python-Ausdrücken, die Sie überprüfen müssen (wiederum, Standard-`pytest`). -{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} /// tip | Tipp @@ -76,7 +76,7 @@ Nehmen wir an, Sie haben eine Dateistruktur wie in [Größere Anwendungen](bigge In der Datei `main.py` haben Sie Ihre **FastAPI**-Anwendung: -{* ../../docs_src/app_testing/main.py *} +{* ../../docs_src/app_testing/app_a_py39/main.py *} ### Testdatei { #testing-file } @@ -93,7 +93,7 @@ Dann könnten Sie eine Datei `test_main.py` mit Ihren Tests haben. Sie könnte s Da sich diese Datei im selben Package befindet, können Sie relative Importe verwenden, um das Objekt `app` aus dem `main`-Modul (`main.py`) zu importieren: -{* ../../docs_src/app_testing/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} ... und haben den Code für die Tests wie zuvor. diff --git a/docs/en/docs/advanced/additional-responses.md b/docs/en/docs/advanced/additional-responses.md index cb3a40d13e..bb70753edd 100644 --- a/docs/en/docs/advanced/additional-responses.md +++ b/docs/en/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Each of those response `dict`s can have a key `model`, containing a Pydantic mod For example, to declare another response with a status code `404` and a Pydantic model `Message`, you can write: -{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} /// note @@ -203,7 +203,7 @@ For example, you can declare a response with a status code `404` that uses a Pyd And a response with a status code `200` that uses your `response_model`, but includes a custom `example`: -{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} It will all be combined and included in your OpenAPI, and shown in the API docs: diff --git a/docs/en/docs/advanced/async-tests.md b/docs/en/docs/advanced/async-tests.md index e920e22c3c..65ddc60b2d 100644 --- a/docs/en/docs/advanced/async-tests.md +++ b/docs/en/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ For a simple example, let's consider a file structure similar to the one describ The file `main.py` would have: -{* ../../docs_src/async_tests/main.py *} +{* ../../docs_src/async_tests/app_a_py39/main.py *} The file `test_main.py` would have the tests for `main.py`, it could look like this now: -{* ../../docs_src/async_tests/test_main.py *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py *} ## Run it { #run-it } @@ -56,7 +56,7 @@ $ pytest The marker `@pytest.mark.anyio` tells pytest that this test function should be called asynchronously: -{* ../../docs_src/async_tests/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} /// tip @@ -66,7 +66,7 @@ Note that the test function is now `async def` instead of just `def` as before w Then we can create an `AsyncClient` with the app, and send async requests to it, using `await`. -{* ../../docs_src/async_tests/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} This is the equivalent to: diff --git a/docs/en/docs/advanced/behind-a-proxy.md b/docs/en/docs/advanced/behind-a-proxy.md index f4dbd45607..4fef02bd1c 100644 --- a/docs/en/docs/advanced/behind-a-proxy.md +++ b/docs/en/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" For example, let's say you define a *path operation* `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} If the client tries to go to `/items`, by default, it would be redirected to `/items/`. @@ -115,7 +115,7 @@ In this case, the original path `/app` would actually be served at `/api/v1/app` Even though all your code is written assuming there's just `/app`. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`. @@ -193,7 +193,7 @@ You can get the current `root_path` used by your application for each request, i Here we are including it in the message just for demonstration purposes. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} Then, if you start Uvicorn with: @@ -220,7 +220,7 @@ The response would be something like: Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app: -{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn. @@ -400,7 +400,7 @@ If you pass a custom list of `servers` and there's a `root_path` (because your A For example: -{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} Will generate an OpenAPI schema like: @@ -455,7 +455,7 @@ If you don't specify the `servers` parameter and `root_path` is equal to `/`, th If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} and then it won't include it in the OpenAPI schema. diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index 0f3d8b7017..e53409c39d 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ This is because by default, FastAPI will inspect every item inside and make sure But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class. -{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} /// info @@ -55,7 +55,7 @@ To return a response with HTML directly from **FastAPI**, use `HTMLResponse`. * Import `HTMLResponse`. * Pass `HTMLResponse` as the parameter `response_class` of your *path operation decorator*. -{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} /// info @@ -73,7 +73,7 @@ As seen in [Return a Response directly](response-directly.md){.internal-link tar The same example from above, returning an `HTMLResponse`, could look like: -{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} /// warning @@ -97,7 +97,7 @@ The `response_class` will then be used only to document the OpenAPI *path operat For example, it could be something like: -{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} In this example, the function `generate_html_response()` already generates and returns a `Response` instead of returning the HTML in a `str`. @@ -136,7 +136,7 @@ It accepts the following parameters: FastAPI (actually Starlette) will automatically include a Content-Length header. It will also include a Content-Type header, based on the `media_type` and appending a charset for text types. -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ Takes some text or bytes and returns an HTML response, as you read above. Takes some text or bytes and returns a plain text response. -{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ This requires installing `ujson` for example with `pip install ujson`. /// -{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} /// tip @@ -194,14 +194,14 @@ Returns an HTTP redirect. Uses a 307 status code (Temporary Redirect) by default You can return a `RedirectResponse` directly: -{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} --- Or you can use it in the `response_class` parameter: -{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} If you do that, then you can return the URL directly from your *path operation* function. @@ -211,13 +211,13 @@ In this case, the `status_code` used will be the default one for the `RedirectRe You can also use the `status_code` parameter combined with the `response_class` parameter: -{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Takes an async generator or a normal generator/iterator and streams the response body. -{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} #### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects } @@ -227,7 +227,7 @@ That way, you don't have to read it all first in memory, and you can pass that g This includes many libraries to interact with cloud storage, video processing, and others. -{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} 1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. 2. By using a `with` block, we make sure that the file-like object is closed after the generator function is done. So, after it finishes sending the response. @@ -256,11 +256,11 @@ Takes a different set of arguments to instantiate than the other response types: File responses will include appropriate `Content-Length`, `Last-Modified` and `ETag` headers. -{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} You can also use the `response_class` parameter: -{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} In this case, you can return the file path directly from your *path operation* function. @@ -274,7 +274,7 @@ Let's say you want it to return indented and formatted JSON, so you want to use You could create a `CustomORJSONResponse`. The main thing you have to do is create a `Response.render(content)` method that returns the content as `bytes`: -{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} Now instead of returning: @@ -300,7 +300,7 @@ The parameter that defines this is `default_response_class`. In the example below, **FastAPI** will use `ORJSONResponse` by default, in all *path operations*, instead of `JSONResponse`. -{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} /// tip diff --git a/docs/en/docs/advanced/events.md b/docs/en/docs/advanced/events.md index d9e3cb52e7..9414b7a3f0 100644 --- a/docs/en/docs/advanced/events.md +++ b/docs/en/docs/advanced/events.md @@ -30,7 +30,7 @@ Let's start with an example and then see it in detail. We create an async function `lifespan()` with `yield` like this: -{* ../../docs_src/events/tutorial003.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} Here we are simulating the expensive *startup* operation of loading the model by putting the (fake) model function in the dictionary with machine learning models before the `yield`. This code will be executed **before** the application **starts taking requests**, during the *startup*. @@ -48,7 +48,7 @@ Maybe you need to start a new version, or you just got tired of running it. 🤷 The first thing to notice, is that we are defining an async function with `yield`. This is very similar to Dependencies with `yield`. -{* ../../docs_src/events/tutorial003.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} The first part of the function, before the `yield`, will be executed **before** the application starts. @@ -60,7 +60,7 @@ If you check, the function is decorated with an `@asynccontextmanager`. That converts the function into something called an "**async context manager**". -{* ../../docs_src/events/tutorial003.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} A **context manager** in Python is something that you can use in a `with` statement, for example, `open()` can be used as a context manager: @@ -82,7 +82,7 @@ In our code example above, we don't use it directly, but we pass it to FastAPI f The `lifespan` parameter of the `FastAPI` app takes an **async context manager**, so we can pass our new `lifespan` async context manager to it. -{* ../../docs_src/events/tutorial003.py hl[22] *} +{* ../../docs_src/events/tutorial003_py39.py hl[22] *} ## Alternative Events (deprecated) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ These functions can be declared with `async def` or normal `def`. To add a function that should be run before the application starts, declare it with the event `"startup"`: -{* ../../docs_src/events/tutorial001.py hl[8] *} +{* ../../docs_src/events/tutorial001_py39.py hl[8] *} In this case, the `startup` event handler function will initialize the items "database" (just a `dict`) with some values. @@ -116,7 +116,7 @@ And your application won't start receiving requests until all the `startup` even To add a function that should be run when the application is shutting down, declare it with the event `"shutdown"`: -{* ../../docs_src/events/tutorial002.py hl[6] *} +{* ../../docs_src/events/tutorial002_py39.py hl[6] *} Here, the `shutdown` event handler function will write a text line `"Application shutdown"` to a file `log.txt`. diff --git a/docs/en/docs/advanced/generate-clients.md b/docs/en/docs/advanced/generate-clients.md index 897c308086..2d0c2aa0c9 100644 --- a/docs/en/docs/advanced/generate-clients.md +++ b/docs/en/docs/advanced/generate-clients.md @@ -167,7 +167,7 @@ But for the generated client, we could **modify** the OpenAPI operation IDs righ We could download the OpenAPI JSON to a file `openapi.json` and then we could **remove that prefixed tag** with a script like this: -{* ../../docs_src/generate_clients/tutorial004.py *} +{* ../../docs_src/generate_clients/tutorial004_py39.py *} //// tab | Node.js diff --git a/docs/en/docs/advanced/middleware.md b/docs/en/docs/advanced/middleware.md index 8deb0d917d..765b389329 100644 --- a/docs/en/docs/advanced/middleware.md +++ b/docs/en/docs/advanced/middleware.md @@ -57,13 +57,13 @@ Enforces that all incoming requests must either be `https` or `wss`. Any incoming request to `http` or `ws` will be redirected to the secure scheme instead. -{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } Enforces that all incoming requests have a correctly set `Host` header, in order to guard against HTTP Host Header attacks. -{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} The following arguments are supported: @@ -78,7 +78,7 @@ Handles GZip responses for any request that includes `"gzip"` in the `Accept-Enc The middleware will handle both standard and streaming responses. -{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} The following arguments are supported: diff --git a/docs/en/docs/advanced/openapi-webhooks.md b/docs/en/docs/advanced/openapi-webhooks.md index 416cf4b75b..59f060c032 100644 --- a/docs/en/docs/advanced/openapi-webhooks.md +++ b/docs/en/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ Webhooks are available in OpenAPI 3.1.0 and above, supported by FastAPI `0.99.0` When you create a **FastAPI** application, there is a `webhooks` attribute that you can use to define *webhooks*, the same way you would define *path operations*, for example with `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} The webhooks that you define will end up in the **OpenAPI** schema and the automatic **docs UI**. diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index 5879bc5c71..01196af79e 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ You can set the OpenAPI `operationId` to be used in your *path operation* with t You would have to make sure that it is unique for each operation. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} ### Using the *path operation function* name as the operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ If you want to use your APIs' function names as `operationId`s, you can iterate You should do it after adding all your *path operations*. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} /// tip @@ -40,7 +40,7 @@ Even if they are in different modules (Python files). To exclude a *path operation* from the generated OpenAPI schema (and thus, from the automatic documentation systems), use the parameter `include_in_schema` and set it to `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} ## Advanced description from docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ You can extend the OpenAPI schema for a *path operation* using the parameter `op This `openapi_extra` can be helpful, for example, to declare [OpenAPI Extensions](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} If you open the automatic API docs, your extension will show up at the bottom of the specific *path operation*. @@ -139,7 +139,7 @@ For example, you could decide to read and validate the request with your own cod You could do that with `openapi_extra`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} In this example, we didn't declare any Pydantic model. In fact, the request body is not even parsed as JSON, it is read directly as `bytes`, and the function `magic_data_reader()` would be in charge of parsing it in some way. diff --git a/docs/en/docs/advanced/response-change-status-code.md b/docs/en/docs/advanced/response-change-status-code.md index 912ed0f1a1..d9708aa627 100644 --- a/docs/en/docs/advanced/response-change-status-code.md +++ b/docs/en/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ You can declare a parameter of type `Response` in your *path operation function* And then you can set the `status_code` in that *temporal* response object. -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} And then you can return any object you need, as you normally would (a `dict`, a database model, etc). diff --git a/docs/en/docs/advanced/response-cookies.md b/docs/en/docs/advanced/response-cookies.md index 1f41d84b7c..5b6fab112b 100644 --- a/docs/en/docs/advanced/response-cookies.md +++ b/docs/en/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ You can declare a parameter of type `Response` in your *path operation function* And then you can set cookies in that *temporal* response object. -{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} And then you can return any object you need, as you normally would (a `dict`, a database model, etc). @@ -24,7 +24,7 @@ To do that, you can create a response as described in [Return a Response Directl Then set Cookies in it, and then return it: -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} /// tip diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 156b4dac7e..4374cb9637 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/docs/en/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Let's say that you want to return an Strawberry documentation. diff --git a/docs/en/docs/management-tasks.md b/docs/en/docs/management-tasks.md index 05cd5d27d0..aac4d6fe40 100644 --- a/docs/en/docs/management-tasks.md +++ b/docs/en/docs/management-tasks.md @@ -239,7 +239,7 @@ A PR should have a specific use case that it is solving. * If the PR is for a feature, it should have docs. * Unless it's a feature we want to discourage, like support for a corner case that we don't want users to use. * The docs should include a source example file, not write Python directly in Markdown. -* If the source example(s) file can have different syntax for Python 3.8, 3.9, 3.10, there should be different versions of the file, and they should be shown in tabs in the docs. +* If the source example(s) file can have different syntax for different Python versions, there should be different versions of the file, and they should be shown in tabs in the docs. * There should be tests testing the source example. * Before the PR is applied, the new tests should fail. * After applying the PR, the new tests should pass. diff --git a/docs/en/docs/python-types.md b/docs/en/docs/python-types.md index e4bd2a8749..b685deef29 100644 --- a/docs/en/docs/python-types.md +++ b/docs/en/docs/python-types.md @@ -22,7 +22,7 @@ If you are a Python expert, and you already know everything about type hints, sk Let's start with a simple example: -{* ../../docs_src/python_types/tutorial001.py *} +{* ../../docs_src/python_types/tutorial001_py39.py *} Calling this program outputs: @@ -36,7 +36,7 @@ The function does the following: * Converts the first letter of each one to upper case with `title()`. * Concatenates them with a space in the middle. -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} ### Edit it { #edit-it } @@ -78,7 +78,7 @@ That's it. Those are the "type hints": -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} That is not the same as declaring default values like would be with: @@ -106,7 +106,7 @@ With that, you can scroll, seeing the options, until you find the one that "ring Check this function, it already has type hints: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} Because the editor knows the types of the variables, you don't only get completion, you also get error checks: @@ -114,7 +114,7 @@ Because the editor knows the types of the variables, you don't only get completi Now you know that you have to fix it, convert `age` to a string with `str(age)`: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} ## Declaring types { #declaring-types } @@ -133,7 +133,7 @@ You can use, for example: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} ### Generic types with type parameters { #generic-types-with-type-parameters } @@ -161,56 +161,24 @@ If you can use the **latest versions of Python**, use the examples for the lates For example, let's define a variable to be a `list` of `str`. -//// tab | Python 3.9+ - Declare the variable, with the same colon (`:`) syntax. As the type, put `list`. As the list is a type that contains some internal types, you put them in square brackets: -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -From `typing`, import `List` (with a capital `L`): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -Declare the variable, with the same colon (`:`) syntax. - -As the type, put the `List` that you imported from `typing`. - -As the list is a type that contains some internal types, you put them in square brackets: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} /// info Those internal types in the square brackets are called "type parameters". -In this case, `str` is the type parameter passed to `List` (or `list` in Python 3.9 and above). +In this case, `str` is the type parameter passed to `list`. /// That means: "the variable `items` is a `list`, and each of the items in this list is a `str`". -/// tip - -If you use Python 3.9 or above, you don't have to import `List` from `typing`, you can use the same regular `list` type instead. - -/// - By doing that, your editor can provide support even while processing items from the list: @@ -225,21 +193,7 @@ And still, the editor knows it is a `str`, and provides support for that. You would do the same to declare `tuple`s and `set`s: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} This means: @@ -254,21 +208,7 @@ The first type parameter is for the keys of the `dict`. The second type parameter is for the values of the `dict`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} This means: @@ -292,10 +232,10 @@ In Python 3.10 there's also a **new syntax** where you can put the possible type //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b_py39.py!} ``` //// @@ -309,7 +249,7 @@ You can declare that a value could have a type, like `str`, but that it could al 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.py!} +{!../../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. @@ -326,18 +266,18 @@ This also means that in Python 3.10, you can use `Something | None`: //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009_py39.py!} ``` //// -//// tab | Python 3.8+ alternative +//// tab | Python 3.9+ alternative ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b_py39.py!} ``` //// @@ -357,7 +297,7 @@ It's just about the words and names. But those words can affect how you and your As an example, let's take this function: -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} +{* ../../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: @@ -390,10 +330,10 @@ You can use the same builtin types as generics (with square brackets and types i * `set` * `dict` -And the same as with Python 3.8, from the `typing` module: +And the same as with previous Python versions, from the `typing` module: * `Union` -* `Optional` (the same as with Python 3.8) +* `Optional` * ...and others. In Python 3.10, as an alternative to using the generics `Union` and `Optional`, you can use the vertical bar (`|`) to declare unions of types, that's a lot better and simpler. @@ -409,7 +349,7 @@ You can use the same builtin types as generics (with square brackets and types i * `set` * `dict` -And the same as with Python 3.8, from the `typing` module: +And generics from the `typing` module: * `Union` * `Optional` @@ -417,29 +357,17 @@ And the same as with Python 3.8, from the `typing` module: //// -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...and others. - -//// - ### Classes as types { #classes-as-types } You can also declare a class as the type of a variable. Let's say you have a class `Person`, with a name: -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} Then you can declare a variable to be of type `Person`: -{* ../../docs_src/python_types/tutorial010.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} And then, again, you get all the editor support: @@ -463,29 +391,7 @@ And you get all the editor support with that resulting object. An example from the official Pydantic docs: -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial011_py310.py *} /// info @@ -507,27 +413,9 @@ Pydantic has a special behavior when you use `Optional` or `Union[Something, Non Python also has a feature that allows putting **additional metadata** in these type hints using `Annotated`. -//// tab | Python 3.9+ +Since Python 3.9, `Annotated` is a part of the standard library, so you can import it from `typing`. -In Python 3.9, `Annotated` is part of the standard library, so you can import it from `typing`. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -In versions below Python 3.9, you import `Annotated` from `typing_extensions`. - -It will already be installed with **FastAPI**. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} Python itself doesn't do anything with this `Annotated`. And for editors and other tools, the type is still `str`. diff --git a/docs/en/docs/tutorial/background-tasks.md b/docs/en/docs/tutorial/background-tasks.md index ab44f89c19..be7ecd5871 100644 --- a/docs/en/docs/tutorial/background-tasks.md +++ b/docs/en/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ This includes, for example: First, import `BackgroundTasks` and define a parameter in your *path operation function* with a type declaration of `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} **FastAPI** will create the object of type `BackgroundTasks` for you and pass it as that parameter. @@ -31,13 +31,13 @@ In this case, the task function will write to a file (simulating sending an emai And as the write operation doesn't use `async` and `await`, we define the function with normal `def`: -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} ## Add the background task { #add-the-background-task } Inside of your *path operation function*, pass your task function to the *background tasks* object with the method `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` receives as arguments: diff --git a/docs/en/docs/tutorial/body-nested-models.md b/docs/en/docs/tutorial/body-nested-models.md index 445235a420..5fd83a8f3c 100644 --- a/docs/en/docs/tutorial/body-nested-models.md +++ b/docs/en/docs/tutorial/body-nested-models.md @@ -14,35 +14,15 @@ This will make `tags` be a list, although it doesn't declare the type of the ele But Python has a specific way to declare lists with internal types, or "type parameters": -### Import typing's `List` { #import-typings-list } - -In Python 3.9 and above you can use the standard `list` to declare these type annotations as we'll see below. 💡 - -But in Python versions before 3.9 (3.6 and above), you first need to import `List` from standard Python's `typing` module: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - ### Declare a `list` with a type parameter { #declare-a-list-with-a-type-parameter } -To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`: - -* If you are in a Python version lower than 3.9, import their equivalent version from the `typing` module -* Pass the internal type(s) as "type parameters" using square brackets: `[` and `]` - -In Python 3.9 it would be: +To declare types that have type parameters (internal types), like `list`, `dict`, `tuple`, +pass the internal type(s) as "type parameters" using square brackets: `[` and `]` ```Python my_list: list[str] ``` -In versions of Python before 3.9, it would be: - -```Python -from typing import List - -my_list: List[str] -``` - That's all standard Python syntax for type declarations. Use that same standard syntax for model attributes with internal types. @@ -178,12 +158,6 @@ Notice how `Offer` has a list of `Item`s, which in turn have an optional list of If the top level value of the JSON body you expect is a JSON `array` (a Python `list`), you can declare the type in the parameter of the function, the same as in Pydantic models: -```Python -images: List[Image] -``` - -or in Python 3.9 and above: - ```Python images: list[Image] ``` diff --git a/docs/en/docs/tutorial/body.md b/docs/en/docs/tutorial/body.md index a820802f74..25087b8406 100644 --- a/docs/en/docs/tutorial/body.md +++ b/docs/en/docs/tutorial/body.md @@ -163,7 +163,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.8+) 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` (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`. But adding the type annotations will allow your editor to give you better support and detect errors. diff --git a/docs/en/docs/tutorial/cors.md b/docs/en/docs/tutorial/cors.md index e3de37b43c..8a3a8eb0a8 100644 --- a/docs/en/docs/tutorial/cors.md +++ b/docs/en/docs/tutorial/cors.md @@ -46,7 +46,7 @@ You can also specify whether your backend allows: * Specific HTTP methods (`POST`, `PUT`) or all of them with the wildcard `"*"`. * Specific HTTP headers or all of them with the wildcard `"*"`. -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} The default parameters used by the `CORSMiddleware` implementation are restrictive by default, so you'll need to explicitly enable particular origins, methods, or headers, in order for browsers to be permitted to use them in a Cross-Domain context. diff --git a/docs/en/docs/tutorial/debugging.md b/docs/en/docs/tutorial/debugging.md index 08b440084e..a2edfe7203 100644 --- a/docs/en/docs/tutorial/debugging.md +++ b/docs/en/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ You can connect the debugger in your editor, for example with Visual Studio Code In your FastAPI application, import and run `uvicorn` directly: -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} ### About `__name__ == "__main__"` { #about-name-main } diff --git a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md index 686a5632e2..0a6a786b50 100644 --- a/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/en/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Now you can declare your dependency using this class. Notice how we write `CommonQueryParams` twice in the above code: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ 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.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip @@ -163,7 +163,7 @@ commons: CommonQueryParams ... You could actually write just: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ 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.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip @@ -225,7 +225,7 @@ For those specific cases, you can do the following: Instead of writing: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...you write: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.8 non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip diff --git a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md index 494c40efab..d9f3345619 100644 --- a/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/en/docs/tutorial/dependencies/dependencies-with-yield.md @@ -29,15 +29,15 @@ For example, you could use this to create a database session and close it after Only the code prior to and including the `yield` statement is executed before creating a response: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} The yielded value is what is injected into *path operations* and other dependencies: -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} The code following the `yield` statement is executed after the response: -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} /// tip @@ -57,7 +57,7 @@ So, you can look for that specific exception inside the dependency with `except In the same way, you can use `finally` to make sure the exit steps are executed, no matter if there was an exception or not. -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} ## Sub-dependencies with `yield` { #sub-dependencies-with-yield } @@ -269,7 +269,7 @@ In Python, you can create Context Managers by deprecated, but without removing it, pass the parameter `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} It will be clearly marked as deprecated in the interactive docs: diff --git a/docs/en/docs/tutorial/path-params-numeric-validations.md b/docs/en/docs/tutorial/path-params-numeric-validations.md index f7f2d6ceb0..8b1b8a8392 100644 --- a/docs/en/docs/tutorial/path-params-numeric-validations.md +++ b/docs/en/docs/tutorial/path-params-numeric-validations.md @@ -54,7 +54,7 @@ It doesn't matter for **FastAPI**. It will detect the parameters by their names, So, you can declare your function as: -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} But keep in mind that if you use `Annotated`, you won't have this problem, it won't matter as you're not using the function parameter default values for `Query()` or `Path()`. @@ -83,7 +83,7 @@ Pass `*`, as the first parameter of the function. Python won't do anything with that `*`, but it will know that all the following parameters should be called as keyword arguments (key-value pairs), also known as kwargs. Even if they don't have a default value. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} ### Better with `Annotated` { #better-with-annotated } diff --git a/docs/en/docs/tutorial/path-params.md b/docs/en/docs/tutorial/path-params.md index 457cc2713f..ea4307900c 100644 --- a/docs/en/docs/tutorial/path-params.md +++ b/docs/en/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ You can declare path "parameters" or "variables" with the same syntax used by Python format strings: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} The value of the path parameter `item_id` will be passed to your function as the argument `item_id`. @@ -16,7 +16,7 @@ So, if you run this example and go to Enumerations (or enums) are available in Python since version 3.4. - -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip @@ -158,7 +152,7 @@ If you are wondering, "AlexNet", "ResNet", and "LeNet" are just names of Machine Then create a *path parameter* with a type annotation using the enum class you created (`ModelName`): -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} ### Check the docs { #check-the-docs } @@ -174,13 +168,13 @@ The value of the *path parameter* will be an *enumeration member*. You can compare it with the *enumeration member* in your created enum `ModelName`: -{* ../../docs_src/path_params/tutorial005.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} #### Get the *enumeration value* { #get-the-enumeration-value } You can get the actual value (a `str` in this case) using `model_name.value`, or in general, `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip @@ -194,7 +188,7 @@ You can return *enum members* from your *path operation*, even nested in a JSON They will be converted to their corresponding values (strings in this case) before returning them to the client: -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} In your client you will get a JSON response like: @@ -233,7 +227,7 @@ In this case, the name of the parameter is `file_path`, and the last part, `:pat So, you can use it with: -{* ../../docs_src/path_params/tutorial004.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} /// tip diff --git a/docs/en/docs/tutorial/query-params-str-validations.md b/docs/en/docs/tutorial/query-params-str-validations.md index adf08a9249..aba87a4484 100644 --- a/docs/en/docs/tutorial/query-params-str-validations.md +++ b/docs/en/docs/tutorial/query-params-str-validations.md @@ -55,7 +55,7 @@ q: str | None = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Union[str, None] = None @@ -73,7 +73,7 @@ q: Annotated[str | None] = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Annotated[Union[str, None]] = None diff --git a/docs/en/docs/tutorial/query-params.md b/docs/en/docs/tutorial/query-params.md index 2323b83c7c..3c9c225fb0 100644 --- a/docs/en/docs/tutorial/query-params.md +++ b/docs/en/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as "query" parameters. -{* ../../docs_src/query_params/tutorial001.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} The query is the set of key-value pairs that go after the `?` in a URL, separated by `&` characters. @@ -128,7 +128,7 @@ If you don't want to add a specific value but just make it optional, set the def But when you want to make a query parameter required, you can just not declare any default value: -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} Here the query parameter `needy` is a required query parameter of type `str`. diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md index 5090dbcdfd..a38c610d4f 100644 --- a/docs/en/docs/tutorial/response-model.md +++ b/docs/en/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ There might be cases where you return something that is not a valid Pydantic fie The most common case would be [returning a Response directly as explained later in the advanced docs](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} This simple case is handled automatically by FastAPI because the return type annotation is the class (or a subclass of) `Response`. @@ -193,7 +193,7 @@ And tools will also be happy because both `RedirectResponse` and `JSONResponse` You can also use a subclass of `Response` in the type annotation: -{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} This will also work because `RedirectResponse` is a subclass of `Response`, and FastAPI will automatically handle this simple case. diff --git a/docs/en/docs/tutorial/response-status-code.md b/docs/en/docs/tutorial/response-status-code.md index a2d9757b28..6389592486 100644 --- a/docs/en/docs/tutorial/response-status-code.md +++ b/docs/en/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ The same way you can specify a response model, you can also declare the HTTP sta * `@app.delete()` * etc. -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} /// note @@ -74,7 +74,7 @@ To know more about each status code and which code is for what, check the parse como JSON, se lee directamente como `bytes`, y la función `magic_data_reader()` sería la encargada de parsearlo de alguna manera. diff --git a/docs/es/docs/advanced/response-change-status-code.md b/docs/es/docs/advanced/response-change-status-code.md index 067267750b..940f1dd3ff 100644 --- a/docs/es/docs/advanced/response-change-status-code.md +++ b/docs/es/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ Puedes declarar un parámetro de tipo `Response` en tu *path operation function* Y luego puedes establecer el `status_code` en ese objeto de response *temporal*. -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} Y luego puedes devolver cualquier objeto que necesites, como lo harías normalmente (un `dict`, un modelo de base de datos, etc.). diff --git a/docs/es/docs/advanced/response-cookies.md b/docs/es/docs/advanced/response-cookies.md index ce2ff0281d..550a5d97a7 100644 --- a/docs/es/docs/advanced/response-cookies.md +++ b/docs/es/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Puedes declarar un parámetro de tipo `Response` en tu *path operation function* Y luego puedes establecer cookies en ese objeto de response *temporal*. -{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} Y entonces puedes devolver cualquier objeto que necesites, como normalmente lo harías (un `dict`, un modelo de base de datos, etc). @@ -24,7 +24,7 @@ Para hacer eso, puedes crear un response como se describe en [Devolver un Respon Luego establece Cookies en ella, y luego devuélvela: -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} /// tip | Consejo diff --git a/docs/es/docs/advanced/response-directly.md b/docs/es/docs/advanced/response-directly.md index 96b30b915c..2da4e84e70 100644 --- a/docs/es/docs/advanced/response-directly.md +++ b/docs/es/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Digamos que quieres devolver un response en documentación de Strawberry. diff --git a/docs/es/docs/python-types.md b/docs/es/docs/python-types.md index e51c2352c5..60b50a08ff 100644 --- a/docs/es/docs/python-types.md +++ b/docs/es/docs/python-types.md @@ -22,7 +22,7 @@ Si eres un experto en Python, y ya sabes todo sobre las anotaciones de tipos, sa Comencemos con un ejemplo simple: -{* ../../docs_src/python_types/tutorial001.py *} +{* ../../docs_src/python_types/tutorial001_py39.py *} Llamar a este programa genera: @@ -36,7 +36,7 @@ La función hace lo siguiente: * Convierte la primera letra de cada uno a mayúsculas con `title()`. * Concatena ambos con un espacio en el medio. -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} ### Edítalo { #edit-it } @@ -78,7 +78,7 @@ Eso es todo. Esas son las "anotaciones de tipos": -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} Eso no es lo mismo que declarar valores predeterminados como sería con: @@ -106,7 +106,7 @@ Con eso, puedes desplazarte, viendo las opciones, hasta que encuentres la que "t Revisa esta función, ya tiene anotaciones de tipos: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} Porque el editor conoce los tipos de las variables, no solo obtienes autocompletado, también obtienes chequeo de errores: @@ -114,7 +114,7 @@ Porque el editor conoce los tipos de las variables, no solo obtienes autocomplet Ahora sabes que debes corregirlo, convertir `age` a un string con `str(age)`: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} ## Declaración de tipos { #declaring-types } @@ -133,7 +133,7 @@ Puedes usar, por ejemplo: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} ### Tipos genéricos con parámetros de tipo { #generic-types-with-type-parameters } @@ -161,56 +161,24 @@ Si puedes usar las **últimas versiones de Python**, utiliza los ejemplos para l Por ejemplo, vamos a definir una variable para ser una `list` de `str`. -//// tab | Python 3.9+ - Declara la variable, con la misma sintaxis de dos puntos (`:`). Como tipo, pon `list`. Como la lista es un tipo que contiene algunos tipos internos, los pones entre corchetes: -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -De `typing`, importa `List` (con una `L` mayúscula): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -Declara la variable, con la misma sintaxis de dos puntos (`:`). - -Como tipo, pon el `List` que importaste de `typing`. - -Como la lista es un tipo que contiene algunos tipos internos, los pones entre corchetes: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} /// info | Información Esos tipos internos en los corchetes se denominan "parámetros de tipo". -En este caso, `str` es el parámetro de tipo pasado a `List` (o `list` en Python 3.9 y superior). +En este caso, `str` es el parámetro de tipo pasado a `list`. /// Eso significa: "la variable `items` es una `list`, y cada uno de los ítems en esta lista es un `str`". -/// tip | Consejo - -Si usas Python 3.9 o superior, no tienes que importar `List` de `typing`, puedes usar el mismo tipo `list` regular en su lugar. - -/// - Al hacer eso, tu editor puede proporcionar soporte incluso mientras procesa elementos de la lista: @@ -225,21 +193,7 @@ Y aún así, el editor sabe que es un `str` y proporciona soporte para eso. Harías lo mismo para declarar `tuple`s y `set`s: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} Esto significa: @@ -254,21 +208,7 @@ El primer parámetro de tipo es para las claves del `dict`. El segundo parámetro de tipo es para los valores del `dict`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} Esto significa: @@ -292,10 +232,10 @@ En Python 3.10 también hay una **nueva sintaxis** donde puedes poner los posibl //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b_py39.py!} ``` //// @@ -309,7 +249,7 @@ Puedes declarar que un valor podría tener un tipo, como `str`, pero que tambié En Python 3.6 y posteriores (incluyendo Python 3.10) puedes declararlo importando y usando `Optional` del módulo `typing`. ```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009_py39.py!} ``` Usar `Optional[str]` en lugar de solo `str` te permitirá al editor ayudarte a detectar errores donde podrías estar asumiendo que un valor siempre es un `str`, cuando en realidad también podría ser `None`. @@ -326,18 +266,18 @@ Esto también significa que en Python 3.10, puedes usar `Something | None`: //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009_py39.py!} ``` //// -//// tab | Python 3.8+ alternativa +//// tab | Python 3.9+ alternativa ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b_py39.py!} ``` //// @@ -357,7 +297,7 @@ Se trata solo de las palabras y nombres. Pero esas palabras pueden afectar cómo Como ejemplo, tomemos esta función: -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} El parámetro `name` está definido como `Optional[str]`, pero **no es opcional**, no puedes llamar a la función sin el parámetro: @@ -390,10 +330,10 @@ Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos d * `set` * `dict` -Y lo mismo que con Python 3.8, desde el módulo `typing`: +Y, como con versiones anteriores de Python, desde el módulo `typing`: * `Union` -* `Optional` (lo mismo que con Python 3.8) +* `Optional` * ...y otros. En Python 3.10, como alternativa a usar los genéricos `Union` y `Optional`, puedes usar la barra vertical (`|`) para declarar uniones de tipos, eso es mucho mejor y más simple. @@ -409,7 +349,7 @@ Puedes usar los mismos tipos integrados como genéricos (con corchetes y tipos d * `set` * `dict` -Y lo mismo que con Python 3.8, desde el módulo `typing`: +Y generics desde el módulo `typing`: * `Union` * `Optional` @@ -417,29 +357,17 @@ Y lo mismo que con Python 3.8, desde el módulo `typing`: //// -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...y otros. - -//// - ### Clases como tipos { #classes-as-types } También puedes declarar una clase como el tipo de una variable. Digamos que tienes una clase `Person`, con un nombre: -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} Luego puedes declarar una variable para que sea de tipo `Person`: -{* ../../docs_src/python_types/tutorial010.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} Y luego, nuevamente, obtienes todo el soporte del editor: @@ -463,29 +391,7 @@ Y obtienes todo el soporte del editor con ese objeto resultante. Un ejemplo de la documentación oficial de Pydantic: -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial011_py310.py *} /// info | Información @@ -507,27 +413,9 @@ Pydantic tiene un comportamiento especial cuando utilizas `Optional` o `Union[So Python también tiene una funcionalidad que permite poner **metadatos adicional** en estas anotaciones de tipos usando `Annotated`. -//// tab | Python 3.9+ +Desde Python 3.9, `Annotated` es parte de la standard library, así que puedes importarlo desde `typing`. -En Python 3.9, `Annotated` es parte de la standard library, así que puedes importarlo desde `typing`. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -En versiones por debajo de Python 3.9, importas `Annotated` de `typing_extensions`. - -Ya estará instalado con **FastAPI**. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} Python en sí no hace nada con este `Annotated`. Y para los editores y otras herramientas, el tipo sigue siendo `str`. diff --git a/docs/es/docs/tutorial/background-tasks.md b/docs/es/docs/tutorial/background-tasks.md index 8cd0767f8e..cc8a2c9cbe 100644 --- a/docs/es/docs/tutorial/background-tasks.md +++ b/docs/es/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Esto incluye, por ejemplo: Primero, importa `BackgroundTasks` y define un parámetro en tu *path operation function* con una declaración de tipo de `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} **FastAPI** creará el objeto de tipo `BackgroundTasks` por ti y lo pasará como ese parámetro. @@ -31,13 +31,13 @@ En este caso, la función de tarea escribirá en un archivo (simulando el envío Y como la operación de escritura no usa `async` y `await`, definimos la función con un `def` normal: -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} ## Agregar la tarea en segundo plano { #add-the-background-task } Dentro de tu *path operation function*, pasa tu función de tarea al objeto de *background tasks* con el método `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` recibe como argumentos: diff --git a/docs/es/docs/tutorial/body-nested-models.md b/docs/es/docs/tutorial/body-nested-models.md index 04f4b39c4a..0dfd6576f1 100644 --- a/docs/es/docs/tutorial/body-nested-models.md +++ b/docs/es/docs/tutorial/body-nested-models.md @@ -14,35 +14,15 @@ Esto hará que `tags` sea una lista, aunque no declare el tipo de los elementos Pero Python tiene una forma específica de declarar listas con tipos internos, o "parámetros de tipo": -### Importar `List` de typing { #import-typings-list } - -En Python 3.9 y superior, puedes usar el `list` estándar para declarar estas anotaciones de tipo como veremos a continuación. 💡 - -Pero en versiones de Python anteriores a 3.9 (desde 3.6 en adelante), primero necesitas importar `List` del módulo `typing` estándar de Python: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - ### Declarar una `list` con un parámetro de tipo { #declare-a-list-with-a-type-parameter } -Para declarar tipos que tienen parámetros de tipo (tipos internos), como `list`, `dict`, `tuple`: - -* Si estás en una versión de Python inferior a 3.9, importa su versión equivalente del módulo `typing` -* Pasa el/los tipo(s) interno(s) como "parámetros de tipo" usando corchetes: `[` y `]` - -En Python 3.9 sería: +Para declarar tipos que tienen parámetros de tipo (tipos internos), como `list`, `dict`, `tuple`, +pasa el/los tipo(s) interno(s) como "parámetros de tipo" usando corchetes: `[` y `]` ```Python my_list: list[str] ``` -En versiones de Python anteriores a 3.9, sería: - -```Python -from typing import List - -my_list: List[str] -``` - Eso es toda la sintaxis estándar de Python para declaraciones de tipo. Usa esa misma sintaxis estándar para atributos de modelos con tipos internos. @@ -178,12 +158,6 @@ Observa cómo `Offer` tiene una lista de `Item`s, que a su vez tienen una lista Si el valor superior del cuerpo JSON que esperas es un `array` JSON (una `list` en Python), puedes declarar el tipo en el parámetro de la función, al igual que en los modelos Pydantic: -```Python -images: List[Image] -``` - -o en Python 3.9 y superior: - ```Python images: list[Image] ``` diff --git a/docs/es/docs/tutorial/body.md b/docs/es/docs/tutorial/body.md index 58877c5c46..06a70dbc79 100644 --- a/docs/es/docs/tutorial/body.md +++ b/docs/es/docs/tutorial/body.md @@ -161,7 +161,7 @@ Los parámetros de la función se reconocerán de la siguiente manera: FastAPI sabrá que el valor de `q` no es requerido debido al valor por defecto `= None`. -El `str | None` (Python 3.10+) o `Union` en `Union[str, None]` (Python 3.8+) no es utilizado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de `= None`. +El `str | None` (Python 3.10+) o `Union` en `Union[str, None]` (Python 3.9+) no es utilizado por FastAPI para determinar que el valor no es requerido, sabrá que no es requerido porque tiene un valor por defecto de `= None`. Pero agregar las anotaciones de tipos permitirá que tu editor te brinde un mejor soporte y detecte errores. diff --git a/docs/es/docs/tutorial/cors.md b/docs/es/docs/tutorial/cors.md index d6bc7ea61f..c1a23295e1 100644 --- a/docs/es/docs/tutorial/cors.md +++ b/docs/es/docs/tutorial/cors.md @@ -46,7 +46,7 @@ También puedes especificar si tu backend permite: * Métodos HTTP específicos (`POST`, `PUT`) o todos ellos con el comodín `"*"`. * Headers HTTP específicos o todos ellos con el comodín `"*"`. -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} Los parámetros predeterminados utilizados por la implementación de `CORSMiddleware` son restrictivos por defecto, por lo que necesitarás habilitar explícitamente orígenes, métodos o headers particulares para que los navegadores estén permitidos de usarlos en un contexto de Cross-Domain. diff --git a/docs/es/docs/tutorial/debugging.md b/docs/es/docs/tutorial/debugging.md index 1e57df2092..c31daf40f4 100644 --- a/docs/es/docs/tutorial/debugging.md +++ b/docs/es/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Puedes conectar el depurador en tu editor, por ejemplo con Visual Studio Code o En tu aplicación de FastAPI, importa y ejecuta `uvicorn` directamente: -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} ### Acerca de `__name__ == "__main__"` { #about-name-main } diff --git a/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md index 37cc2e2133..a3a75efcd9 100644 --- a/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/es/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ Ahora puedes declarar tu dependencia usando esta clase. Nota cómo escribimos `CommonQueryParams` dos veces en el código anterior: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ sin `Annotated` +//// tab | Python 3.9+ sin `Annotated` /// tip | Consejo @@ -137,7 +137,7 @@ Es a partir de este que **FastAPI** extraerá los parámetros declarados y es lo En este caso, el primer `CommonQueryParams`, en: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.8+ sin `Annotated` +//// tab | Python 3.9+ sin `Annotated` /// tip | Consejo @@ -163,7 +163,7 @@ commons: CommonQueryParams ... De hecho, podrías escribir simplemente: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ sin `Annotated` +//// tab | Python 3.9+ sin `Annotated` /// tip | Consejo @@ -197,7 +197,7 @@ Pero declarar el tipo es recomendable, ya que de esa manera tu editor sabrá lo Pero ves que estamos teniendo algo de repetición de código aquí, escribiendo `CommonQueryParams` dos veces: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ sin `Annotated` +//// tab | Python 3.9+ sin `Annotated` /// tip | Consejo @@ -225,7 +225,7 @@ Para esos casos específicos, puedes hacer lo siguiente: En lugar de escribir: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ sin `Annotated` +//// tab | Python 3.9+ sin `Annotated` /// tip | Consejo @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...escribes: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.8 sin `Annotated` +//// tab | Python 3.9+ sin `Annotated` /// tip | Consejo diff --git a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md index e29c749a5f..aa645daa47 100644 --- a/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/es/docs/tutorial/dependencies/dependencies-with-yield.md @@ -29,15 +29,15 @@ Por ejemplo, podrías usar esto para crear una sesión de base de datos y cerrar Solo el código anterior e incluyendo la declaración `yield` se ejecuta antes de crear un response: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} El valor generado es lo que se inyecta en *path operations* y otras dependencias: -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} El código posterior a la declaración `yield` se ejecuta después del response: -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} /// tip | Consejo @@ -57,7 +57,7 @@ Por lo tanto, puedes buscar esa excepción específica dentro de la dependencia Del mismo modo, puedes usar `finally` para asegurarte de que los pasos de salida se ejecuten, sin importar si hubo una excepción o no. -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} ## Sub-dependencias con `yield` { #sub-dependencies-with-yield } @@ -270,7 +270,7 @@ En Python, puedes crear Context Managers deprecated, pero sin eliminarla, pasa el parámetro `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} Se marcará claramente como deprecado en la documentación interactiva: diff --git a/docs/es/docs/tutorial/path-params-numeric-validations.md b/docs/es/docs/tutorial/path-params-numeric-validations.md index a6f0a4cd3d..569dd03dd0 100644 --- a/docs/es/docs/tutorial/path-params-numeric-validations.md +++ b/docs/es/docs/tutorial/path-params-numeric-validations.md @@ -54,7 +54,7 @@ No importa para **FastAPI**. Detectará los parámetros por sus nombres, tipos y Así que puedes declarar tu función como: -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} Pero ten en cuenta que si usas `Annotated`, no tendrás este problema, no importará ya que no estás usando los valores por defecto de los parámetros de la función para `Query()` o `Path()`. @@ -83,7 +83,7 @@ Pasa `*`, como el primer parámetro de la función. Python no hará nada con ese `*`, pero sabrá que todos los parámetros siguientes deben ser llamados como argumentos de palabras clave (parejas key-value), también conocidos como kwargs. Incluso si no tienen un valor por defecto. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} ### Mejor con `Annotated` { #better-with-annotated } diff --git a/docs/es/docs/tutorial/path-params.md b/docs/es/docs/tutorial/path-params.md index c49b31c44e..7ba49f3b0b 100644 --- a/docs/es/docs/tutorial/path-params.md +++ b/docs/es/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Puedes declarar "parámetros" o "variables" de path con la misma sintaxis que se usa en los format strings de Python: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} El valor del parámetro de path `item_id` se pasará a tu función como el argumento `item_id`. @@ -16,7 +16,7 @@ Así que, si ejecutas este ejemplo y vas a Las enumeraciones (o enums) están disponibles en Python desde la versión 3.4. - -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | Consejo @@ -158,7 +152,7 @@ Si te estás preguntando, "AlexNet", "ResNet" y "LeNet" son solo nombres de analisado como JSON, ele é lido diretamente como `bytes` e a função `magic_data_reader()` seria a responsável por analisar ele de alguma forma. diff --git a/docs/pt/docs/advanced/response-change-status-code.md b/docs/pt/docs/advanced/response-change-status-code.md index 0f08873f67..ee81f0bfc8 100644 --- a/docs/pt/docs/advanced/response-change-status-code.md +++ b/docs/pt/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ Você pode declarar um parâmetro do tipo `Response` em sua *função de operaç E então você pode definir o `status_code` neste objeto de retorno temporal. -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} E então você pode retornar qualquer objeto que você precise, como você faria normalmente (um `dict`, um modelo de banco de dados, etc.). diff --git a/docs/pt/docs/advanced/response-cookies.md b/docs/pt/docs/advanced/response-cookies.md index 41fc000133..67820b433b 100644 --- a/docs/pt/docs/advanced/response-cookies.md +++ b/docs/pt/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Você pode declarar um parâmetro do tipo `Response` na sua *função de operaç E então você pode definir cookies nesse objeto de resposta *temporário*. -{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} Em seguida, você pode retornar qualquer objeto que precise, como normalmente faria (um `dict`, um modelo de banco de dados, etc). @@ -24,7 +24,7 @@ Para fazer isso, você pode criar uma resposta como descrito em [Retorne uma Res Então, defina os cookies nela e a retorne: -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} /// tip | Dica diff --git a/docs/pt/docs/advanced/response-directly.md b/docs/pt/docs/advanced/response-directly.md index 0c0144e9a1..bbbef2f913 100644 --- a/docs/pt/docs/advanced/response-directly.md +++ b/docs/pt/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Vamos dizer que você quer retornar uma resposta documentação do Strawberry. diff --git a/docs/pt/docs/python-types.md b/docs/pt/docs/python-types.md index 3e2d1ccb30..fc983d1df1 100644 --- a/docs/pt/docs/python-types.md +++ b/docs/pt/docs/python-types.md @@ -22,7 +22,7 @@ Se você é um especialista em Python e já sabe tudo sobre type hints, pule par Vamos começar com um exemplo simples: -{* ../../docs_src/python_types/tutorial001.py *} +{* ../../docs_src/python_types/tutorial001_py39.py *} A chamada deste programa gera: @@ -36,7 +36,7 @@ A função faz o seguinte: * Converte a primeira letra de cada uma em maiúsculas com `title()`. * Concatena com um espaço no meio. -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} ### Edite-o { #edit-it } @@ -78,7 +78,7 @@ para: Esses são os "type hints": -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} Isso não é o mesmo que declarar valores padrão como seria com: @@ -106,7 +106,7 @@ Com isso, você pode rolar, vendo as opções, até encontrar o que "soa familia Verifique esta função, ela já possui type hints: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} Como o editor conhece os tipos de variáveis, você não obtém apenas o preenchimento automático, mas também as verificações de erro: @@ -114,7 +114,7 @@ Como o editor conhece os tipos de variáveis, você não obtém apenas o preench Agora você sabe que precisa corrigí-lo, converta `age` em uma string com `str(age)`: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} ## Declarando Tipos { #declaring-types } @@ -133,7 +133,7 @@ Você pode usar, por exemplo: * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} ### Tipos genéricos com parâmetros de tipo { #generic-types-with-type-parameters } @@ -161,56 +161,24 @@ Se você pode utilizar a **versão mais recente do Python**, utilize os exemplos Por exemplo, vamos definir uma variável para ser uma `list` de `str`. -//// tab | Python 3.9+ +Declare a variável, com a mesma sintaxe com dois pontos (`:`). -Declare uma variável com a mesma sintaxe com dois pontos (`:`) +Como o tipo, coloque `list`. -Como tipo, coloque `list`. +Como a lista é um tipo que contém tipos internos, você os coloca entre colchetes: -Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes: - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -De `typing`, importe `List` (com o `L` maiúsculo): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -Declare uma variável com a mesma sintaxe com dois pontos (`:`) - -Como tipo, coloque o `List` que você importou de `typing`. - -Como a lista é o tipo que contém algum tipo interno, você coloca o tipo dentro de colchetes: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} /// info | Informação Estes tipos internos dentro dos colchetes são chamados "parâmetros de tipo" (type parameters). -Neste caso, `str` é o parâmetro de tipo passado para `List` (ou `list` no Python 3.9 ou superior). +Neste caso, `str` é o parâmetro de tipo passado para `list`. /// Isso significa: "a variável `items` é uma `list`, e cada um dos itens desta lista é uma `str`". -/// tip | Dica - -Se você usa o Python 3.9 ou superior, você não precisa importar `List` de `typing`. Você pode utilizar o mesmo tipo `list` no lugar. - -/// - Ao fazer isso, seu editor pode fornecer suporte mesmo durante o processamento de itens da lista: @@ -225,21 +193,7 @@ E, ainda assim, o editor sabe que é um `str` e fornece suporte para isso. Você faria o mesmo para declarar `tuple`s e `set`s: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} Isso significa que: @@ -254,21 +208,7 @@ O primeiro parâmetro de tipo é para as chaves do `dict`. O segundo parâmetro de tipo é para os valores do `dict`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} Isso significa que: @@ -292,10 +232,10 @@ No Python 3.10 também existe uma **nova sintaxe** onde você pode colocar os po //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b_py39.py!} ``` //// @@ -309,7 +249,7 @@ Você pode declarar que um valor pode ter um tipo, como `str`, mas que ele tamb No Python 3.6 e superior (incluindo o Python 3.10) você pode declará-lo importando e utilizando `Optional` do módulo `typing`. ```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009_py39.py!} ``` O uso de `Optional[str]` em vez de apenas `str` permitirá que o editor o ajude a detectar erros, onde você pode estar assumindo que um valor é sempre um `str`, quando na verdade também pode ser `None`. @@ -326,18 +266,18 @@ Isso também significa que no Python 3.10, você pode utilizar `Something | None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009_py39.py!} ``` //// -//// tab | Python 3.8+ alternativa +//// tab | Python 3.9+ alternativa ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b_py39.py!} ``` //// @@ -357,7 +297,7 @@ Isso é apenas sobre palavras e nomes. Mas estas palavras podem afetar como os s Por exemplo, vamos pegar esta função: -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} O parâmetro `name` é definido como `Optional[str]`, mas ele **não é opcional**, você não pode chamar a função sem o parâmetro: @@ -390,10 +330,10 @@ Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e ti * `set` * `dict` -E o mesmo como no Python 3.8, do módulo `typing`: +E o mesmo que com versões anteriores do Python, do módulo `typing`: * `Union` -* `Optional` (o mesmo que com o 3.8) +* `Optional` * ...entre outros. No Python 3.10, como uma alternativa para a utilização dos genéricos `Union` e `Optional`, você pode usar a barra vertical (`|`) para declarar uniões de tipos. Isso é muito melhor e mais simples. @@ -409,7 +349,7 @@ Você pode utilizar os mesmos tipos internos como genéricos (com colchetes e ti * `set` * `dict` -E o mesmo como no Python 3.8, do módulo `typing`: +E genéricos do módulo `typing`: * `Union` * `Optional` @@ -417,31 +357,19 @@ E o mesmo como no Python 3.8, do módulo `typing`: //// -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...entre outros. - -//// - ### Classes como tipos { #classes-as-types } Você também pode declarar uma classe como o tipo de uma variável. Digamos que você tenha uma classe `Person`, com um nome: -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} Então você pode declarar que uma variável é do tipo `Person`: -{* ../../docs_src/python_types/tutorial010.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} -E então, novamente, você recebe todo o suporte do editor: +E então, novamente, você recebe todo o apoio do editor: @@ -461,31 +389,9 @@ Em seguida, você cria uma instância dessa classe com alguns valores e ela os v E você recebe todo o suporte do editor com esse objeto resultante. -Retirado dos documentos oficiais dos Pydantic: +Um exemplo da documentação oficial do Pydantic: -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial011_py310.py *} /// info | Informação @@ -507,27 +413,9 @@ O Pydantic tem um comportamento especial quando você usa `Optional` ou `Union[S O Python possui uma funcionalidade que nos permite incluir **metadados adicionais** nos type hints utilizando `Annotated`. -//// tab | Python 3.9+ +Desde o Python 3.9, `Annotated` faz parte da biblioteca padrão, então você pode importá-lo de `typing`. -No Python 3.9, `Annotated` é parte da biblioteca padrão, então você pode importá-lo de `typing`. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -Em versões abaixo do Python 3.9, você importa `Annotated` de `typing_extensions`. - -Ele já estará instalado com o **FastAPI**. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} O Python em si não faz nada com este `Annotated`. E para editores e outras ferramentas, o tipo ainda é `str`. diff --git a/docs/pt/docs/tutorial/background-tasks.md b/docs/pt/docs/tutorial/background-tasks.md index af0c8b2acd..34805364b4 100644 --- a/docs/pt/docs/tutorial/background-tasks.md +++ b/docs/pt/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Isso inclui, por exemplo: Primeiro, importe `BackgroundTasks` e defina um parâmetro na sua *função de operação de rota* com uma declaração de tipo `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} O **FastAPI** criará o objeto do tipo `BackgroundTasks` para você e o passará como esse parâmetro. @@ -31,13 +31,13 @@ Neste caso, a função da tarefa escreverá em um arquivo (simulando o envio de E como a operação de escrita não usa `async` e `await`, definimos a função com um `def` normal: -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} ## Adicione a tarefa em segundo plano { #add-the-background-task } Dentro da sua *função de operação de rota*, passe sua função de tarefa para o objeto de *tarefas em segundo plano* com o método `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} O `.add_task()` recebe como argumentos: diff --git a/docs/pt/docs/tutorial/body-nested-models.md b/docs/pt/docs/tutorial/body-nested-models.md index 4f3ca661fb..f2bec19a2f 100644 --- a/docs/pt/docs/tutorial/body-nested-models.md +++ b/docs/pt/docs/tutorial/body-nested-models.md @@ -14,35 +14,15 @@ Isso fará com que tags seja uma lista de itens mesmo sem declarar o tipo dos el Mas o Python tem uma maneira específica de declarar listas com tipos internos ou "parâmetros de tipo": -### Importe `List` do typing { #import-typings-list } - -No Python 3.9 e superior você pode usar a `list` padrão para declarar essas anotações de tipo, como veremos abaixo. 💡 - -Mas nas versões do Python anteriores à 3.9 (3.6 e superiores), primeiro é necessário importar `List` do módulo padrão `typing` do Python: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - ### Declare uma `list` com um parâmetro de tipo { #declare-a-list-with-a-type-parameter } -Para declarar tipos que têm parâmetros de tipo (tipos internos), como `list`, `dict`, `tuple`: - -* Se você estiver em uma versão do Python inferior a 3.9, importe a versão equivalente do módulo `typing` -* Passe o(s) tipo(s) interno(s) como "parâmetros de tipo" usando colchetes: `[` e `]` - -No Python 3.9, seria: +Para declarar tipos que têm parâmetros de tipo (tipos internos), como `list`, `dict`, `tuple`, +passe o(s) tipo(s) interno(s) como "parâmetros de tipo" usando colchetes: `[` e `]` ```Python my_list: list[str] ``` -Em versões do Python anteriores à 3.9, seria: - -```Python -from typing import List - -my_list: List[str] -``` - Essa é a sintaxe padrão do Python para declarações de tipo. Use a mesma sintaxe padrão para atributos de modelo com tipos internos. @@ -178,12 +158,6 @@ Observe como `Offer` tem uma lista de `Item`s, que por sua vez têm uma lista op Se o valor de primeiro nível do corpo JSON que você espera for um `array` do JSON (uma` lista` do Python), você pode declarar o tipo no parâmetro da função, da mesma forma que nos modelos do Pydantic: -```Python -images: List[Image] -``` - -ou no Python 3.9 e superior: - ```Python images: list[Image] ``` diff --git a/docs/pt/docs/tutorial/body.md b/docs/pt/docs/tutorial/body.md index ef00b9a7a7..1330f4458f 100644 --- a/docs/pt/docs/tutorial/body.md +++ b/docs/pt/docs/tutorial/body.md @@ -161,7 +161,7 @@ Os parâmetros da função serão reconhecidos conforme abaixo: O FastAPI saberá que o valor de `q` não é obrigatório por causa do valor padrão `= None`. -O `str | None` (Python 3.10+) ou o `Union` em `Union[str, None]` (Python 3.8+) não é utilizado pelo FastAPI para determinar que o valor não é obrigatório, ele saberá que não é obrigatório porque tem um valor padrão `= None`. +O `str | None` (Python 3.10+) ou o `Union` em `Union[str, None]` (Python 3.9+) não é utilizado pelo FastAPI para determinar que o valor não é obrigatório, ele saberá que não é obrigatório porque tem um valor padrão `= None`. Mas adicionar as anotações de tipo permitirá ao seu editor oferecer um suporte melhor e detectar erros. diff --git a/docs/pt/docs/tutorial/cors.md b/docs/pt/docs/tutorial/cors.md index c08191db14..0f99db888e 100644 --- a/docs/pt/docs/tutorial/cors.md +++ b/docs/pt/docs/tutorial/cors.md @@ -46,7 +46,7 @@ Você também pode especificar se o seu backend permite: * Métodos HTTP específicos (`POST`, `PUT`) ou todos eles com o curinga `"*"`. * Cabeçalhos HTTP específicos ou todos eles com o curinga `"*"`. -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} Os parâmetros padrão usados ​​pela implementação `CORSMiddleware` são restritivos por padrão, então você precisará habilitar explicitamente as origens, métodos ou cabeçalhos específicos para que os navegadores tenham permissão para usá-los em um contexto cross domain. diff --git a/docs/pt/docs/tutorial/debugging.md b/docs/pt/docs/tutorial/debugging.md index 21d1d527bd..e39c7d1280 100644 --- a/docs/pt/docs/tutorial/debugging.md +++ b/docs/pt/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ Você pode conectar o depurador no seu editor, por exemplo, com o Visual Studio Em sua aplicação FastAPI, importe e execute `uvicorn` diretamente: -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} ### Sobre `__name__ == "__main__"` { #about-name-main } diff --git a/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md index aa26d158f4..c30d0b5f08 100644 --- a/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/pt/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ O **FastAPI** chama a classe `CommonQueryParams`. Isso cria uma "instância" des Perceba como escrevemos `CommonQueryParams` duas vezes no código abaixo: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Dica @@ -137,7 +137,7 @@ O último `CommonQueryParams`, em: Nesse caso, o primeiro `CommonQueryParams`, em: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Dica @@ -163,7 +163,7 @@ commons: CommonQueryParams ... Na verdade você poderia escrever apenas: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Dica @@ -197,7 +197,7 @@ Mas declarar o tipo é encorajado por que é a forma que o seu editor de texto s Mas você pode ver que temos uma repetição do código neste exemplo, escrevendo `CommonQueryParams` duas vezes: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Dica @@ -225,7 +225,7 @@ Para esses casos específicos, você pode fazer o seguinte: Em vez de escrever: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Dica @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...escreva: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.8 non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Dica diff --git a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md index 0aedcfb31e..3678730139 100644 --- a/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/pt/docs/tutorial/dependencies/dependencies-with-yield.md @@ -29,15 +29,15 @@ Por exemplo, você poderia utilizar isso para criar uma sessão do banco de dado Apenas o código anterior à declaração com `yield` e o código contendo essa declaração são executados antes de criar uma resposta: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} O valor gerado (yielded) é o que é injetado nas *operações de rota* e outras dependências: -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} O código após o `yield` é executado após a resposta: -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} /// tip | Dica @@ -57,7 +57,7 @@ Então, você pode procurar por essa exceção específica dentro da dependênci Da mesma forma, você pode utilizar `finally` para garantir que os passos de saída são executados, com ou sem exceções. -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} ## Subdependências com `yield` { #sub-dependencies-with-yield } @@ -269,7 +269,7 @@ Em Python, você pode criar Gerenciadores de Contexto ao descontinuada, mas sem removê-la, passe o parâmetro `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} Ela será claramente marcada como descontinuada nas documentações interativas: diff --git a/docs/pt/docs/tutorial/path-params-numeric-validations.md b/docs/pt/docs/tutorial/path-params-numeric-validations.md index cec744fd5e..9f12ba38fe 100644 --- a/docs/pt/docs/tutorial/path-params-numeric-validations.md +++ b/docs/pt/docs/tutorial/path-params-numeric-validations.md @@ -54,7 +54,7 @@ Isso não faz diferença para o **FastAPI**. Ele vai detectar os parâmetros pel Então, você pode declarar sua função assim: -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} Mas tenha em mente que, se você usar `Annotated`, você não terá esse problema, não fará diferença, pois você não está usando valores padrão de parâmetros de função para `Query()` ou `Path()`. @@ -83,7 +83,7 @@ Passe `*`, como o primeiro parâmetro da função. O Python não fará nada com esse `*`, mas saberá que todos os parâmetros seguintes devem ser chamados como argumentos nomeados (pares chave-valor), também conhecidos como kwargs. Mesmo que eles não tenham um valor padrão. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} ### Melhor com `Annotated` { #better-with-annotated } diff --git a/docs/pt/docs/tutorial/path-params.md b/docs/pt/docs/tutorial/path-params.md index d795d5b2a0..1f47ca6e59 100644 --- a/docs/pt/docs/tutorial/path-params.md +++ b/docs/pt/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Você pode declarar "parâmetros" ou "variáveis" de path com a mesma sintaxe usada por strings de formatação do Python: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} O valor do parâmetro de path `item_id` será passado para a sua função como o argumento `item_id`. @@ -16,7 +16,7 @@ Então, se você executar este exemplo e acessar Enumerations (ou enums) estão disponíveis no Python desde a versão 3.4. -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | Dica Se você está se perguntando, "AlexNet", "ResNet" e "LeNet" são apenas nomes de modelos de Aprendizado de Máquina. @@ -146,7 +142,7 @@ Se você está se perguntando, "AlexNet", "ResNet" e "LeNet" são apenas nomes d Em seguida, crie um *parâmetro de path* com anotação de tipo usando a classe enum que você criou (`ModelName`): -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} ### Verifique a documentação { #check-the-docs } @@ -162,13 +158,13 @@ O valor do *parâmetro de path* será um *membro de enumeração*. Você pode compará-lo com o *membro de enumeração* no seu enum `ModelName` criado: -{* ../../docs_src/path_params/tutorial005.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} #### Obtenha o valor da enumeração { #get-the-enumeration-value } Você pode obter o valor real (um `str` neste caso) usando `model_name.value`, ou, em geral, `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip | Dica Você também pode acessar o valor `"lenet"` com `ModelName.lenet.value`. @@ -180,7 +176,7 @@ Você pode retornar *membros de enum* da sua *operação de rota*, até mesmo an Eles serão convertidos para seus valores correspondentes (strings neste caso) antes de serem retornados ao cliente: -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} No seu cliente, você receberá uma resposta JSON como: @@ -219,7 +215,7 @@ Nesse caso, o nome do parâmetro é `file_path`, e a última parte, `:path`, diz Então, você pode usá-lo com: -{* ../../docs_src/path_params/tutorial004.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} /// tip | Dica Você pode precisar que o parâmetro contenha `/home/johndoe/myfile.txt`, com uma barra inicial (`/`). diff --git a/docs/pt/docs/tutorial/query-params-str-validations.md b/docs/pt/docs/tutorial/query-params-str-validations.md index 948f8ca8fa..5ec1b1b55e 100644 --- a/docs/pt/docs/tutorial/query-params-str-validations.md +++ b/docs/pt/docs/tutorial/query-params-str-validations.md @@ -55,7 +55,7 @@ q: str | None = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Union[str, None] = None @@ -73,7 +73,7 @@ q: Annotated[str | None] = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Annotated[Union[str, None]] = None diff --git a/docs/pt/docs/tutorial/query-params.md b/docs/pt/docs/tutorial/query-params.md index 5a3fed0359..8826602a25 100644 --- a/docs/pt/docs/tutorial/query-params.md +++ b/docs/pt/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Quando você declara outros parâmetros na função que não fazem parte dos parâmetros da rota, esses parâmetros são automaticamente interpretados como parâmetros de "consulta". -{* ../../docs_src/query_params/tutorial001.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} A consulta é o conjunto de pares chave-valor que vai depois de `?` na URL, separado pelo caractere `&`. @@ -127,7 +127,7 @@ Caso você não queira adicionar um valor específico mas queira apenas torná-l Porém, quando você quiser fazer com que o parâmetro de consulta seja obrigatório, você pode simplesmente não declarar nenhum valor como padrão. -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} Aqui o parâmetro de consulta `needy` é um valor obrigatório, do tipo `str`. diff --git a/docs/pt/docs/tutorial/response-model.md b/docs/pt/docs/tutorial/response-model.md index 5958240e4c..dc66bb46c4 100644 --- a/docs/pt/docs/tutorial/response-model.md +++ b/docs/pt/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ Pode haver casos em que você retorna algo que não é um campo Pydantic válido O caso mais comum seria [retornar uma Response diretamente, conforme explicado posteriormente na documentação avançada](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} Este caso simples é tratado automaticamente pelo FastAPI porque a anotação do tipo de retorno é a classe (ou uma subclasse de) `Response`. @@ -193,7 +193,7 @@ E as ferramentas também ficarão felizes porque `RedirectResponse` e ​​`JSO Você também pode usar uma subclasse de `Response` na anotação de tipo: -{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} Isso também funcionará porque `RedirectResponse` é uma subclasse de `Response`, e o FastAPI tratará automaticamente este caso simples. diff --git a/docs/pt/docs/tutorial/response-status-code.md b/docs/pt/docs/tutorial/response-status-code.md index 854bf57c9d..756c86dada 100644 --- a/docs/pt/docs/tutorial/response-status-code.md +++ b/docs/pt/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ Da mesma forma que você pode especificar um modelo de resposta, você também p * `@app.delete()` * etc. -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} /// note | Nota @@ -74,7 +74,7 @@ Para saber mais sobre cada código de status e qual código serve para quê, ver Vamos ver o exemplo anterior novamente: -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} `201` é o código de status para "Criado". @@ -82,7 +82,7 @@ Mas você não precisa memorizar o que cada um desses códigos significa. Você pode usar as variáveis de conveniência de `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} Eles são apenas uma conveniência, eles possuem o mesmo número, mas dessa forma você pode usar o preenchimento automático do editor para encontrá-los: diff --git a/docs/pt/docs/tutorial/static-files.md b/docs/pt/docs/tutorial/static-files.md index 13313a909e..04a02c7f96 100644 --- a/docs/pt/docs/tutorial/static-files.md +++ b/docs/pt/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ Você pode servir arquivos estáticos automaticamente a partir de um diretório * Importe `StaticFiles`. * "Monte" uma instância de `StaticFiles()` em um path específico. -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} /// note | Detalhes Técnicos diff --git a/docs/pt/docs/tutorial/testing.md b/docs/pt/docs/tutorial/testing.md index 03f1981a3c..e56edcb8c2 100644 --- a/docs/pt/docs/tutorial/testing.md +++ b/docs/pt/docs/tutorial/testing.md @@ -30,7 +30,7 @@ Use o objeto `TestClient` da mesma forma que você faz com `httpx`. Escreva instruções `assert` simples com as expressões Python padrão que você precisa verificar (novamente, `pytest` padrão). -{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} /// tip | Dica @@ -76,7 +76,7 @@ Digamos que você tenha uma estrutura de arquivo conforme descrito em [Aplicaç No arquivo `main.py` você tem sua aplicação **FastAPI**: -{* ../../docs_src/app_testing/main.py *} +{* ../../docs_src/app_testing/app_a_py39/main.py *} ### Arquivo de teste { #testing-file } @@ -92,7 +92,7 @@ Então você poderia ter um arquivo `test_main.py` com seus testes. Ele poderia Como esse arquivo está no mesmo pacote, você pode usar importações relativas para importar o objeto `app` do módulo `main` (`main.py`): -{* ../../docs_src/app_testing/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} ...e ter o código para os testes como antes. diff --git a/docs/pt/llm-prompt.md b/docs/pt/llm-prompt.md index 01ce4143cb..2374070ced 100644 --- a/docs/pt/llm-prompt.md +++ b/docs/pt/llm-prompt.md @@ -47,7 +47,7 @@ For the next terms, use the following translations: * list (as in Python list): list * Machine Learning: Aprendizado de Máquina * media type: media type (do not translate to "tipo de mídia") -* non-Annotated: non-Annotated (do not translate non-Annotated when it comes after a Python version.e.g., “Python 3.8+ non-Annotated”) +* non-Annotated: non-Annotated (do not translate non-Annotated when it comes after a Python version.e.g., “Python 3.10+ non-Annotated”) * operation IDs: IDs de operação * path (as in URL path): path * path operation: operação de rota diff --git a/docs/ru/docs/advanced/additional-responses.md b/docs/ru/docs/advanced/additional-responses.md index 1fc3715e41..fca4f072da 100644 --- a/docs/ru/docs/advanced/additional-responses.md +++ b/docs/ru/docs/advanced/additional-responses.md @@ -26,7 +26,7 @@ Например, чтобы объявить ещё один ответ со статус-кодом `404` и Pydantic-моделью `Message`, можно написать: -{* ../../docs_src/additional_responses/tutorial001.py hl[18,22] *} +{* ../../docs_src/additional_responses/tutorial001_py39.py hl[18,22] *} /// note | Примечание @@ -203,7 +203,7 @@ А также ответ со статус-кодом `200`, который использует ваш `response_model`, но включает пользовательский `example`: -{* ../../docs_src/additional_responses/tutorial003.py hl[20:31] *} +{* ../../docs_src/additional_responses/tutorial003_py39.py hl[20:31] *} Всё это будет объединено и включено в ваш OpenAPI и отображено в документации API: diff --git a/docs/ru/docs/advanced/async-tests.md b/docs/ru/docs/advanced/async-tests.md index 5062bc52e7..e689704066 100644 --- a/docs/ru/docs/advanced/async-tests.md +++ b/docs/ru/docs/advanced/async-tests.md @@ -32,11 +32,11 @@ Файл `main.py`: -{* ../../docs_src/async_tests/main.py *} +{* ../../docs_src/async_tests/app_a_py39/main.py *} Файл `test_main.py` содержит тесты для `main.py`, теперь он может выглядеть так: -{* ../../docs_src/async_tests/test_main.py *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py *} ## Запуск тестов { #run-it } @@ -56,7 +56,7 @@ $ pytest Маркер `@pytest.mark.anyio` говорит pytest, что тестовая функция должна быть вызвана асинхронно: -{* ../../docs_src/async_tests/test_main.py hl[7] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[7] *} /// tip | Подсказка @@ -66,7 +66,7 @@ $ pytest Затем мы можем создать `AsyncClient` со ссылкой на приложение и посылать асинхронные запросы, используя `await`. -{* ../../docs_src/async_tests/test_main.py hl[9:12] *} +{* ../../docs_src/async_tests/app_a_py39/test_main.py hl[9:12] *} Это эквивалентно следующему: diff --git a/docs/ru/docs/advanced/behind-a-proxy.md b/docs/ru/docs/advanced/behind-a-proxy.md index 7119efe2dc..f78da01a09 100644 --- a/docs/ru/docs/advanced/behind-a-proxy.md +++ b/docs/ru/docs/advanced/behind-a-proxy.md @@ -44,7 +44,7 @@ $ fastapi run --forwarded-allow-ips="*" Например, вы объявили операцию пути `/items/`: -{* ../../docs_src/behind_a_proxy/tutorial001_01.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_01_py39.py hl[6] *} Если клиент обратится к `/items`, по умолчанию произойдёт редирект на `/items/`. @@ -115,7 +115,7 @@ sequenceDiagram Хотя весь ваш код написан с расчётом, что путь один — `/app`. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[6] *} Прокси будет «обрезать» префикс пути на лету перед передачей запроса на сервер приложения (скорее всего Uvicorn, запущенный через FastAPI CLI), поддерживая у вашего приложения иллюзию, что его обслуживают по `/app`, чтобы вам не пришлось менять весь код и добавлять префикс `/api/v1`. @@ -193,7 +193,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Здесь мы добавляем его в сообщение лишь для демонстрации. -{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *} +{* ../../docs_src/behind_a_proxy/tutorial001_py39.py hl[8] *} Затем, если вы запустите Uvicorn так: @@ -220,7 +220,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Если нет возможности передать опцию командной строки `--root-path` (или аналог), вы можете указать параметр `root_path` при создании приложения FastAPI: -{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *} +{* ../../docs_src/behind_a_proxy/tutorial002_py39.py hl[3] *} Передача `root_path` в `FastAPI` эквивалентна опции командной строки `--root-path` для Uvicorn или Hypercorn. @@ -400,7 +400,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Например: -{* ../../docs_src/behind_a_proxy/tutorial003.py hl[4:7] *} +{* ../../docs_src/behind_a_proxy/tutorial003_py39.py hl[4:7] *} Будет сгенерирована схема OpenAPI примерно такая: @@ -455,7 +455,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1 Если вы не хотите, чтобы FastAPI добавлял автоматический сервер, используя `root_path`, укажите параметр `root_path_in_servers=False`: -{* ../../docs_src/behind_a_proxy/tutorial004.py hl[9] *} +{* ../../docs_src/behind_a_proxy/tutorial004_py39.py hl[9] *} и тогда этот сервер не будет добавлен в схему OpenAPI. diff --git a/docs/ru/docs/advanced/custom-response.md b/docs/ru/docs/advanced/custom-response.md index 2c238bd95b..49550b49ff 100644 --- a/docs/ru/docs/advanced/custom-response.md +++ b/docs/ru/docs/advanced/custom-response.md @@ -30,7 +30,7 @@ Но если вы уверены, что содержимое, которое вы возвращаете, **сериализуемо в JSON**, вы можете передать его напрямую в класс ответа и избежать дополнительных накладных расходов, которые FastAPI понёс бы, пропуская возвращаемое содержимое через `jsonable_encoder` перед передачей в класс ответа. -{* ../../docs_src/custom_response/tutorial001b.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001b_py39.py hl[2,7] *} /// info | Информация @@ -55,7 +55,7 @@ - Импортируйте `HTMLResponse`. - Передайте `HTMLResponse` в параметр `response_class` вашего декоратора операции пути. -{* ../../docs_src/custom_response/tutorial002.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial002_py39.py hl[2,7] *} /// info | Информация @@ -73,7 +73,7 @@ Тот же пример сверху, возвращающий `HTMLResponse`, может выглядеть так: -{* ../../docs_src/custom_response/tutorial003.py hl[2,7,19] *} +{* ../../docs_src/custom_response/tutorial003_py39.py hl[2,7,19] *} /// warning | Предупреждение @@ -97,7 +97,7 @@ Например, это может быть что-то вроде: -{* ../../docs_src/custom_response/tutorial004.py hl[7,21,23] *} +{* ../../docs_src/custom_response/tutorial004_py39.py hl[7,21,23] *} В этом примере функция `generate_html_response()` уже генерирует и возвращает `Response` вместо возврата HTML в `str`. @@ -136,7 +136,7 @@ FastAPI (фактически Starlette) автоматически добавит заголовок Content-Length. Также будет добавлен заголовок Content-Type, основанный на `media_type` и с добавлением charset для текстовых типов. -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} ### `HTMLResponse` { #htmlresponse } @@ -146,7 +146,7 @@ FastAPI (фактически Starlette) автоматически добави Принимает текст или байты и возвращает ответ в виде простого текста. -{* ../../docs_src/custom_response/tutorial005.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial005_py39.py hl[2,7,9] *} ### `JSONResponse` { #jsonresponse } @@ -180,7 +180,7 @@ FastAPI (фактически Starlette) автоматически добави /// -{* ../../docs_src/custom_response/tutorial001.py hl[2,7] *} +{* ../../docs_src/custom_response/tutorial001_py39.py hl[2,7] *} /// tip | Совет @@ -194,13 +194,13 @@ FastAPI (фактически Starlette) автоматически добави Вы можете вернуть `RedirectResponse` напрямую: -{* ../../docs_src/custom_response/tutorial006.py hl[2,9] *} +{* ../../docs_src/custom_response/tutorial006_py39.py hl[2,9] *} --- Или можно использовать его в параметре `response_class`: -{* ../../docs_src/custom_response/tutorial006b.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006b_py39.py hl[2,7,9] *} Если вы сделаете так, то сможете возвращать URL напрямую из своей функции-обработчика пути. @@ -210,13 +210,13 @@ FastAPI (фактически Starlette) автоматически добави Также вы можете использовать параметр `status_code` в сочетании с параметром `response_class`: -{* ../../docs_src/custom_response/tutorial006c.py hl[2,7,9] *} +{* ../../docs_src/custom_response/tutorial006c_py39.py hl[2,7,9] *} ### `StreamingResponse` { #streamingresponse } Принимает асинхронный генератор или обычный генератор/итератор и отправляет тело ответа потоково. -{* ../../docs_src/custom_response/tutorial007.py hl[2,14] *} +{* ../../docs_src/custom_response/tutorial007_py39.py hl[2,14] *} #### Использование `StreamingResponse` с файлоподобными объектами { #using-streamingresponse-with-file-like-objects } @@ -226,7 +226,7 @@ FastAPI (фактически Starlette) автоматически добави Это включает многие библиотеки для работы с облачным хранилищем, обработки видео и т.д. -{* ../../docs_src/custom_response/tutorial008.py hl[2,10:12,14] *} +{* ../../docs_src/custom_response/tutorial008_py39.py hl[2,10:12,14] *} 1. Это функция-генератор. Она является «функцией-генератором», потому что содержит оператор(ы) `yield` внутри. 2. Используя блок `with`, мы гарантируем, что файлоподобный объект будет закрыт после завершения работы функции-генератора. То есть после того, как она закончит отправку ответа. @@ -255,11 +255,11 @@ FastAPI (фактически Starlette) автоматически добави Файловые ответы будут содержать соответствующие заголовки `Content-Length`, `Last-Modified` и `ETag`. -{* ../../docs_src/custom_response/tutorial009.py hl[2,10] *} +{* ../../docs_src/custom_response/tutorial009_py39.py hl[2,10] *} Вы также можете использовать параметр `response_class`: -{* ../../docs_src/custom_response/tutorial009b.py hl[2,8,10] *} +{* ../../docs_src/custom_response/tutorial009b_py39.py hl[2,8,10] *} В этом случае вы можете возвращать путь к файлу напрямую из своей функции-обработчика пути. @@ -273,7 +273,7 @@ FastAPI (фактически Starlette) автоматически добави Вы могли бы создать `CustomORJSONResponse`. Главное, что вам нужно сделать — реализовать метод `Response.render(content)`, который возвращает содержимое как `bytes`: -{* ../../docs_src/custom_response/tutorial009c.py hl[9:14,17] *} +{* ../../docs_src/custom_response/tutorial009c_py39.py hl[9:14,17] *} Теперь вместо того, чтобы возвращать: @@ -299,7 +299,7 @@ FastAPI (фактически Starlette) автоматически добави В примере ниже **FastAPI** будет использовать `ORJSONResponse` по умолчанию во всех операциях пути вместо `JSONResponse`. -{* ../../docs_src/custom_response/tutorial010.py hl[2,4] *} +{* ../../docs_src/custom_response/tutorial010_py39.py hl[2,4] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/events.md b/docs/ru/docs/advanced/events.md index 20d1df98a9..db73d9094e 100644 --- a/docs/ru/docs/advanced/events.md +++ b/docs/ru/docs/advanced/events.md @@ -30,7 +30,7 @@ Мы создаём асинхронную функцию `lifespan()` с `yield` примерно так: -{* ../../docs_src/events/tutorial003.py hl[16,19] *} +{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *} Здесь мы симулируем дорогую операцию startup по загрузке модели, помещая (фиктивную) функцию модели в словарь с моделями Машинного обучения до `yield`. Этот код будет выполнен до того, как приложение начнет принимать запросы, во время startup. @@ -48,7 +48,7 @@ Первое, на что стоит обратить внимание, — мы определяем асинхронную функцию с `yield`. Это очень похоже на Зависимости с `yield`. -{* ../../docs_src/events/tutorial003.py hl[14:19] *} +{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *} Первая часть функции, до `yield`, будет выполнена до запуска приложения. @@ -60,7 +60,7 @@ Это превращает функцию в «асинхронный менеджер контекста». -{* ../../docs_src/events/tutorial003.py hl[1,13] *} +{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *} Менеджер контекста в Python — это то, что можно использовать в операторе `with`. Например, `open()` можно использовать как менеджер контекста: @@ -82,7 +82,7 @@ async with lifespan(app): Параметр `lifespan` приложения `FastAPI` принимает асинхронный менеджер контекста, поэтому мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`. -{* ../../docs_src/events/tutorial003.py hl[22] *} +{* ../../docs_src/events/tutorial003_py39.py hl[22] *} ## Альтернативные события (устаревшие) { #alternative-events-deprecated } @@ -104,7 +104,7 @@ async with lifespan(app): Чтобы добавить функцию, которую нужно запустить до старта приложения, объявите её как обработчик события `"startup"`: -{* ../../docs_src/events/tutorial001.py hl[8] *} +{* ../../docs_src/events/tutorial001_py39.py hl[8] *} В этом случае функция-обработчик события `startup` инициализирует «базу данных» items (это просто `dict`) некоторыми значениями. @@ -116,7 +116,7 @@ async with lifespan(app): Чтобы добавить функцию, которую нужно запустить при завершении работы приложения, объявите её как обработчик события `"shutdown"`: -{* ../../docs_src/events/tutorial002.py hl[6] *} +{* ../../docs_src/events/tutorial002_py39.py hl[6] *} Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`. diff --git a/docs/ru/docs/advanced/generate-clients.md b/docs/ru/docs/advanced/generate-clients.md index ee52412c6d..00bdd31fe8 100644 --- a/docs/ru/docs/advanced/generate-clients.md +++ b/docs/ru/docs/advanced/generate-clients.md @@ -167,7 +167,7 @@ FastAPI использует **уникальный ID** для каждой *о Мы можем скачать OpenAPI JSON в файл `openapi.json`, а затем **убрать этот префикс‑тег** таким скриптом: -{* ../../docs_src/generate_clients/tutorial004.py *} +{* ../../docs_src/generate_clients/tutorial004_py39.py *} //// tab | Node.js diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md index 82c86b2317..5ebe010782 100644 --- a/docs/ru/docs/advanced/middleware.md +++ b/docs/ru/docs/advanced/middleware.md @@ -57,13 +57,13 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") Любой входящий запрос по `http` или `ws` будет перенаправлен на безопасную схему. -{* ../../docs_src/advanced_middleware/tutorial001.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial001_py39.py hl[2,6] *} ## `TrustedHostMiddleware` { #trustedhostmiddleware } Гарантирует, что во всех входящих запросах корректно установлен `Host`‑заголовок, чтобы защититься от атак на HTTP‑заголовок Host. -{* ../../docs_src/advanced_middleware/tutorial002.py hl[2,6:8] *} +{* ../../docs_src/advanced_middleware/tutorial002_py39.py hl[2,6:8] *} Поддерживаются следующие аргументы: @@ -78,7 +78,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") Это middleware обрабатывает как обычные, так и потоковые ответы. -{* ../../docs_src/advanced_middleware/tutorial003.py hl[2,6] *} +{* ../../docs_src/advanced_middleware/tutorial003_py39.py hl[2,6] *} Поддерживаются следующие аргументы: diff --git a/docs/ru/docs/advanced/openapi-webhooks.md b/docs/ru/docs/advanced/openapi-webhooks.md index d38cf315f7..3a2b9fff74 100644 --- a/docs/ru/docs/advanced/openapi-webhooks.md +++ b/docs/ru/docs/advanced/openapi-webhooks.md @@ -32,7 +32,7 @@ При создании приложения на **FastAPI** есть атрибут `webhooks`, с помощью которого можно объявлять вебхуки так же, как вы объявляете операции пути (обработчики пути), например с `@app.webhooks.post()`. -{* ../../docs_src/openapi_webhooks/tutorial001.py hl[9:13,36:53] *} +{* ../../docs_src/openapi_webhooks/tutorial001_py39.py hl[9:13,36:53] *} Определенные вами вебхуки попадут в схему **OpenAPI** и в автоматический **интерфейс документации**. diff --git a/docs/ru/docs/advanced/path-operation-advanced-configuration.md b/docs/ru/docs/advanced/path-operation-advanced-configuration.md index 78a16a5583..eaf9ad0528 100644 --- a/docs/ru/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/ru/docs/advanced/path-operation-advanced-configuration.md @@ -12,7 +12,7 @@ Нужно убедиться, что он уникален для каждой операции. -{* ../../docs_src/path_operation_advanced_configuration/tutorial001.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial001_py39.py hl[6] *} ### Использование имени функции-обработчика пути как operationId { #using-the-path-operation-function-name-as-the-operationid } @@ -20,7 +20,7 @@ Делать это следует после добавления всех *операций пути*. -{* ../../docs_src/path_operation_advanced_configuration/tutorial002.py hl[2, 12:21, 24] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial002_py39.py hl[2, 12:21, 24] *} /// tip | Совет @@ -40,7 +40,7 @@ Чтобы исключить *операцию пути* из генерируемой схемы OpenAPI (а значит, и из автоматической документации), используйте параметр `include_in_schema` и установите его в `False`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial003.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial003_py39.py hl[6] *} ## Расширенное описание из docstring { #advanced-description-from-docstring } @@ -92,7 +92,7 @@ `openapi_extra` может пригодиться, например, чтобы объявить [Расширения OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specificationExtensions): -{* ../../docs_src/path_operation_advanced_configuration/tutorial005.py hl[6] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial005_py39.py hl[6] *} Если вы откроете автоматическую документацию API, ваше расширение появится внизу страницы конкретной *операции пути*. @@ -139,7 +139,7 @@ Это можно сделать с помощью `openapi_extra`: -{* ../../docs_src/path_operation_advanced_configuration/tutorial006.py hl[19:36, 39:40] *} +{* ../../docs_src/path_operation_advanced_configuration/tutorial006_py39.py hl[19:36, 39:40] *} В этом примере мы не объявляли никакую Pydantic-модель. Фактически тело запроса даже не распарсено как JSON, оно читается напрямую как `bytes`, а функция `magic_data_reader()` будет отвечать за его парсинг каким-то способом. diff --git a/docs/ru/docs/advanced/response-change-status-code.md b/docs/ru/docs/advanced/response-change-status-code.md index e9e1c9470f..85d9050ffc 100644 --- a/docs/ru/docs/advanced/response-change-status-code.md +++ b/docs/ru/docs/advanced/response-change-status-code.md @@ -20,7 +20,7 @@ И затем вы можете установить `status_code` в этом *временном* объекте ответа. -{* ../../docs_src/response_change_status_code/tutorial001.py hl[1,9,12] *} +{* ../../docs_src/response_change_status_code/tutorial001_py39.py hl[1,9,12] *} После этого вы можете вернуть любой объект, который вам нужен, как обычно (`dict`, модель базы данных и т.д.). diff --git a/docs/ru/docs/advanced/response-cookies.md b/docs/ru/docs/advanced/response-cookies.md index 9319aba6e2..2872d6c0ad 100644 --- a/docs/ru/docs/advanced/response-cookies.md +++ b/docs/ru/docs/advanced/response-cookies.md @@ -6,7 +6,7 @@ Затем установить cookies в этом временном объекте ответа. -{* ../../docs_src/response_cookies/tutorial002.py hl[1, 8:9] *} +{* ../../docs_src/response_cookies/tutorial002_py39.py hl[1, 8:9] *} После этого можно вернуть любой объект, как и раньше (например, `dict`, объект модели базы данных и так далее). @@ -24,7 +24,7 @@ Затем установите cookies и верните этот объект: -{* ../../docs_src/response_cookies/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_cookies/tutorial001_py39.py hl[10:12] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/response-directly.md b/docs/ru/docs/advanced/response-directly.md index 3c10633e91..b452810714 100644 --- a/docs/ru/docs/advanced/response-directly.md +++ b/docs/ru/docs/advanced/response-directly.md @@ -54,7 +54,7 @@ Вы можете поместить ваш XML-контент в строку, поместить её в `Response` и вернуть: -{* ../../docs_src/response_directly/tutorial002.py hl[1,18] *} +{* ../../docs_src/response_directly/tutorial002_py39.py hl[1,18] *} ## Примечания { #notes } diff --git a/docs/ru/docs/advanced/response-headers.md b/docs/ru/docs/advanced/response-headers.md index 1c9360b31d..8f24f05b07 100644 --- a/docs/ru/docs/advanced/response-headers.md +++ b/docs/ru/docs/advanced/response-headers.md @@ -6,7 +6,7 @@ А затем вы можете устанавливать HTTP-заголовки в этом *временном* объекте ответа. -{* ../../docs_src/response_headers/tutorial002.py hl[1, 7:8] *} +{* ../../docs_src/response_headers/tutorial002_py39.py hl[1, 7:8] *} После этого вы можете вернуть любой нужный объект, как обычно (например, `dict`, модель из базы данных и т.д.). @@ -22,7 +22,7 @@ Создайте ответ, как описано в [Вернуть Response напрямую](response-directly.md){.internal-link target=_blank}, и передайте заголовки как дополнительный параметр: -{* ../../docs_src/response_headers/tutorial001.py hl[10:12] *} +{* ../../docs_src/response_headers/tutorial001_py39.py hl[10:12] *} /// note | Технические детали diff --git a/docs/ru/docs/advanced/settings.md b/docs/ru/docs/advanced/settings.md index 0ef46fb13c..b96ee44a3a 100644 --- a/docs/ru/docs/advanced/settings.md +++ b/docs/ru/docs/advanced/settings.md @@ -62,7 +62,7 @@ $ pip install "fastapi[all]" //// tab | Pydantic v2 -{* ../../docs_src/settings/tutorial001.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} //// @@ -74,7 +74,7 @@ $ pip install "fastapi[all]" /// -{* ../../docs_src/settings/tutorial001_pv1.py hl[2,5:8,11] *} +{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} //// @@ -92,7 +92,7 @@ $ pip install "fastapi[all]" Затем вы можете использовать новый объект `settings` в вашем приложении: -{* ../../docs_src/settings/tutorial001.py hl[18:20] *} +{* ../../docs_src/settings/tutorial001_py39.py hl[18:20] *} ### Запуск сервера { #run-the-server } @@ -126,11 +126,11 @@ $ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.p Например, у вас может быть файл `config.py` со следующим содержимым: -{* ../../docs_src/settings/app01/config.py *} +{* ../../docs_src/settings/app01_py39/config.py *} А затем использовать его в файле `main.py`: -{* ../../docs_src/settings/app01/main.py hl[3,11:13] *} +{* ../../docs_src/settings/app01_py39/main.py hl[3,11:13] *} /// tip | Совет diff --git a/docs/ru/docs/advanced/sub-applications.md b/docs/ru/docs/advanced/sub-applications.md index 3464f17040..fa5a683f45 100644 --- a/docs/ru/docs/advanced/sub-applications.md +++ b/docs/ru/docs/advanced/sub-applications.md @@ -10,7 +10,7 @@ Сначала создайте основное, верхнего уровня, приложение **FastAPI** и его *операции пути*: -{* ../../docs_src/sub_applications/tutorial001.py hl[3, 6:8] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[3, 6:8] *} ### Подприложение { #sub-application } @@ -18,7 +18,7 @@ Это подприложение — обычное стандартное приложение FastAPI, но именно оно будет «смонтировано»: -{* ../../docs_src/sub_applications/tutorial001.py hl[11, 14:16] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 14:16] *} ### Смонтируйте подприложение { #mount-the-sub-application } @@ -26,7 +26,7 @@ В этом случае оно будет смонтировано по пути `/subapi`: -{* ../../docs_src/sub_applications/tutorial001.py hl[11, 19] *} +{* ../../docs_src/sub_applications/tutorial001_py39.py hl[11, 19] *} ### Проверьте автоматическую документацию API { #check-the-automatic-api-docs } diff --git a/docs/ru/docs/advanced/templates.md b/docs/ru/docs/advanced/templates.md index 204e887605..460e2e4660 100644 --- a/docs/ru/docs/advanced/templates.md +++ b/docs/ru/docs/advanced/templates.md @@ -27,7 +27,7 @@ $ pip install jinja2 - Объявите параметр `Request` в *операции пути*, которая будет возвращать шаблон. - Используйте созданный `templates`, чтобы отрендерить и вернуть `TemplateResponse`; передайте имя шаблона, объект `request` и словарь «context» с парами ключ-значение для использования внутри шаблона Jinja2. -{* ../../docs_src/templates/tutorial001.py hl[4,11,15:18] *} +{* ../../docs_src/templates/tutorial001_py39.py hl[4,11,15:18] *} /// note | Примечание diff --git a/docs/ru/docs/advanced/testing-events.md b/docs/ru/docs/advanced/testing-events.md index e0ec774399..82caea845b 100644 --- a/docs/ru/docs/advanced/testing-events.md +++ b/docs/ru/docs/advanced/testing-events.md @@ -2,11 +2,11 @@ Если вам нужно, чтобы `lifespan` выполнялся в ваших тестах, вы можете использовать `TestClient` вместе с оператором `with`: -{* ../../docs_src/app_testing/tutorial004.py hl[9:15,18,27:28,30:32,41:43] *} +{* ../../docs_src/app_testing/tutorial004_py39.py hl[9:15,18,27:28,30:32,41:43] *} Вы можете узнать больше подробностей в статье [Запуск lifespan в тестах на официальном сайте документации Starlette.](https://www.starlette.dev/lifespan/#running-lifespan-in-tests) Для устаревших событий `startup` и `shutdown` вы можете использовать `TestClient` следующим образом: -{* ../../docs_src/app_testing/tutorial003.py hl[9:12,20:24] *} +{* ../../docs_src/app_testing/tutorial003_py39.py hl[9:12,20:24] *} diff --git a/docs/ru/docs/advanced/testing-websockets.md b/docs/ru/docs/advanced/testing-websockets.md index e840a03f28..b6626679e4 100644 --- a/docs/ru/docs/advanced/testing-websockets.md +++ b/docs/ru/docs/advanced/testing-websockets.md @@ -4,7 +4,7 @@ Для этого используйте `TestClient` с менеджером контекста `with`, подключаясь к WebSocket: -{* ../../docs_src/app_testing/tutorial002.py hl[27:31] *} +{* ../../docs_src/app_testing/tutorial002_py39.py hl[27:31] *} /// note | Примечание diff --git a/docs/ru/docs/advanced/using-request-directly.md b/docs/ru/docs/advanced/using-request-directly.md index b922216105..cdf500c0e2 100644 --- a/docs/ru/docs/advanced/using-request-directly.md +++ b/docs/ru/docs/advanced/using-request-directly.md @@ -29,7 +29,7 @@ Для этого нужно обратиться к запросу напрямую. -{* ../../docs_src/using_request_directly/tutorial001.py hl[1,7:8] *} +{* ../../docs_src/using_request_directly/tutorial001_py39.py hl[1,7:8] *} Если объявить параметр *функции-обработчика пути* с типом `Request`, **FastAPI** поймёт, что нужно передать объект `Request` в этот параметр. diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md index f26185bea5..fa5e4738eb 100644 --- a/docs/ru/docs/advanced/websockets.md +++ b/docs/ru/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets Для примера нам нужен наиболее простой способ, который позволит сосредоточиться на серверной части веб‑сокетов и получить рабочий код: -{* ../../docs_src/websockets/tutorial001.py hl[2,6:38,41:43] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[2,6:38,41:43] *} ## Создание `websocket` { #create-a-websocket } Создайте `websocket` в своем **FastAPI** приложении: -{* ../../docs_src/websockets/tutorial001.py hl[1,46:47] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[1,46:47] *} /// note | Технические детали @@ -58,7 +58,7 @@ $ pip install websockets Через эндпоинт веб-сокета вы можете получать и отправлять сообщения. -{* ../../docs_src/websockets/tutorial001.py hl[48:52] *} +{* ../../docs_src/websockets/tutorial001_py39.py hl[48:52] *} Вы можете получать и отправлять двоичные, текстовые и JSON данные. diff --git a/docs/ru/docs/advanced/wsgi.md b/docs/ru/docs/advanced/wsgi.md index 1c5bf0a626..64d7c7a289 100644 --- a/docs/ru/docs/advanced/wsgi.md +++ b/docs/ru/docs/advanced/wsgi.md @@ -12,7 +12,7 @@ После этого смонтируйте его на путь. -{* ../../docs_src/wsgi/tutorial001.py hl[2:3,3] *} +{* ../../docs_src/wsgi/tutorial001_py39.py hl[2:3,3] *} ## Проверьте { #check-it } diff --git a/docs/ru/docs/how-to/conditional-openapi.md b/docs/ru/docs/how-to/conditional-openapi.md index dc987ae26b..d0845b91e2 100644 --- a/docs/ru/docs/how-to/conditional-openapi.md +++ b/docs/ru/docs/how-to/conditional-openapi.md @@ -29,7 +29,7 @@ Например: -{* ../../docs_src/conditional_openapi/tutorial001.py hl[6,11] *} +{* ../../docs_src/conditional_openapi/tutorial001_py39.py hl[6,11] *} Здесь мы объявляем настройку `openapi_url` с тем же значением по умолчанию — `"/openapi.json"`. diff --git a/docs/ru/docs/how-to/configure-swagger-ui.md b/docs/ru/docs/how-to/configure-swagger-ui.md index 9d104423d7..b3b1c1ba62 100644 --- a/docs/ru/docs/how-to/configure-swagger-ui.md +++ b/docs/ru/docs/how-to/configure-swagger-ui.md @@ -18,7 +18,7 @@ FastAPI преобразует эти настройки в **JSON**, чтобы Но вы можете отключить её, установив `syntaxHighlight` в `False`: -{* ../../docs_src/configure_swagger_ui/tutorial001.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial001_py39.py hl[3] *} …и после этого Swagger UI больше не будет показывать подсветку синтаксиса: @@ -28,7 +28,7 @@ FastAPI преобразует эти настройки в **JSON**, чтобы Аналогично вы можете задать тему подсветки синтаксиса с ключом "syntaxHighlight.theme" (обратите внимание, что посередине стоит точка): -{* ../../docs_src/configure_swagger_ui/tutorial002.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial002_py39.py hl[3] *} Эта настройка изменит цветовую тему подсветки синтаксиса: @@ -46,7 +46,7 @@ FastAPI включает некоторые параметры конфигур Например, чтобы отключить `deepLinking`, можно передать такие настройки в `swagger_ui_parameters`: -{* ../../docs_src/configure_swagger_ui/tutorial003.py hl[3] *} +{* ../../docs_src/configure_swagger_ui/tutorial003_py39.py hl[3] *} ## Другие параметры Swagger UI { #other-swagger-ui-parameters } diff --git a/docs/ru/docs/how-to/custom-docs-ui-assets.md b/docs/ru/docs/how-to/custom-docs-ui-assets.md index c07a9695b9..f524911e65 100644 --- a/docs/ru/docs/how-to/custom-docs-ui-assets.md +++ b/docs/ru/docs/how-to/custom-docs-ui-assets.md @@ -18,7 +18,7 @@ Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[8] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[8] *} ### Подключить пользовательскую документацию { #include-the-custom-docs } @@ -34,7 +34,7 @@ Аналогично и для ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[2:6,11:19,22:24,27:33] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[2:6,11:19,22:24,27:33] *} /// tip | Совет @@ -50,7 +50,7 @@ Swagger UI сделает это за вас «за кулисами», но д Чтобы убедиться, что всё работает, создайте *операцию пути*: -{* ../../docs_src/custom_docs_ui/tutorial001.py hl[36:38] *} +{* ../../docs_src/custom_docs_ui/tutorial001_py39.py hl[36:38] *} ### Тестирование { #test-it } @@ -118,7 +118,7 @@ Swagger UI сделает это за вас «за кулисами», но д * Импортируйте `StaticFiles`. * Смонтируйте экземпляр `StaticFiles()` в определённый путь. -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[7,11] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[7,11] *} ### Протестируйте статические файлы { #test-the-static-files } @@ -144,7 +144,7 @@ Swagger UI сделает это за вас «за кулисами», но д Чтобы отключить её, установите их URL в значение `None` при создании вашего приложения `FastAPI`: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[9] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[9] *} ### Подключить пользовательскую документацию со статическими файлами { #include-the-custom-docs-for-static-files } @@ -160,7 +160,7 @@ Swagger UI сделает это за вас «за кулисами», но д Аналогично и для ReDoc... -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[2:6,14:22,25:27,30:36] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[2:6,14:22,25:27,30:36] *} /// tip | Совет @@ -176,7 +176,7 @@ Swagger UI сделает это за вас «за кулисами», но д Чтобы убедиться, что всё работает, создайте *операцию пути*: -{* ../../docs_src/custom_docs_ui/tutorial002.py hl[39:41] *} +{* ../../docs_src/custom_docs_ui/tutorial002_py39.py hl[39:41] *} ### Тестирование UI со статическими файлами { #test-static-files-ui } diff --git a/docs/ru/docs/how-to/extending-openapi.md b/docs/ru/docs/how-to/extending-openapi.md index 2897fb89ba..1d69cbdb3a 100644 --- a/docs/ru/docs/how-to/extending-openapi.md +++ b/docs/ru/docs/how-to/extending-openapi.md @@ -43,19 +43,19 @@ Сначала напишите приложение **FastAPI** как обычно: -{* ../../docs_src/extending_openapi/tutorial001.py hl[1,4,7:9] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[1,4,7:9] *} ### Сгенерируйте схему OpenAPI { #generate-the-openapi-schema } Затем используйте ту же вспомогательную функцию для генерации схемы OpenAPI внутри функции `custom_openapi()`: -{* ../../docs_src/extending_openapi/tutorial001.py hl[2,15:21] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[2,15:21] *} ### Измените схему OpenAPI { #modify-the-openapi-schema } Теперь можно добавить расширение ReDoc, добавив кастомный `x-logo` в «объект» `info` в схеме OpenAPI: -{* ../../docs_src/extending_openapi/tutorial001.py hl[22:24] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[22:24] *} ### Кэшируйте схему OpenAPI { #cache-the-openapi-schema } @@ -65,13 +65,13 @@ Она будет создана один раз, а затем тот же кэшированный вариант будет использоваться для последующих запросов. -{* ../../docs_src/extending_openapi/tutorial001.py hl[13:14,25:26] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[13:14,25:26] *} ### Переопределите метод { #override-the-method } Теперь вы можете заменить метод `.openapi()` на вашу новую функцию. -{* ../../docs_src/extending_openapi/tutorial001.py hl[29] *} +{* ../../docs_src/extending_openapi/tutorial001_py39.py hl[29] *} ### Проверьте { #check-it } diff --git a/docs/ru/docs/how-to/graphql.md b/docs/ru/docs/how-to/graphql.md index 9ed6d95ca1..97278069ad 100644 --- a/docs/ru/docs/how-to/graphql.md +++ b/docs/ru/docs/how-to/graphql.md @@ -35,7 +35,7 @@ Вот небольшой пример того, как можно интегрировать Strawberry с FastAPI: -{* ../../docs_src/graphql/tutorial001.py hl[3,22,25] *} +{* ../../docs_src/graphql/tutorial001_py39.py hl[3,22,25] *} Подробнее о Strawberry можно узнать в документации Strawberry. diff --git a/docs/ru/docs/python-types.md b/docs/ru/docs/python-types.md index 84a901f547..ae4a1e2b79 100644 --- a/docs/ru/docs/python-types.md +++ b/docs/ru/docs/python-types.md @@ -22,7 +22,7 @@ Python поддерживает необязательные «подсказк Давайте начнем с простого примера: -{* ../../docs_src/python_types/tutorial001.py *} +{* ../../docs_src/python_types/tutorial001_py39.py *} Вызов этой программы выводит: @@ -36,7 +36,7 @@ John Doe * Преобразует первую букву каждого значения в верхний регистр с помощью `title()`. * Соединяет их пробелом посередине. -{* ../../docs_src/python_types/tutorial001.py hl[2] *} +{* ../../docs_src/python_types/tutorial001_py39.py hl[2] *} ### Отредактируем пример { #edit-it } @@ -78,7 +78,7 @@ John Doe Это и есть «подсказки типов»: -{* ../../docs_src/python_types/tutorial002.py hl[1] *} +{* ../../docs_src/python_types/tutorial002_py39.py hl[1] *} Это не то же самое, что объявление значений по умолчанию, как, например: @@ -106,7 +106,7 @@ John Doe Посмотрите на эту функцию — у неё уже есть подсказки типов: -{* ../../docs_src/python_types/tutorial003.py hl[1] *} +{* ../../docs_src/python_types/tutorial003_py39.py hl[1] *} Так как редактор кода знает типы переменных, вы получаете не только автозавершение, но и проверки ошибок: @@ -114,7 +114,7 @@ John Doe Теперь вы знаете, что нужно исправить — преобразовать `age` в строку с помощью `str(age)`: -{* ../../docs_src/python_types/tutorial004.py hl[2] *} +{* ../../docs_src/python_types/tutorial004_py39.py hl[2] *} ## Объявление типов { #declaring-types } @@ -133,7 +133,7 @@ John Doe * `bool` * `bytes` -{* ../../docs_src/python_types/tutorial005.py hl[1] *} +{* ../../docs_src/python_types/tutorial005_py39.py hl[1] *} ### Generic-типы с параметрами типов { #generic-types-with-type-parameters } @@ -161,56 +161,24 @@ John Doe Например, давайте определим переменную как `list` из `str`. -//// tab | Python 3.9+ - Объявите переменную с тем же синтаксисом двоеточия (`:`). В качестве типа укажите `list`. Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках: -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -Из `typing` импортируйте `List` (с заглавной `L`): - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -Объявите переменную с тем же синтаксисом двоеточия (`:`). - -В качестве типа используйте `List`, который вы импортировали из `typing`. - -Так как список — это тип, содержащий внутренние типы, укажите их в квадратных скобках: - -```Python hl_lines="4" -{!> ../../docs_src/python_types/tutorial006.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial006_py39.py hl[1] *} /// info | Информация Эти внутренние типы в квадратных скобках называются «параметрами типов». -В данном случае `str` — это параметр типа, передаваемый в `List` (или `list` в Python 3.9 и выше). +В данном случае `str` — это параметр типа, передаваемый в `list`. /// Это означает: «переменная `items` — это `list`, и каждый элемент этого списка — `str`». -/// tip | Совет - -Если вы используете Python 3.9 или выше, вам не нужно импортировать `List` из `typing`, можно использовать обычный встроенный тип `list`. - -/// - Таким образом, ваш редактор кода сможет помогать даже при обработке элементов списка: @@ -225,21 +193,7 @@ John Doe Аналогично вы бы объявили `tuple` и `set`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial007_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial007.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial007_py39.py hl[1] *} Это означает: @@ -254,21 +208,7 @@ John Doe Второй параметр типа — для значений `dict`: -//// tab | Python 3.9+ - -```Python hl_lines="1" -{!> ../../docs_src/python_types/tutorial008_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial008_py39.py hl[1] *} Это означает: @@ -292,10 +232,10 @@ John Doe //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial008b.py!} +{!> ../../docs_src/python_types/tutorial008b_py39.py!} ``` //// @@ -309,7 +249,7 @@ John Doe В Python 3.6 и выше (включая Python 3.10) это можно объявить, импортировав и используя `Optional` из модуля `typing`. ```Python hl_lines="1 4" -{!../../docs_src/python_types/tutorial009.py!} +{!../../docs_src/python_types/tutorial009_py39.py!} ``` Использование `Optional[str]` вместо просто `str` позволит редактору кода помочь вам обнаружить ошибки, когда вы предполагаете, что значение всегда `str`, хотя на самом деле оно может быть и `None`. @@ -326,18 +266,18 @@ John Doe //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009.py!} +{!> ../../docs_src/python_types/tutorial009_py39.py!} ``` //// -//// tab | Python 3.8+ альтернативный вариант +//// tab | Python 3.9+ альтернативный вариант ```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial009b.py!} +{!> ../../docs_src/python_types/tutorial009b_py39.py!} ``` //// @@ -357,7 +297,7 @@ John Doe В качестве примера возьмём эту функцию: -{* ../../docs_src/python_types/tutorial009c.py hl[1,4] *} +{* ../../docs_src/python_types/tutorial009c_py39.py hl[1,4] *} Параметр `name` определён как `Optional[str]`, но он **не необязательный** — вы не можете вызвать функцию без этого параметра: @@ -390,10 +330,10 @@ say_hi(name=None) # Это работает, None допустим 🎉 * `set` * `dict` -И, как и в Python 3.8, из модуля `typing`: +И, как и в предыдущих версиях Python, из модуля `typing`: * `Union` -* `Optional` (так же, как в Python 3.8) +* `Optional` * ...и другие. В Python 3.10, как альтернативу generics `Union` и `Optional`, можно использовать вертикальную черту (`|`) для объявления объединений типов — это гораздо лучше и проще. @@ -409,7 +349,7 @@ say_hi(name=None) # Это работает, None допустим 🎉 * `set` * `dict` -И, как и в Python 3.8, из модуля `typing`: +И generics из модуля `typing`: * `Union` * `Optional` @@ -417,29 +357,17 @@ say_hi(name=None) # Это работает, None допустим 🎉 //// -//// tab | Python 3.8+ - -* `List` -* `Tuple` -* `Set` -* `Dict` -* `Union` -* `Optional` -* ...и другие. - -//// - ### Классы как типы { #classes-as-types } Вы также можете объявлять класс как тип переменной. Допустим, у вас есть класс `Person` с именем: -{* ../../docs_src/python_types/tutorial010.py hl[1:3] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[1:3] *} Тогда вы можете объявить переменную типа `Person`: -{* ../../docs_src/python_types/tutorial010.py hl[6] *} +{* ../../docs_src/python_types/tutorial010_py39.py hl[6] *} И снова вы получите полную поддержку редактора кода: @@ -463,29 +391,7 @@ say_hi(name=None) # Это работает, None допустим 🎉 Пример из официальной документации Pydantic: -//// tab | Python 3.10+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py310.py!} -``` - -//// - -//// tab | Python 3.9+ - -```Python -{!> ../../docs_src/python_types/tutorial011_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -```Python -{!> ../../docs_src/python_types/tutorial011.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial011_py310.py *} /// info | Информация @@ -507,27 +413,9 @@ say_hi(name=None) # Это работает, None допустим 🎉 В Python также есть возможность добавлять **дополнительные метаданные** к подсказкам типов с помощью `Annotated`. -//// tab | Python 3.9+ +Начиная с Python 3.9, `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`. -В Python 3.9 `Annotated` входит в стандартную библиотеку, поэтому вы можете импортировать его из `typing`. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013_py39.py!} -``` - -//// - -//// tab | Python 3.8+ - -В версиях ниже Python 3.9 импортируйте `Annotated` из `typing_extensions`. - -Он уже будет установлен вместе с **FastAPI**. - -```Python hl_lines="1 4" -{!> ../../docs_src/python_types/tutorial013.py!} -``` - -//// +{* ../../docs_src/python_types/tutorial013_py39.py hl[1,4] *} Сам Python ничего не делает с `Annotated`. А для редакторов кода и других инструментов тип по-прежнему `str`. @@ -558,7 +446,7 @@ say_hi(name=None) # Это работает, None допустим 🎉 ...и **FastAPI** использует эти же объявления для: -* **Определения требований**: из path-параметров, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д. +* **Определения требований**: из path-параметров пути запроса, query-параметров, HTTP-заголовков, тел запросов, зависимостей и т.д. * **Преобразования данных**: из HTTP-запроса к требуемому типу. * **Валидации данных**: приходящих с каждого HTTP-запроса: * Генерации **автоматических ошибок**, возвращаемых клиенту, когда данные некорректны. diff --git a/docs/ru/docs/tutorial/background-tasks.md b/docs/ru/docs/tutorial/background-tasks.md index 1ed8522d69..8d7b7442f9 100644 --- a/docs/ru/docs/tutorial/background-tasks.md +++ b/docs/ru/docs/tutorial/background-tasks.md @@ -15,7 +15,7 @@ Сначала импортируйте `BackgroundTasks` и объявите параметр в вашей функции‑обработчике пути с типом `BackgroundTasks`: -{* ../../docs_src/background_tasks/tutorial001.py hl[1,13] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[1,13] *} **FastAPI** создаст объект типа `BackgroundTasks` для вас и передаст его через этот параметр. @@ -31,13 +31,13 @@ Так как операция записи не использует `async` и `await`, мы определим функцию как обычную `def`: -{* ../../docs_src/background_tasks/tutorial001.py hl[6:9] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[6:9] *} ## Добавление фоновой задачи { #add-the-background-task } Внутри вашей функции‑обработчика пути передайте функцию задачи объекту фоновых задач методом `.add_task()`: -{* ../../docs_src/background_tasks/tutorial001.py hl[14] *} +{* ../../docs_src/background_tasks/tutorial001_py39.py hl[14] *} `.add_task()` принимает следующие аргументы: diff --git a/docs/ru/docs/tutorial/body-nested-models.md b/docs/ru/docs/tutorial/body-nested-models.md index 5bb5abbe63..4c914b97f0 100644 --- a/docs/ru/docs/tutorial/body-nested-models.md +++ b/docs/ru/docs/tutorial/body-nested-models.md @@ -14,35 +14,14 @@ В Python есть специальный способ объявлять списки с внутренними типами, или «параметрами типа»: -### Импортируйте `List` из модуля typing { #import-typings-list } - -В Python 3.9 и выше вы можете использовать стандартный тип `list` для объявления аннотаций типов, как мы увидим ниже. 💡 - -Но в версиях Python до 3.9 (начиная с 3.6) сначала вам необходимо импортировать `List` из стандартного модуля `typing`: - -{* ../../docs_src/body_nested_models/tutorial002.py hl[1] *} - ### Объявите `list` с параметром типа { #declare-a-list-with-a-type-parameter } -Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`: - -* Если у вас Python версии ниже 3.9, импортируйте их аналоги из модуля `typing` -* Передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]` - -В Python 3.9 это будет: +Для объявления типов, у которых есть параметры типа (внутренние типы), таких как `list`, `dict`, `tuple`, передайте внутренний(ие) тип(ы) как «параметры типа», используя квадратные скобки: `[` и `]` ```Python my_list: list[str] ``` -В версиях Python до 3.9 это будет: - -```Python -from typing import List - -my_list: List[str] -``` - Это всё стандартный синтаксис Python для объявления типов. Используйте этот же стандартный синтаксис для атрибутов модели с внутренними типами. @@ -107,7 +86,7 @@ my_list: List[str] Ещё раз: сделав такое объявление, с помощью **FastAPI** вы получите: -* Поддержку редактора кода (автозавершение и т. д.), даже для вложенных моделей +* Поддержку редактора кода (автозавершение и т.д.), даже для вложенных моделей * Преобразование данных * Валидацию данных * Автоматическую документацию @@ -178,12 +157,6 @@ my_list: List[str] Если верхний уровень значения тела JSON-объекта представляет собой JSON `array` (в Python — `list`), вы можете объявить тип в параметре функции, так же как в моделях Pydantic: -```Python -images: List[Image] -``` - -или в Python 3.9 и выше: - ```Python images: list[Image] ``` diff --git a/docs/ru/docs/tutorial/body.md b/docs/ru/docs/tutorial/body.md index 16ff6466c1..b61f3e7a09 100644 --- a/docs/ru/docs/tutorial/body.md +++ b/docs/ru/docs/tutorial/body.md @@ -161,7 +161,7 @@ JSON Schema ваших моделей будет частью сгенериро FastAPI понимает, что значение `q` не является обязательным из-за значения по умолчанию `= None`. -Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.8+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`. +Аннотации типов `str | None` (Python 3.10+) или `Union[str, None]` (Python 3.9+) не используются FastAPI для определения обязательности; он узнает, что параметр не обязателен, потому что у него есть значение по умолчанию `= None`. Но добавление аннотаций типов позволит вашему редактору кода лучше вас поддерживать и обнаруживать ошибки. diff --git a/docs/ru/docs/tutorial/cors.md b/docs/ru/docs/tutorial/cors.md index b0704351a9..d09a31e2c3 100644 --- a/docs/ru/docs/tutorial/cors.md +++ b/docs/ru/docs/tutorial/cors.md @@ -46,7 +46,7 @@ * Отдельных HTTP-методов (`POST`, `PUT`) или всех вместе, используя `"*"`. * Отдельных HTTP-заголовков или всех вместе, используя `"*"`. -{* ../../docs_src/cors/tutorial001.py hl[2,6:11,13:19] *} +{* ../../docs_src/cors/tutorial001_py39.py hl[2,6:11,13:19] *} `CORSMiddleware` использует "запрещающие" значения по умолчанию, поэтому вам нужно явным образом разрешить использование отдельных источников, методов или заголовков, чтобы браузеры могли использовать их в кросс-доменном контексте. diff --git a/docs/ru/docs/tutorial/debugging.md b/docs/ru/docs/tutorial/debugging.md index a5340af089..51955835e6 100644 --- a/docs/ru/docs/tutorial/debugging.md +++ b/docs/ru/docs/tutorial/debugging.md @@ -6,7 +6,7 @@ В вашем FastAPI приложении, импортируйте и вызовите `uvicorn` напрямую: -{* ../../docs_src/debugging/tutorial001.py hl[1,15] *} +{* ../../docs_src/debugging/tutorial001_py39.py hl[1,15] *} ### Описание `__name__ == "__main__"` { #about-name-main } diff --git a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md index ec7770d960..a38e885d43 100644 --- a/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md +++ b/docs/ru/docs/tutorial/dependencies/classes-as-dependencies.md @@ -101,7 +101,7 @@ fluffy = Cat(name="Mr Fluffy") Обратите внимание, что в приведенном выше коде мы два раза пишем `CommonQueryParams`: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -109,7 +109,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Подсказка @@ -137,7 +137,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) В этом случае первый `CommonQueryParams`, в: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, ... @@ -145,7 +145,7 @@ commons: Annotated[CommonQueryParams, ... //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Подсказка @@ -163,7 +163,7 @@ commons: CommonQueryParams ... На самом деле можно написать просто: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[Any, Depends(CommonQueryParams)] @@ -171,7 +171,7 @@ commons: Annotated[Any, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Подсказка @@ -197,7 +197,7 @@ commons = Depends(CommonQueryParams) Но вы видите, что здесь мы имеем некоторое повторение кода, дважды написав `CommonQueryParams`: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -205,7 +205,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Подсказка @@ -225,7 +225,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) Вместо того чтобы писать: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] @@ -233,7 +233,7 @@ commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)] //// -//// tab | Python 3.8+ non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Подсказка @@ -249,7 +249,7 @@ commons: CommonQueryParams = Depends(CommonQueryParams) ...следует написать: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python commons: Annotated[CommonQueryParams, Depends()] @@ -257,7 +257,7 @@ commons: Annotated[CommonQueryParams, Depends()] //// -//// tab | Python 3.8 non-Annotated +//// tab | Python 3.9+ non-Annotated /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md index 7ff85246dc..dc202db616 100644 --- a/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md +++ b/docs/ru/docs/tutorial/dependencies/dependencies-with-yield.md @@ -29,15 +29,15 @@ FastAPI поддерживает зависимости, которые выпо Перед созданием ответа будет выполнен только код до и включая оператор `yield`: -{* ../../docs_src/dependencies/tutorial007.py hl[2:4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[2:4] *} Значение, полученное из `yield`, внедряется в *операции пути* и другие зависимости: -{* ../../docs_src/dependencies/tutorial007.py hl[4] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[4] *} Код, следующий за оператором `yield`, выполняется после ответа: -{* ../../docs_src/dependencies/tutorial007.py hl[5:6] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[5:6] *} /// tip | Подсказка @@ -57,7 +57,7 @@ FastAPI поддерживает зависимости, которые выпо Точно так же можно использовать `finally`, чтобы убедиться, что обязательные шаги при выходе выполнены независимо от того, было ли исключение или нет. -{* ../../docs_src/dependencies/tutorial007.py hl[3,5] *} +{* ../../docs_src/dependencies/tutorial007_py39.py hl[3,5] *} ## Подзависимости с `yield` { #sub-dependencies-with-yield } @@ -269,7 +269,7 @@ with open("./somefile.txt") as f: Их также можно использовать внутри зависимостей **FastAPI** с `yield`, применяя операторы `with` или `async with` внутри функции зависимости: -{* ../../docs_src/dependencies/tutorial010.py hl[1:9,13] *} +{* ../../docs_src/dependencies/tutorial010_py39.py hl[1:9,13] *} /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/dependencies/global-dependencies.md b/docs/ru/docs/tutorial/dependencies/global-dependencies.md index 075d6b0baa..2347c6dd83 100644 --- a/docs/ru/docs/tutorial/dependencies/global-dependencies.md +++ b/docs/ru/docs/tutorial/dependencies/global-dependencies.md @@ -6,7 +6,7 @@ В этом случае они будут применяться ко всем *операциям пути* в приложении: -{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[16] *} +{* ../../docs_src/dependencies/tutorial012_an_py39.py hl[17] *} Все способы [добавления `dependencies` (зависимостей) в *декораторах операций пути*](dependencies-in-path-operation-decorators.md){.internal-link target=_blank} по-прежнему применимы, но в данном случае зависимости применяются ко всем *операциям пути* приложения. diff --git a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md index efe8d98c31..da31a6682f 100644 --- a/docs/ru/docs/tutorial/dependencies/sub-dependencies.md +++ b/docs/ru/docs/tutorial/dependencies/sub-dependencies.md @@ -62,7 +62,7 @@ query_extractor --> query_or_cookie_extractor --> read_query В расширенном сценарии, когда вы знаете, что вам нужно, чтобы зависимость вызывалась на каждом шаге (возможно, несколько раз) в одном и том же запросе, вместо использования "кэшированного" значения, вы можете установить параметр `use_cache=False` при использовании `Depends`: -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```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.8+ без Annotated +//// tab | Python 3.9+ без Annotated /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/first-steps.md b/docs/ru/docs/tutorial/first-steps.md index 6f59d72054..798c03d517 100644 --- a/docs/ru/docs/tutorial/first-steps.md +++ b/docs/ru/docs/tutorial/first-steps.md @@ -2,7 +2,7 @@ Самый простой файл FastAPI может выглядеть так: -{* ../../docs_src/first_steps/tutorial001.py *} +{* ../../docs_src/first_steps/tutorial001_py39.py *} Скопируйте это в файл `main.py`. @@ -183,7 +183,7 @@ Deploying to FastAPI Cloud... ### Шаг 1: импортируйте `FastAPI` { #step-1-import-fastapi } -{* ../../docs_src/first_steps/tutorial001.py hl[1] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[1] *} `FastAPI` — это класс на Python, который предоставляет всю функциональность для вашего API. @@ -197,7 +197,7 @@ Deploying to FastAPI Cloud... ### Шаг 2: создайте экземпляр `FastAPI` { #step-2-create-a-fastapi-instance } -{* ../../docs_src/first_steps/tutorial001.py hl[3] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[3] *} Здесь переменная `app` будет экземпляром класса `FastAPI`. @@ -266,7 +266,7 @@ https://example.com/items/foo #### Определите *декоратор операции пути (path operation decorator)* { #define-a-path-operation-decorator } -{* ../../docs_src/first_steps/tutorial001.py hl[6] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[6] *} `@app.get("/")` сообщает **FastAPI**, что функция прямо под ним отвечает за обработку запросов, поступающих: @@ -320,7 +320,7 @@ https://example.com/items/foo * **операция**: `get`. * **функция**: функция ниже «декоратора» (ниже `@app.get("/")`). -{* ../../docs_src/first_steps/tutorial001.py hl[7] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[7] *} Это функция на Python. @@ -332,7 +332,7 @@ https://example.com/items/foo Вы также можете определить её как обычную функцию вместо `async def`: -{* ../../docs_src/first_steps/tutorial003.py hl[7] *} +{* ../../docs_src/first_steps/tutorial003_py39.py hl[7] *} /// note | Примечание @@ -342,7 +342,7 @@ https://example.com/items/foo ### Шаг 5: верните содержимое { #step-5-return-the-content } -{* ../../docs_src/first_steps/tutorial001.py hl[8] *} +{* ../../docs_src/first_steps/tutorial001_py39.py hl[8] *} Вы можете вернуть `dict`, `list`, отдельные значения `str`, `int` и т.д. diff --git a/docs/ru/docs/tutorial/handling-errors.md b/docs/ru/docs/tutorial/handling-errors.md index 63ca8665ef..2e00d70759 100644 --- a/docs/ru/docs/tutorial/handling-errors.md +++ b/docs/ru/docs/tutorial/handling-errors.md @@ -25,7 +25,7 @@ ### Импортируйте `HTTPException` { #import-httpexception } -{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[1] *} ### Вызовите `HTTPException` в своем коде { #raise-an-httpexception-in-your-code } @@ -39,7 +39,7 @@ В данном примере, когда клиент запрашивает элемент по несуществующему ID, возникает исключение со статус-кодом `404`: -{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} +{* ../../docs_src/handling_errors/tutorial001_py39.py hl[11] *} ### Возвращаемый ответ { #the-resulting-response } @@ -77,7 +77,7 @@ Но в случае, если это необходимо для продвинутого сценария, можно добавить пользовательские заголовки: -{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial002_py39.py hl[14] *} ## Установка пользовательских обработчиков исключений { #install-custom-exception-handlers } @@ -89,7 +89,7 @@ Можно добавить собственный обработчик исключений с помощью `@app.exception_handler()`: -{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} +{* ../../docs_src/handling_errors/tutorial003_py39.py hl[5:7,13:18,24] *} Здесь, если запросить `/unicorns/yolo`, то *операция пути* вызовет `UnicornException`. @@ -127,7 +127,7 @@ Обработчик исключения получит объект `Request` и исключение. -{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:19] *} +{* ../../docs_src/handling_errors/tutorial004_py39.py hl[2,14:19] *} Теперь, если перейти к `/items/foo`, то вместо стандартной JSON-ошибки с: @@ -159,7 +159,7 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa Например, для этих ошибок можно вернуть обычный текстовый ответ вместо JSON: -{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,25] *} +{* ../../docs_src/handling_errors/tutorial004_py39.py hl[3:4,9:11,25] *} /// note | Технические детали @@ -183,7 +183,7 @@ Field: ('path', 'item_id'), Error: Input should be a valid integer, unable to pa Вы можете использовать его при разработке приложения для регистрации тела и его отладки, возврата пользователю и т.д. -{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} +{* ../../docs_src/handling_errors/tutorial005_py39.py hl[14] *} Теперь попробуйте отправить недействительный элемент, например: @@ -239,6 +239,6 @@ from starlette.exceptions import HTTPException as StarletteHTTPException Если вы хотите использовать исключение вместе с теми же обработчиками исключений по умолчанию из **FastAPI**, вы можете импортировать и повторно использовать обработчики исключений по умолчанию из `fastapi.exception_handlers`: -{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} +{* ../../docs_src/handling_errors/tutorial006_py39.py hl[2:5,15,21] *} В этом примере вы просто `выводите в терминал` ошибку с очень выразительным сообщением, но идея вам понятна. Вы можете использовать исключение, а затем просто повторно использовать стандартные обработчики исключений. diff --git a/docs/ru/docs/tutorial/metadata.md b/docs/ru/docs/tutorial/metadata.md index 2e359752e1..e4fe5fb544 100644 --- a/docs/ru/docs/tutorial/metadata.md +++ b/docs/ru/docs/tutorial/metadata.md @@ -18,7 +18,7 @@ Вы можете задать их следующим образом: -{* ../../docs_src/metadata/tutorial001.py hl[3:16, 19:32] *} +{* ../../docs_src/metadata/tutorial001_py39.py hl[3:16, 19:32] *} /// tip | Подсказка @@ -36,7 +36,7 @@ К примеру: -{* ../../docs_src/metadata/tutorial001_1.py hl[31] *} +{* ../../docs_src/metadata/tutorial001_1_py39.py hl[31] *} ## Метаданные для тегов { #metadata-for-tags } @@ -58,7 +58,7 @@ Создайте метаданные для ваших тегов и передайте их в параметре `openapi_tags`: -{* ../../docs_src/metadata/tutorial004.py hl[3:16,18] *} +{* ../../docs_src/metadata/tutorial004_py39.py hl[3:16,18] *} Помните, что вы можете использовать Markdown внутри описания, к примеру "login" будет отображен жирным шрифтом (**login**) и "fancy" будет отображаться курсивом (_fancy_). @@ -72,7 +72,7 @@ Используйте параметр `tags` с вашими *операциями пути* (и `APIRouter`ами), чтобы присвоить им различные теги: -{* ../../docs_src/metadata/tutorial004.py hl[21,26] *} +{* ../../docs_src/metadata/tutorial004_py39.py hl[21,26] *} /// info | Дополнительная информация @@ -100,7 +100,7 @@ К примеру, чтобы задать её отображение по адресу `/api/v1/openapi.json`: -{* ../../docs_src/metadata/tutorial002.py hl[3] *} +{* ../../docs_src/metadata/tutorial002_py39.py hl[3] *} Если вы хотите отключить схему OpenAPI полностью, вы можете задать `openapi_url=None`, это также отключит пользовательские интерфейсы документации, которые её используют. @@ -117,4 +117,4 @@ К примеру, чтобы задать отображение Swagger UI по адресу `/documentation` и отключить ReDoc: -{* ../../docs_src/metadata/tutorial003.py hl[3] *} +{* ../../docs_src/metadata/tutorial003_py39.py hl[3] *} diff --git a/docs/ru/docs/tutorial/middleware.md b/docs/ru/docs/tutorial/middleware.md index 5803b398b9..a83d3c0111 100644 --- a/docs/ru/docs/tutorial/middleware.md +++ b/docs/ru/docs/tutorial/middleware.md @@ -33,7 +33,7 @@ * Затем она возвращает ответ `response`, сгенерированный *операцией пути*. * Также имеется возможность видоизменить `response`, перед тем как его вернуть. -{* ../../docs_src/middleware/tutorial001.py hl[8:9,11,14] *} +{* ../../docs_src/middleware/tutorial001_py39.py hl[8:9,11,14] *} /// tip | Примечание @@ -59,7 +59,7 @@ Например, вы можете добавить собственный заголовок `X-Process-Time`, содержащий время в секундах, необходимое для обработки запроса и генерации ответа: -{* ../../docs_src/middleware/tutorial001.py hl[10,12:13] *} +{* ../../docs_src/middleware/tutorial001_py39.py hl[10,12:13] *} /// tip | Примечание diff --git a/docs/ru/docs/tutorial/path-operation-configuration.md b/docs/ru/docs/tutorial/path-operation-configuration.md index 63b48a3941..96a54ffea0 100644 --- a/docs/ru/docs/tutorial/path-operation-configuration.md +++ b/docs/ru/docs/tutorial/path-operation-configuration.md @@ -46,7 +46,7 @@ **FastAPI** поддерживает это так же, как и в случае с обычными строками: -{* ../../docs_src/path_operation_configuration/tutorial002b.py hl[1,8:10,13,18] *} +{* ../../docs_src/path_operation_configuration/tutorial002b_py39.py hl[1,8:10,13,18] *} ## Краткое и развёрнутое содержание { #summary-and-description } @@ -92,7 +92,7 @@ OpenAPI указывает, что каждой *операции пути* не Если вам необходимо пометить *операцию пути* как устаревшую, при этом не удаляя её, передайте параметр `deprecated`: -{* ../../docs_src/path_operation_configuration/tutorial006.py hl[16] *} +{* ../../docs_src/path_operation_configuration/tutorial006_py39.py hl[16] *} Он будет четко помечен как устаревший в интерактивной документации: diff --git a/docs/ru/docs/tutorial/path-params-numeric-validations.md b/docs/ru/docs/tutorial/path-params-numeric-validations.md index ccea1945e4..f0fe788051 100644 --- a/docs/ru/docs/tutorial/path-params-numeric-validations.md +++ b/docs/ru/docs/tutorial/path-params-numeric-validations.md @@ -54,7 +54,7 @@ Path-параметр всегда является обязательным, п Поэтому вы можете определить функцию так: -{* ../../docs_src/path_params_numeric_validations/tutorial002.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial002_py39.py hl[7] *} Но имейте в виду, что если вы используете `Annotated`, вы не столкнётесь с этой проблемой, так как вы не используете значения по умолчанию параметров функции для `Query()` или `Path()`. @@ -83,7 +83,7 @@ Path-параметр всегда является обязательным, п Python не будет ничего делать с `*`, но он будет знать, что все следующие параметры являются именованными аргументами (парами ключ-значение), также известными как kwargs, даже если у них нет значений по умолчанию. -{* ../../docs_src/path_params_numeric_validations/tutorial003.py hl[7] *} +{* ../../docs_src/path_params_numeric_validations/tutorial003_py39.py hl[7] *} ### Лучше с `Annotated` { #better-with-annotated } diff --git a/docs/ru/docs/tutorial/path-params.md b/docs/ru/docs/tutorial/path-params.md index f7d138afbe..83a7ed3ffe 100644 --- a/docs/ru/docs/tutorial/path-params.md +++ b/docs/ru/docs/tutorial/path-params.md @@ -2,7 +2,7 @@ Вы можете определить "параметры" или "переменные" пути, используя синтаксис форматированных строк Python: -{* ../../docs_src/path_params/tutorial001.py hl[6:7] *} +{* ../../docs_src/path_params/tutorial001_py39.py hl[6:7] *} Значение параметра пути `item_id` будет передано в функцию в качестве аргумента `item_id`. @@ -16,7 +16,7 @@ Вы можете объявить тип параметра пути в функции, используя стандартные аннотации типов Python: -{* ../../docs_src/path_params/tutorial002.py hl[7] *} +{* ../../docs_src/path_params/tutorial002_py39.py hl[7] *} Здесь, `item_id` объявлен типом `int`. @@ -118,13 +118,13 @@ Поскольку *операции пути* выполняются в порядке их объявления, необходимо, чтобы путь для `/users/me` был объявлен раньше, чем путь для `/users/{user_id}`: -{* ../../docs_src/path_params/tutorial003.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003_py39.py hl[6,11] *} Иначе путь для `/users/{user_id}` также будет соответствовать `/users/me`, "подразумевая", что он получает параметр `user_id` со значением `"me"`. Аналогично, вы не можете переопределить операцию с путем: -{* ../../docs_src/path_params/tutorial003b.py hl[6,11] *} +{* ../../docs_src/path_params/tutorial003b_py39.py hl[6,11] *} Первый будет выполняться всегда, так как путь совпадает первым. @@ -140,13 +140,7 @@ Затем создайте атрибуты класса с фиксированными допустимыми значениями: -{* ../../docs_src/path_params/tutorial005.py hl[1,6:9] *} - -/// info | Дополнительная информация - -Перечисления (enum) доступны в Python начиная с версии 3.4. - -/// +{* ../../docs_src/path_params/tutorial005_py39.py hl[1,6:9] *} /// tip | Подсказка @@ -158,7 +152,7 @@ Определите *параметр пути*, используя в аннотации типа класс перечисления (`ModelName`), созданный ранее: -{* ../../docs_src/path_params/tutorial005.py hl[16] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[16] *} ### Проверьте документацию { #check-the-docs } @@ -174,13 +168,13 @@ Вы можете сравнить это значение с *элементом перечисления* класса `ModelName`: -{* ../../docs_src/path_params/tutorial005.py hl[17] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[17] *} #### Получение *значения перечисления* { #get-the-enumeration-value } Можно получить фактическое значение (в данном случае - `str`) с помощью `model_name.value` или в общем случае `your_enum_member.value`: -{* ../../docs_src/path_params/tutorial005.py hl[20] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[20] *} /// tip | Подсказка @@ -194,7 +188,7 @@ Они будут преобразованы в соответствующие значения (в данном случае - строки) перед их возвратом клиенту: -{* ../../docs_src/path_params/tutorial005.py hl[18,21,23] *} +{* ../../docs_src/path_params/tutorial005_py39.py hl[18,21,23] *} Вы отправите клиенту такой JSON-ответ: ```JSON @@ -232,7 +226,7 @@ OpenAPI не поддерживает способов объявления *п Можете использовать так: -{* ../../docs_src/path_params/tutorial004.py hl[6] *} +{* ../../docs_src/path_params/tutorial004_py39.py hl[6] *} /// tip | Подсказка diff --git a/docs/ru/docs/tutorial/query-params-str-validations.md b/docs/ru/docs/tutorial/query-params-str-validations.md index 302901d4ec..3a4ecc37dc 100644 --- a/docs/ru/docs/tutorial/query-params-str-validations.md +++ b/docs/ru/docs/tutorial/query-params-str-validations.md @@ -55,7 +55,7 @@ q: str | None = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Union[str, None] = None @@ -73,7 +73,7 @@ q: Annotated[str | None] = None //// -//// tab | Python 3.8+ +//// tab | Python 3.9+ ```Python q: Annotated[Union[str, None]] = None diff --git a/docs/ru/docs/tutorial/query-params.md b/docs/ru/docs/tutorial/query-params.md index 5a84f9768c..be1c0e46e1 100644 --- a/docs/ru/docs/tutorial/query-params.md +++ b/docs/ru/docs/tutorial/query-params.md @@ -2,7 +2,7 @@ Когда вы объявляете параметры функции, которые не являются параметрами пути, они автоматически интерпретируются как "query"-параметры. -{* ../../docs_src/query_params/tutorial001.py hl[9] *} +{* ../../docs_src/query_params/tutorial001_py39.py hl[9] *} Query-параметры представляют из себя набор пар ключ-значение, которые идут после знака `?` в URL-адресе, разделенные символами `&`. @@ -127,7 +127,7 @@ http://127.0.0.1:8000/items/foo?short=yes Но если вы хотите сделать query-параметр обязательным, вы можете просто не указывать значение по умолчанию: -{* ../../docs_src/query_params/tutorial005.py hl[6:7] *} +{* ../../docs_src/query_params/tutorial005_py39.py hl[6:7] *} Здесь параметр запроса `needy` является обязательным параметром с типом данных `str`. diff --git a/docs/ru/docs/tutorial/response-model.md b/docs/ru/docs/tutorial/response-model.md index c045f9ed78..07308c1db2 100644 --- a/docs/ru/docs/tutorial/response-model.md +++ b/docs/ru/docs/tutorial/response-model.md @@ -183,7 +183,7 @@ FastAPI делает несколько вещей внутри вместе с Самый распространённый случай — [возвращать Response напрямую, как описано далее в разделах для продвинутых](../advanced/response-directly.md){.internal-link target=_blank}. -{* ../../docs_src/response_model/tutorial003_02.py hl[8,10:11] *} +{* ../../docs_src/response_model/tutorial003_02_py39.py hl[8,10:11] *} Этот простой случай обрабатывается FastAPI автоматически, потому что аннотация возвращаемого типа — это класс (или подкласс) `Response`. @@ -193,7 +193,7 @@ FastAPI делает несколько вещей внутри вместе с Вы также можете использовать подкласс `Response` в аннотации типа: -{* ../../docs_src/response_model/tutorial003_03.py hl[8:9] *} +{* ../../docs_src/response_model/tutorial003_03_py39.py hl[8:9] *} Это тоже сработает, так как `RedirectResponse` — подкласс `Response`, и FastAPI автоматически обработает этот случай. diff --git a/docs/ru/docs/tutorial/response-status-code.md b/docs/ru/docs/tutorial/response-status-code.md index f5b1ff6ad7..30f642b643 100644 --- a/docs/ru/docs/tutorial/response-status-code.md +++ b/docs/ru/docs/tutorial/response-status-code.md @@ -8,7 +8,7 @@ * `@app.delete()` * и других. -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} /// note | Примечание @@ -74,7 +74,7 @@ FastAPI знает об этом и создаст документацию Open Рассмотрим предыдущий пример еще раз: -{* ../../docs_src/response_status_code/tutorial001.py hl[6] *} +{* ../../docs_src/response_status_code/tutorial001_py39.py hl[6] *} `201` – это код статуса "Создано". @@ -82,7 +82,7 @@ FastAPI знает об этом и создаст документацию Open Для удобства вы можете использовать переменные из `fastapi.status`. -{* ../../docs_src/response_status_code/tutorial002.py hl[1,6] *} +{* ../../docs_src/response_status_code/tutorial002_py39.py hl[1,6] *} Они содержат те же числовые значения, но позволяют использовать автозавершение редактора кода для выбора кода статуса: diff --git a/docs/ru/docs/tutorial/static-files.md b/docs/ru/docs/tutorial/static-files.md index 8455aea0a2..f40cfe9b04 100644 --- a/docs/ru/docs/tutorial/static-files.md +++ b/docs/ru/docs/tutorial/static-files.md @@ -7,7 +7,7 @@ * Импортируйте `StaticFiles`. * "Примонтируйте" экземпляр `StaticFiles()` к определённому пути. -{* ../../docs_src/static_files/tutorial001.py hl[2,6] *} +{* ../../docs_src/static_files/tutorial001_py39.py hl[2,6] *} /// note | Технические детали diff --git a/docs/ru/docs/tutorial/testing.md b/docs/ru/docs/tutorial/testing.md index 7354ed895f..ab58429c51 100644 --- a/docs/ru/docs/tutorial/testing.md +++ b/docs/ru/docs/tutorial/testing.md @@ -30,7 +30,7 @@ $ pip install httpx Напишите простое утверждение с `assert` дабы проверить истинность Python-выражения (это тоже стандарт `pytest`). -{* ../../docs_src/app_testing/tutorial001.py hl[2,12,15:18] *} +{* ../../docs_src/app_testing/tutorial001_py39.py hl[2,12,15:18] *} /// tip | Подсказка @@ -76,7 +76,7 @@ $ pip install httpx В файле `main.py` находится Ваше приложение **FastAPI**: -{* ../../docs_src/app_testing/main.py *} +{* ../../docs_src/app_testing/app_a_py39/main.py *} ### Файл тестов { #testing-file } @@ -92,7 +92,7 @@ $ pip install httpx Так как оба файла находятся в одной директории, для импорта объекта приложения из файла `main` в файл `test_main` Вы можете использовать относительный импорт: -{* ../../docs_src/app_testing/test_main.py hl[3] *} +{* ../../docs_src/app_testing/app_a_py39/test_main.py hl[3] *} ...и писать дальше тесты, как и раньше. diff --git a/docs_src/additional_responses/tutorial001.py b/docs_src/additional_responses/tutorial001_py39.py similarity index 100% rename from docs_src/additional_responses/tutorial001.py rename to docs_src/additional_responses/tutorial001_py39.py diff --git a/docs_src/additional_responses/tutorial002.py b/docs_src/additional_responses/tutorial002_py39.py similarity index 100% rename from docs_src/additional_responses/tutorial002.py rename to docs_src/additional_responses/tutorial002_py39.py diff --git a/docs_src/additional_responses/tutorial003.py b/docs_src/additional_responses/tutorial003_py39.py similarity index 100% rename from docs_src/additional_responses/tutorial003.py rename to docs_src/additional_responses/tutorial003_py39.py diff --git a/docs_src/additional_responses/tutorial004.py b/docs_src/additional_responses/tutorial004_py39.py similarity index 100% rename from docs_src/additional_responses/tutorial004.py rename to docs_src/additional_responses/tutorial004_py39.py diff --git a/docs_src/additional_status_codes/tutorial001_an.py b/docs_src/additional_status_codes/tutorial001_an.py deleted file mode 100644 index b5ad6a16b6..0000000000 --- a/docs_src/additional_status_codes/tutorial001_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI, status -from fastapi.responses import JSONResponse -from typing_extensions import Annotated - -app = FastAPI() - -items = {"foo": {"name": "Fighters", "size": 6}, "bar": {"name": "Tenders", "size": 3}} - - -@app.put("/items/{item_id}") -async def upsert_item( - item_id: str, - name: Annotated[Union[str, None], Body()] = None, - size: Annotated[Union[int, None], Body()] = None, -): - if item_id in items: - item = items[item_id] - item["name"] = name - item["size"] = size - return item - else: - item = {"name": name, "size": size} - items[item_id] = item - return JSONResponse(status_code=status.HTTP_201_CREATED, content=item) diff --git a/docs_src/additional_status_codes/tutorial001.py b/docs_src/additional_status_codes/tutorial001_py39.py similarity index 100% rename from docs_src/additional_status_codes/tutorial001.py rename to docs_src/additional_status_codes/tutorial001_py39.py diff --git a/docs_src/advanced_middleware/tutorial001.py b/docs_src/advanced_middleware/tutorial001_py39.py similarity index 100% rename from docs_src/advanced_middleware/tutorial001.py rename to docs_src/advanced_middleware/tutorial001_py39.py diff --git a/docs_src/advanced_middleware/tutorial002.py b/docs_src/advanced_middleware/tutorial002_py39.py similarity index 100% rename from docs_src/advanced_middleware/tutorial002.py rename to docs_src/advanced_middleware/tutorial002_py39.py diff --git a/docs_src/advanced_middleware/tutorial003.py b/docs_src/advanced_middleware/tutorial003_py39.py similarity index 100% rename from docs_src/advanced_middleware/tutorial003.py rename to docs_src/advanced_middleware/tutorial003_py39.py diff --git a/docs_src/app_testing/app_b/__init__.py b/docs_src/app_testing/app_a_py39/__init__.py similarity index 100% rename from docs_src/app_testing/app_b/__init__.py rename to docs_src/app_testing/app_a_py39/__init__.py diff --git a/docs_src/app_testing/main.py b/docs_src/app_testing/app_a_py39/main.py similarity index 100% rename from docs_src/app_testing/main.py rename to docs_src/app_testing/app_a_py39/main.py diff --git a/docs_src/app_testing/test_main.py b/docs_src/app_testing/app_a_py39/test_main.py similarity index 100% rename from docs_src/app_testing/test_main.py rename to docs_src/app_testing/app_a_py39/test_main.py diff --git a/docs_src/app_testing/app_b_an/main.py b/docs_src/app_testing/app_b_an/main.py deleted file mode 100644 index c66278fdd0..0000000000 --- a/docs_src/app_testing/app_b_an/main.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header, HTTPException -from pydantic import BaseModel -from typing_extensions import Annotated - -fake_secret_token = "coneofsilence" - -fake_db = { - "foo": {"id": "foo", "title": "Foo", "description": "There goes my hero"}, - "bar": {"id": "bar", "title": "Bar", "description": "The bartenders"}, -} - -app = FastAPI() - - -class Item(BaseModel): - id: str - title: str - description: Union[str, None] = None - - -@app.get("/items/{item_id}", response_model=Item) -async def read_main(item_id: str, x_token: Annotated[str, Header()]): - if x_token != fake_secret_token: - raise HTTPException(status_code=400, detail="Invalid X-Token header") - if item_id not in fake_db: - raise HTTPException(status_code=404, detail="Item not found") - return fake_db[item_id] - - -@app.post("/items/", response_model=Item) -async def create_item(item: Item, x_token: Annotated[str, Header()]): - if x_token != fake_secret_token: - raise HTTPException(status_code=400, detail="Invalid X-Token header") - if item.id in fake_db: - raise HTTPException(status_code=409, detail="Item already exists") - fake_db[item.id] = item - return item diff --git a/docs_src/app_testing/app_b_an/test_main.py b/docs_src/app_testing/app_b_an/test_main.py deleted file mode 100644 index 4e1c51ecc8..0000000000 --- a/docs_src/app_testing/app_b_an/test_main.py +++ /dev/null @@ -1,65 +0,0 @@ -from fastapi.testclient import TestClient - -from .main import app - -client = TestClient(app) - - -def test_read_item(): - response = client.get("/items/foo", headers={"X-Token": "coneofsilence"}) - assert response.status_code == 200 - assert response.json() == { - "id": "foo", - "title": "Foo", - "description": "There goes my hero", - } - - -def test_read_item_bad_token(): - response = client.get("/items/foo", headers={"X-Token": "hailhydra"}) - assert response.status_code == 400 - assert response.json() == {"detail": "Invalid X-Token header"} - - -def test_read_nonexistent_item(): - response = client.get("/items/baz", headers={"X-Token": "coneofsilence"}) - assert response.status_code == 404 - assert response.json() == {"detail": "Item not found"} - - -def test_create_item(): - response = client.post( - "/items/", - headers={"X-Token": "coneofsilence"}, - json={"id": "foobar", "title": "Foo Bar", "description": "The Foo Barters"}, - ) - assert response.status_code == 200 - assert response.json() == { - "id": "foobar", - "title": "Foo Bar", - "description": "The Foo Barters", - } - - -def test_create_item_bad_token(): - response = client.post( - "/items/", - headers={"X-Token": "hailhydra"}, - json={"id": "bazz", "title": "Bazz", "description": "Drop the bazz"}, - ) - assert response.status_code == 400 - assert response.json() == {"detail": "Invalid X-Token header"} - - -def test_create_existing_item(): - response = client.post( - "/items/", - headers={"X-Token": "coneofsilence"}, - json={ - "id": "foo", - "title": "The Foo ID Stealers", - "description": "There goes my stealer", - }, - ) - assert response.status_code == 409 - assert response.json() == {"detail": "Item already exists"} diff --git a/docs_src/app_testing/app_b_an/__init__.py b/docs_src/app_testing/app_b_py39/__init__.py similarity index 100% rename from docs_src/app_testing/app_b_an/__init__.py rename to docs_src/app_testing/app_b_py39/__init__.py diff --git a/docs_src/app_testing/app_b/main.py b/docs_src/app_testing/app_b_py39/main.py similarity index 100% rename from docs_src/app_testing/app_b/main.py rename to docs_src/app_testing/app_b_py39/main.py diff --git a/docs_src/app_testing/app_b/test_main.py b/docs_src/app_testing/app_b_py39/test_main.py similarity index 100% rename from docs_src/app_testing/app_b/test_main.py rename to docs_src/app_testing/app_b_py39/test_main.py diff --git a/docs_src/app_testing/tutorial001.py b/docs_src/app_testing/tutorial001_py39.py similarity index 100% rename from docs_src/app_testing/tutorial001.py rename to docs_src/app_testing/tutorial001_py39.py diff --git a/docs_src/app_testing/tutorial002.py b/docs_src/app_testing/tutorial002_py39.py similarity index 100% rename from docs_src/app_testing/tutorial002.py rename to docs_src/app_testing/tutorial002_py39.py diff --git a/docs_src/app_testing/tutorial003.py b/docs_src/app_testing/tutorial003_py39.py similarity index 100% rename from docs_src/app_testing/tutorial003.py rename to docs_src/app_testing/tutorial003_py39.py diff --git a/docs_src/app_testing/tutorial004.py b/docs_src/app_testing/tutorial004_py39.py similarity index 100% rename from docs_src/app_testing/tutorial004.py rename to docs_src/app_testing/tutorial004_py39.py diff --git a/docs_src/bigger_applications/app/__init__.py b/docs_src/async_tests/app_a_py39/__init__.py similarity index 100% rename from docs_src/bigger_applications/app/__init__.py rename to docs_src/async_tests/app_a_py39/__init__.py diff --git a/docs_src/async_tests/main.py b/docs_src/async_tests/app_a_py39/main.py similarity index 100% rename from docs_src/async_tests/main.py rename to docs_src/async_tests/app_a_py39/main.py diff --git a/docs_src/async_tests/test_main.py b/docs_src/async_tests/app_a_py39/test_main.py similarity index 100% rename from docs_src/async_tests/test_main.py rename to docs_src/async_tests/app_a_py39/test_main.py diff --git a/docs_src/authentication_error_status_code/tutorial001_an.py b/docs_src/authentication_error_status_code/tutorial001_an.py deleted file mode 100644 index 40678e858d..0000000000 --- a/docs_src/authentication_error_status_code/tutorial001_an.py +++ /dev/null @@ -1,20 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer -from typing_extensions import Annotated - -app = FastAPI() - - -class HTTPBearer403(HTTPBearer): - def make_not_authenticated_error(self) -> HTTPException: - return HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="Not authenticated" - ) - - -CredentialsDep = Annotated[HTTPAuthorizationCredentials, Depends(HTTPBearer403())] - - -@app.get("/me") -def read_me(credentials: CredentialsDep): - return {"message": "You are authenticated", "token": credentials.credentials} diff --git a/docs_src/background_tasks/tutorial001.py b/docs_src/background_tasks/tutorial001_py39.py similarity index 100% rename from docs_src/background_tasks/tutorial001.py rename to docs_src/background_tasks/tutorial001_py39.py diff --git a/docs_src/background_tasks/tutorial002_an.py b/docs_src/background_tasks/tutorial002_an.py deleted file mode 100644 index f63502b09a..0000000000 --- a/docs_src/background_tasks/tutorial002_an.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Union - -from fastapi import BackgroundTasks, Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -def write_log(message: str): - with open("log.txt", mode="a") as log: - log.write(message) - - -def get_query(background_tasks: BackgroundTasks, q: Union[str, None] = None): - if q: - message = f"found query: {q}\n" - background_tasks.add_task(write_log, message) - return q - - -@app.post("/send-notification/{email}") -async def send_notification( - email: str, background_tasks: BackgroundTasks, q: Annotated[str, Depends(get_query)] -): - message = f"message to {email}\n" - background_tasks.add_task(write_log, message) - return {"message": "Message sent"} diff --git a/docs_src/background_tasks/tutorial002.py b/docs_src/background_tasks/tutorial002_py39.py similarity index 100% rename from docs_src/background_tasks/tutorial002.py rename to docs_src/background_tasks/tutorial002_py39.py diff --git a/docs_src/behind_a_proxy/tutorial001_01.py b/docs_src/behind_a_proxy/tutorial001_01_py39.py similarity index 100% rename from docs_src/behind_a_proxy/tutorial001_01.py rename to docs_src/behind_a_proxy/tutorial001_01_py39.py diff --git a/docs_src/behind_a_proxy/tutorial001.py b/docs_src/behind_a_proxy/tutorial001_py39.py similarity index 100% rename from docs_src/behind_a_proxy/tutorial001.py rename to docs_src/behind_a_proxy/tutorial001_py39.py diff --git a/docs_src/behind_a_proxy/tutorial002.py b/docs_src/behind_a_proxy/tutorial002_py39.py similarity index 100% rename from docs_src/behind_a_proxy/tutorial002.py rename to docs_src/behind_a_proxy/tutorial002_py39.py diff --git a/docs_src/behind_a_proxy/tutorial003.py b/docs_src/behind_a_proxy/tutorial003_py39.py similarity index 100% rename from docs_src/behind_a_proxy/tutorial003.py rename to docs_src/behind_a_proxy/tutorial003_py39.py diff --git a/docs_src/behind_a_proxy/tutorial004.py b/docs_src/behind_a_proxy/tutorial004_py39.py similarity index 100% rename from docs_src/behind_a_proxy/tutorial004.py rename to docs_src/behind_a_proxy/tutorial004_py39.py diff --git a/docs_src/bigger_applications/app_an/dependencies.py b/docs_src/bigger_applications/app_an/dependencies.py deleted file mode 100644 index 1374c54b31..0000000000 --- a/docs_src/bigger_applications/app_an/dependencies.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import Header, HTTPException -from typing_extensions import Annotated - - -async def get_token_header(x_token: Annotated[str, Header()]): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def get_query_token(token: str): - if token != "jessica": - raise HTTPException(status_code=400, detail="No Jessica token provided") diff --git a/docs_src/bigger_applications/app_an/internal/admin.py b/docs_src/bigger_applications/app_an/internal/admin.py deleted file mode 100644 index 99d3da86b9..0000000000 --- a/docs_src/bigger_applications/app_an/internal/admin.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.post("/") -async def update_admin(): - return {"message": "Admin getting schwifty"} diff --git a/docs_src/bigger_applications/app_an/main.py b/docs_src/bigger_applications/app_an/main.py deleted file mode 100644 index ae544a3aac..0000000000 --- a/docs_src/bigger_applications/app_an/main.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi import Depends, FastAPI - -from .dependencies import get_query_token, get_token_header -from .internal import admin -from .routers import items, users - -app = FastAPI(dependencies=[Depends(get_query_token)]) - - -app.include_router(users.router) -app.include_router(items.router) -app.include_router( - admin.router, - prefix="/admin", - tags=["admin"], - dependencies=[Depends(get_token_header)], - responses={418: {"description": "I'm a teapot"}}, -) - - -@app.get("/") -async def root(): - return {"message": "Hello Bigger Applications!"} diff --git a/docs_src/bigger_applications/app_an/routers/items.py b/docs_src/bigger_applications/app_an/routers/items.py deleted file mode 100644 index bde9ff4d55..0000000000 --- a/docs_src/bigger_applications/app_an/routers/items.py +++ /dev/null @@ -1,38 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException - -from ..dependencies import get_token_header - -router = APIRouter( - prefix="/items", - tags=["items"], - dependencies=[Depends(get_token_header)], - responses={404: {"description": "Not found"}}, -) - - -fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}} - - -@router.get("/") -async def read_items(): - return fake_items_db - - -@router.get("/{item_id}") -async def read_item(item_id: str): - if item_id not in fake_items_db: - raise HTTPException(status_code=404, detail="Item not found") - return {"name": fake_items_db[item_id]["name"], "item_id": item_id} - - -@router.put( - "/{item_id}", - tags=["custom"], - responses={403: {"description": "Operation forbidden"}}, -) -async def update_item(item_id: str): - if item_id != "plumbus": - raise HTTPException( - status_code=403, detail="You can only update the item: plumbus" - ) - return {"item_id": item_id, "name": "The great Plumbus"} diff --git a/docs_src/bigger_applications/app_an/routers/users.py b/docs_src/bigger_applications/app_an/routers/users.py deleted file mode 100644 index 39b3d7e7cf..0000000000 --- a/docs_src/bigger_applications/app_an/routers/users.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import APIRouter - -router = APIRouter() - - -@router.get("/users/", tags=["users"]) -async def read_users(): - return [{"username": "Rick"}, {"username": "Morty"}] - - -@router.get("/users/me", tags=["users"]) -async def read_user_me(): - return {"username": "fakecurrentuser"} - - -@router.get("/users/{username}", tags=["users"]) -async def read_user(username: str): - return {"username": username} diff --git a/docs_src/bigger_applications/app/internal/__init__.py b/docs_src/bigger_applications/app_py39/__init__.py similarity index 100% rename from docs_src/bigger_applications/app/internal/__init__.py rename to docs_src/bigger_applications/app_py39/__init__.py diff --git a/docs_src/bigger_applications/app/dependencies.py b/docs_src/bigger_applications/app_py39/dependencies.py similarity index 100% rename from docs_src/bigger_applications/app/dependencies.py rename to docs_src/bigger_applications/app_py39/dependencies.py diff --git a/docs_src/bigger_applications/app/routers/__init__.py b/docs_src/bigger_applications/app_py39/internal/__init__.py similarity index 100% rename from docs_src/bigger_applications/app/routers/__init__.py rename to docs_src/bigger_applications/app_py39/internal/__init__.py diff --git a/docs_src/bigger_applications/app/internal/admin.py b/docs_src/bigger_applications/app_py39/internal/admin.py similarity index 100% rename from docs_src/bigger_applications/app/internal/admin.py rename to docs_src/bigger_applications/app_py39/internal/admin.py diff --git a/docs_src/bigger_applications/app/main.py b/docs_src/bigger_applications/app_py39/main.py similarity index 100% rename from docs_src/bigger_applications/app/main.py rename to docs_src/bigger_applications/app_py39/main.py diff --git a/docs_src/bigger_applications/app_an/__init__.py b/docs_src/bigger_applications/app_py39/routers/__init__.py similarity index 100% rename from docs_src/bigger_applications/app_an/__init__.py rename to docs_src/bigger_applications/app_py39/routers/__init__.py diff --git a/docs_src/bigger_applications/app/routers/items.py b/docs_src/bigger_applications/app_py39/routers/items.py similarity index 100% rename from docs_src/bigger_applications/app/routers/items.py rename to docs_src/bigger_applications/app_py39/routers/items.py diff --git a/docs_src/bigger_applications/app/routers/users.py b/docs_src/bigger_applications/app_py39/routers/users.py similarity index 100% rename from docs_src/bigger_applications/app/routers/users.py rename to docs_src/bigger_applications/app_py39/routers/users.py diff --git a/docs_src/body/tutorial001.py b/docs_src/body/tutorial001_py39.py similarity index 100% rename from docs_src/body/tutorial001.py rename to docs_src/body/tutorial001_py39.py diff --git a/docs_src/body/tutorial002.py b/docs_src/body/tutorial002_py39.py similarity index 100% rename from docs_src/body/tutorial002.py rename to docs_src/body/tutorial002_py39.py diff --git a/docs_src/body/tutorial003.py b/docs_src/body/tutorial003_py39.py similarity index 100% rename from docs_src/body/tutorial003.py rename to docs_src/body/tutorial003_py39.py diff --git a/docs_src/body/tutorial004.py b/docs_src/body/tutorial004_py39.py similarity index 100% rename from docs_src/body/tutorial004.py rename to docs_src/body/tutorial004_py39.py diff --git a/docs_src/body_fields/tutorial001_an.py b/docs_src/body_fields/tutorial001_an.py deleted file mode 100644 index 15ea1b53dc..0000000000 --- a/docs_src/body_fields/tutorial001_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel, Field -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = Field( - default=None, title="The description of the item", max_length=300 - ) - price: float = Field(gt=0, description="The price must be greater than zero") - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_fields/tutorial001.py b/docs_src/body_fields/tutorial001_py39.py similarity index 100% rename from docs_src/body_fields/tutorial001.py rename to docs_src/body_fields/tutorial001_py39.py diff --git a/docs_src/body_multiple_params/tutorial001_an.py b/docs_src/body_multiple_params/tutorial001_an.py deleted file mode 100644 index 308eee8544..0000000000 --- a/docs_src/body_multiple_params/tutorial001_an.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Path -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)], - q: Union[str, None] = None, - item: Union[Item, None] = None, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - if item: - results.update({"item": item}) - return results diff --git a/docs_src/body_multiple_params/tutorial001.py b/docs_src/body_multiple_params/tutorial001_py39.py similarity index 100% rename from docs_src/body_multiple_params/tutorial001.py rename to docs_src/body_multiple_params/tutorial001_py39.py diff --git a/docs_src/body_multiple_params/tutorial002.py b/docs_src/body_multiple_params/tutorial002_py39.py similarity index 100% rename from docs_src/body_multiple_params/tutorial002.py rename to docs_src/body_multiple_params/tutorial002_py39.py diff --git a/docs_src/body_multiple_params/tutorial003_an.py b/docs_src/body_multiple_params/tutorial003_an.py deleted file mode 100644 index 39ef7340a5..0000000000 --- a/docs_src/body_multiple_params/tutorial003_an.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: int, item: Item, user: User, importance: Annotated[int, Body()] -): - results = {"item_id": item_id, "item": item, "user": user, "importance": importance} - return results diff --git a/docs_src/body_multiple_params/tutorial003.py b/docs_src/body_multiple_params/tutorial003_py39.py similarity index 100% rename from docs_src/body_multiple_params/tutorial003.py rename to docs_src/body_multiple_params/tutorial003_py39.py diff --git a/docs_src/body_multiple_params/tutorial004_an.py b/docs_src/body_multiple_params/tutorial004_an.py deleted file mode 100644 index f6830f3920..0000000000 --- a/docs_src/body_multiple_params/tutorial004_an.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -class User(BaseModel): - username: str - full_name: Union[str, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Item, - user: User, - importance: Annotated[int, Body(gt=0)], - q: Union[str, None] = None, -): - results = {"item_id": item_id, "item": item, "user": user, "importance": importance} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/body_multiple_params/tutorial004.py b/docs_src/body_multiple_params/tutorial004_py39.py similarity index 100% rename from docs_src/body_multiple_params/tutorial004.py rename to docs_src/body_multiple_params/tutorial004_py39.py diff --git a/docs_src/body_multiple_params/tutorial005_an.py b/docs_src/body_multiple_params/tutorial005_an.py deleted file mode 100644 index dadde80b55..0000000000 --- a/docs_src/body_multiple_params/tutorial005_an.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_multiple_params/tutorial005.py b/docs_src/body_multiple_params/tutorial005_py39.py similarity index 100% rename from docs_src/body_multiple_params/tutorial005.py rename to docs_src/body_multiple_params/tutorial005_py39.py diff --git a/docs_src/body_nested_models/tutorial001.py b/docs_src/body_nested_models/tutorial001_py39.py similarity index 100% rename from docs_src/body_nested_models/tutorial001.py rename to docs_src/body_nested_models/tutorial001_py39.py diff --git a/docs_src/body_nested_models/tutorial002.py b/docs_src/body_nested_models/tutorial002.py deleted file mode 100644 index 155cff7885..0000000000 --- a/docs_src/body_nested_models/tutorial002.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: List[str] = [] - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial003.py b/docs_src/body_nested_models/tutorial003.py deleted file mode 100644 index 84ed18bf48..0000000000 --- a/docs_src/body_nested_models/tutorial003.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial004.py b/docs_src/body_nested_models/tutorial004.py deleted file mode 100644 index a07bfacac1..0000000000 --- a/docs_src/body_nested_models/tutorial004.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Image(BaseModel): - url: str - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - image: Union[Image, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial005.py b/docs_src/body_nested_models/tutorial005.py deleted file mode 100644 index 5a01264eda..0000000000 --- a/docs_src/body_nested_models/tutorial005.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - image: Union[Image, None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial006.py b/docs_src/body_nested_models/tutorial006.py deleted file mode 100644 index 75f1f30e33..0000000000 --- a/docs_src/body_nested_models/tutorial006.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import List, Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - images: Union[List[Image], None] = None - - -@app.put("/items/{item_id}") -async def update_item(item_id: int, item: Item): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/body_nested_models/tutorial007.py b/docs_src/body_nested_models/tutorial007.py deleted file mode 100644 index 641f09dced..0000000000 --- a/docs_src/body_nested_models/tutorial007.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import List, Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - images: Union[List[Image], None] = None - - -class Offer(BaseModel): - name: str - description: Union[str, None] = None - price: float - items: List[Item] - - -@app.post("/offers/") -async def create_offer(offer: Offer): - return offer diff --git a/docs_src/body_nested_models/tutorial008.py b/docs_src/body_nested_models/tutorial008.py deleted file mode 100644 index 3431cc6365..0000000000 --- a/docs_src/body_nested_models/tutorial008.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import List - -from fastapi import FastAPI -from pydantic import BaseModel, HttpUrl - -app = FastAPI() - - -class Image(BaseModel): - url: HttpUrl - name: str - - -@app.post("/images/multiple/") -async def create_multiple_images(images: List[Image]): - return images diff --git a/docs_src/body_nested_models/tutorial009.py b/docs_src/body_nested_models/tutorial009.py deleted file mode 100644 index 41dce946ec..0000000000 --- a/docs_src/body_nested_models/tutorial009.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Dict - -from fastapi import FastAPI - -app = FastAPI() - - -@app.post("/index-weights/") -async def create_index_weights(weights: Dict[int, float]): - return weights diff --git a/docs_src/body_updates/tutorial001.py b/docs_src/body_updates/tutorial001.py deleted file mode 100644 index 4e65d77e26..0000000000 --- a/docs_src/body_updates/tutorial001.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from fastapi.encoders import jsonable_encoder -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: Union[str, None] = None - description: Union[str, None] = None - price: Union[float, None] = None - tax: float = 10.5 - tags: List[str] = [] - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, -} - - -@app.get("/items/{item_id}", response_model=Item) -async def read_item(item_id: str): - return items[item_id] - - -@app.put("/items/{item_id}", response_model=Item) -async def update_item(item_id: str, item: Item): - update_item_encoded = jsonable_encoder(item) - items[item_id] = update_item_encoded - return update_item_encoded diff --git a/docs_src/body_updates/tutorial002.py b/docs_src/body_updates/tutorial002.py deleted file mode 100644 index c3a0fe79ea..0000000000 --- a/docs_src/body_updates/tutorial002.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from fastapi.encoders import jsonable_encoder -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: Union[str, None] = None - description: Union[str, None] = None - price: Union[float, None] = None - tax: float = 10.5 - tags: List[str] = [] - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, -} - - -@app.get("/items/{item_id}", response_model=Item) -async def read_item(item_id: str): - return items[item_id] - - -@app.patch("/items/{item_id}", response_model=Item) -async def update_item(item_id: str, item: Item): - stored_item_data = items[item_id] - stored_item_model = Item(**stored_item_data) - update_data = item.dict(exclude_unset=True) - updated_item = stored_item_model.copy(update=update_data) - items[item_id] = jsonable_encoder(updated_item) - return updated_item diff --git a/docs_src/conditional_openapi/tutorial001.py b/docs_src/conditional_openapi/tutorial001_py39.py similarity index 100% rename from docs_src/conditional_openapi/tutorial001.py rename to docs_src/conditional_openapi/tutorial001_py39.py diff --git a/docs_src/configure_swagger_ui/tutorial001.py b/docs_src/configure_swagger_ui/tutorial001_py39.py similarity index 100% rename from docs_src/configure_swagger_ui/tutorial001.py rename to docs_src/configure_swagger_ui/tutorial001_py39.py diff --git a/docs_src/configure_swagger_ui/tutorial002.py b/docs_src/configure_swagger_ui/tutorial002_py39.py similarity index 100% rename from docs_src/configure_swagger_ui/tutorial002.py rename to docs_src/configure_swagger_ui/tutorial002_py39.py diff --git a/docs_src/configure_swagger_ui/tutorial003.py b/docs_src/configure_swagger_ui/tutorial003_py39.py similarity index 100% rename from docs_src/configure_swagger_ui/tutorial003.py rename to docs_src/configure_swagger_ui/tutorial003_py39.py diff --git a/docs_src/cookie_param_models/tutorial001_an.py b/docs_src/cookie_param_models/tutorial001_an.py deleted file mode 100644 index e5839ffd54..0000000000 --- a/docs_src/cookie_param_models/tutorial001_an.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Cookies(BaseModel): - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial001.py b/docs_src/cookie_param_models/tutorial001_py39.py similarity index 100% rename from docs_src/cookie_param_models/tutorial001.py rename to docs_src/cookie_param_models/tutorial001_py39.py diff --git a/docs_src/cookie_param_models/tutorial002_an.py b/docs_src/cookie_param_models/tutorial002_an.py deleted file mode 100644 index ce5644b7bd..0000000000 --- a/docs_src/cookie_param_models/tutorial002_an.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Cookies(BaseModel): - model_config = {"extra": "forbid"} - - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an.py b/docs_src/cookie_param_models/tutorial002_pv1_an.py deleted file mode 100644 index ddfda9b6f5..0000000000 --- a/docs_src/cookie_param_models/tutorial002_pv1_an.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Cookies(BaseModel): - class Config: - extra = "forbid" - - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1.py b/docs_src/cookie_param_models/tutorial002_pv1_py39.py similarity index 100% rename from docs_src/cookie_param_models/tutorial002_pv1.py rename to docs_src/cookie_param_models/tutorial002_pv1_py39.py diff --git a/docs_src/cookie_param_models/tutorial002.py b/docs_src/cookie_param_models/tutorial002_py39.py similarity index 100% rename from docs_src/cookie_param_models/tutorial002.py rename to docs_src/cookie_param_models/tutorial002_py39.py diff --git a/docs_src/cookie_params/tutorial001_an.py b/docs_src/cookie_params/tutorial001_an.py deleted file mode 100644 index 6d5931229c..0000000000 --- a/docs_src/cookie_params/tutorial001_an.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(ads_id: Annotated[Union[str, None], Cookie()] = None): - return {"ads_id": ads_id} diff --git a/docs_src/cookie_params/tutorial001.py b/docs_src/cookie_params/tutorial001_py39.py similarity index 100% rename from docs_src/cookie_params/tutorial001.py rename to docs_src/cookie_params/tutorial001_py39.py diff --git a/docs_src/cors/tutorial001.py b/docs_src/cors/tutorial001_py39.py similarity index 100% rename from docs_src/cors/tutorial001.py rename to docs_src/cors/tutorial001_py39.py diff --git a/docs_src/custom_docs_ui/tutorial001.py b/docs_src/custom_docs_ui/tutorial001_py39.py similarity index 100% rename from docs_src/custom_docs_ui/tutorial001.py rename to docs_src/custom_docs_ui/tutorial001_py39.py diff --git a/docs_src/custom_docs_ui/tutorial002.py b/docs_src/custom_docs_ui/tutorial002_py39.py similarity index 100% rename from docs_src/custom_docs_ui/tutorial002.py rename to docs_src/custom_docs_ui/tutorial002_py39.py diff --git a/docs_src/custom_request_and_route/tutorial001.py b/docs_src/custom_request_and_route/tutorial001.py deleted file mode 100644 index 268ce9019e..0000000000 --- a/docs_src/custom_request_and_route/tutorial001.py +++ /dev/null @@ -1,35 +0,0 @@ -import gzip -from typing import Callable, List - -from fastapi import Body, FastAPI, Request, Response -from fastapi.routing import APIRoute - - -class GzipRequest(Request): - async def body(self) -> bytes: - if not hasattr(self, "_body"): - body = await super().body() - if "gzip" in self.headers.getlist("Content-Encoding"): - body = gzip.decompress(body) - self._body = body - return self._body - - -class GzipRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - request = GzipRequest(request.scope, request.receive) - return await original_route_handler(request) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = GzipRoute - - -@app.post("/sum") -async def sum_numbers(numbers: List[int] = Body()): - return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial001_an.py b/docs_src/custom_request_and_route/tutorial001_an.py deleted file mode 100644 index 6224ba8254..0000000000 --- a/docs_src/custom_request_and_route/tutorial001_an.py +++ /dev/null @@ -1,36 +0,0 @@ -import gzip -from typing import Callable, List - -from fastapi import Body, FastAPI, Request, Response -from fastapi.routing import APIRoute -from typing_extensions import Annotated - - -class GzipRequest(Request): - async def body(self) -> bytes: - if not hasattr(self, "_body"): - body = await super().body() - if "gzip" in self.headers.getlist("Content-Encoding"): - body = gzip.decompress(body) - self._body = body - return self._body - - -class GzipRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - request = GzipRequest(request.scope, request.receive) - return await original_route_handler(request) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = GzipRoute - - -@app.post("/sum") -async def sum_numbers(numbers: Annotated[List[int], Body()]): - return {"sum": sum(numbers)} diff --git a/docs_src/custom_request_and_route/tutorial002.py b/docs_src/custom_request_and_route/tutorial002.py deleted file mode 100644 index cee4a95f08..0000000000 --- a/docs_src/custom_request_and_route/tutorial002.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Callable, List - -from fastapi import Body, FastAPI, HTTPException, Request, Response -from fastapi.exceptions import RequestValidationError -from fastapi.routing import APIRoute - - -class ValidationErrorLoggingRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - try: - return await original_route_handler(request) - except RequestValidationError as exc: - body = await request.body() - detail = {"errors": exc.errors(), "body": body.decode()} - raise HTTPException(status_code=422, detail=detail) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = ValidationErrorLoggingRoute - - -@app.post("/") -async def sum_numbers(numbers: List[int] = Body()): - return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial002_an.py b/docs_src/custom_request_and_route/tutorial002_an.py deleted file mode 100644 index 127f7a9ce0..0000000000 --- a/docs_src/custom_request_and_route/tutorial002_an.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Callable, List - -from fastapi import Body, FastAPI, HTTPException, Request, Response -from fastapi.exceptions import RequestValidationError -from fastapi.routing import APIRoute -from typing_extensions import Annotated - - -class ValidationErrorLoggingRoute(APIRoute): - def get_route_handler(self) -> Callable: - original_route_handler = super().get_route_handler() - - async def custom_route_handler(request: Request) -> Response: - try: - return await original_route_handler(request) - except RequestValidationError as exc: - body = await request.body() - detail = {"errors": exc.errors(), "body": body.decode()} - raise HTTPException(status_code=422, detail=detail) - - return custom_route_handler - - -app = FastAPI() -app.router.route_class = ValidationErrorLoggingRoute - - -@app.post("/") -async def sum_numbers(numbers: Annotated[List[int], Body()]): - return sum(numbers) diff --git a/docs_src/custom_request_and_route/tutorial003.py b/docs_src/custom_request_and_route/tutorial003_py39.py similarity index 100% rename from docs_src/custom_request_and_route/tutorial003.py rename to docs_src/custom_request_and_route/tutorial003_py39.py diff --git a/docs_src/custom_response/tutorial001.py b/docs_src/custom_response/tutorial001_py39.py similarity index 100% rename from docs_src/custom_response/tutorial001.py rename to docs_src/custom_response/tutorial001_py39.py diff --git a/docs_src/custom_response/tutorial001b.py b/docs_src/custom_response/tutorial001b_py39.py similarity index 100% rename from docs_src/custom_response/tutorial001b.py rename to docs_src/custom_response/tutorial001b_py39.py diff --git a/docs_src/custom_response/tutorial002.py b/docs_src/custom_response/tutorial002_py39.py similarity index 100% rename from docs_src/custom_response/tutorial002.py rename to docs_src/custom_response/tutorial002_py39.py diff --git a/docs_src/custom_response/tutorial003.py b/docs_src/custom_response/tutorial003_py39.py similarity index 100% rename from docs_src/custom_response/tutorial003.py rename to docs_src/custom_response/tutorial003_py39.py diff --git a/docs_src/custom_response/tutorial004.py b/docs_src/custom_response/tutorial004_py39.py similarity index 100% rename from docs_src/custom_response/tutorial004.py rename to docs_src/custom_response/tutorial004_py39.py diff --git a/docs_src/custom_response/tutorial005.py b/docs_src/custom_response/tutorial005_py39.py similarity index 100% rename from docs_src/custom_response/tutorial005.py rename to docs_src/custom_response/tutorial005_py39.py diff --git a/docs_src/custom_response/tutorial006.py b/docs_src/custom_response/tutorial006_py39.py similarity index 100% rename from docs_src/custom_response/tutorial006.py rename to docs_src/custom_response/tutorial006_py39.py diff --git a/docs_src/custom_response/tutorial006b.py b/docs_src/custom_response/tutorial006b_py39.py similarity index 100% rename from docs_src/custom_response/tutorial006b.py rename to docs_src/custom_response/tutorial006b_py39.py diff --git a/docs_src/custom_response/tutorial006c.py b/docs_src/custom_response/tutorial006c_py39.py similarity index 100% rename from docs_src/custom_response/tutorial006c.py rename to docs_src/custom_response/tutorial006c_py39.py diff --git a/docs_src/custom_response/tutorial007.py b/docs_src/custom_response/tutorial007_py39.py similarity index 100% rename from docs_src/custom_response/tutorial007.py rename to docs_src/custom_response/tutorial007_py39.py diff --git a/docs_src/custom_response/tutorial008.py b/docs_src/custom_response/tutorial008_py39.py similarity index 100% rename from docs_src/custom_response/tutorial008.py rename to docs_src/custom_response/tutorial008_py39.py diff --git a/docs_src/custom_response/tutorial009.py b/docs_src/custom_response/tutorial009_py39.py similarity index 100% rename from docs_src/custom_response/tutorial009.py rename to docs_src/custom_response/tutorial009_py39.py diff --git a/docs_src/custom_response/tutorial009b.py b/docs_src/custom_response/tutorial009b_py39.py similarity index 100% rename from docs_src/custom_response/tutorial009b.py rename to docs_src/custom_response/tutorial009b_py39.py diff --git a/docs_src/custom_response/tutorial009c.py b/docs_src/custom_response/tutorial009c_py39.py similarity index 100% rename from docs_src/custom_response/tutorial009c.py rename to docs_src/custom_response/tutorial009c_py39.py diff --git a/docs_src/custom_response/tutorial010.py b/docs_src/custom_response/tutorial010_py39.py similarity index 100% rename from docs_src/custom_response/tutorial010.py rename to docs_src/custom_response/tutorial010_py39.py diff --git a/docs_src/dataclasses/tutorial001.py b/docs_src/dataclasses/tutorial001_py39.py similarity index 100% rename from docs_src/dataclasses/tutorial001.py rename to docs_src/dataclasses/tutorial001_py39.py diff --git a/docs_src/dataclasses/tutorial002.py b/docs_src/dataclasses/tutorial002.py deleted file mode 100644 index ece2f150cc..0000000000 --- a/docs_src/dataclasses/tutorial002.py +++ /dev/null @@ -1,26 +0,0 @@ -from dataclasses import dataclass, field -from typing import List, Union - -from fastapi import FastAPI - - -@dataclass -class Item: - name: str - price: float - tags: List[str] = field(default_factory=list) - description: Union[str, None] = None - tax: Union[float, None] = None - - -app = FastAPI() - - -@app.get("/items/next", response_model=Item) -async def read_next_item(): - return { - "name": "Island In The Moon", - "price": 12.99, - "description": "A place to be playin' and havin' fun", - "tags": ["breater"], - } diff --git a/docs_src/dataclasses/tutorial003.py b/docs_src/dataclasses/tutorial003.py deleted file mode 100644 index c613155133..0000000000 --- a/docs_src/dataclasses/tutorial003.py +++ /dev/null @@ -1,55 +0,0 @@ -from dataclasses import field # (1) -from typing import List, Union - -from fastapi import FastAPI -from pydantic.dataclasses import dataclass # (2) - - -@dataclass -class Item: - name: str - description: Union[str, None] = None - - -@dataclass -class Author: - name: str - items: List[Item] = field(default_factory=list) # (3) - - -app = FastAPI() - - -@app.post("/authors/{author_id}/items/", response_model=Author) # (4) -async def create_author_items(author_id: str, items: List[Item]): # (5) - return {"name": author_id, "items": items} # (6) - - -@app.get("/authors/", response_model=List[Author]) # (7) -def get_authors(): # (8) - return [ # (9) - { - "name": "Breaters", - "items": [ - { - "name": "Island In The Moon", - "description": "A place to be playin' and havin' fun", - }, - {"name": "Holy Buddies"}, - ], - }, - { - "name": "System of an Up", - "items": [ - { - "name": "Salt", - "description": "The kombucha mushroom people's favorite", - }, - {"name": "Pad Thai"}, - { - "name": "Lonely Night", - "description": "The mostests lonliest nightiest of allest", - }, - ], - }, - ] diff --git a/docs_src/debugging/tutorial001.py b/docs_src/debugging/tutorial001_py39.py similarity index 100% rename from docs_src/debugging/tutorial001.py rename to docs_src/debugging/tutorial001_py39.py diff --git a/docs_src/dependencies/tutorial001_02_an.py b/docs_src/dependencies/tutorial001_02_an.py deleted file mode 100644 index 455d60c822..0000000000 --- a/docs_src/dependencies/tutorial001_02_an.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -CommonsDep = Annotated[dict, Depends(common_parameters)] - - -@app.get("/items/") -async def read_items(commons: CommonsDep): - return commons - - -@app.get("/users/") -async def read_users(commons: CommonsDep): - return commons diff --git a/docs_src/dependencies/tutorial001_an.py b/docs_src/dependencies/tutorial001_an.py deleted file mode 100644 index 81e24fe86c..0000000000 --- a/docs_src/dependencies/tutorial001_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -@app.get("/items/") -async def read_items(commons: Annotated[dict, Depends(common_parameters)]): - return commons - - -@app.get("/users/") -async def read_users(commons: Annotated[dict, Depends(common_parameters)]): - return commons diff --git a/docs_src/dependencies/tutorial001.py b/docs_src/dependencies/tutorial001_py39.py similarity index 100% rename from docs_src/dependencies/tutorial001.py rename to docs_src/dependencies/tutorial001_py39.py diff --git a/docs_src/dependencies/tutorial002_an.py b/docs_src/dependencies/tutorial002_an.py deleted file mode 100644 index 964ccf66ca..0000000000 --- a/docs_src/dependencies/tutorial002_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial002.py b/docs_src/dependencies/tutorial002_py39.py similarity index 100% rename from docs_src/dependencies/tutorial002.py rename to docs_src/dependencies/tutorial002_py39.py diff --git a/docs_src/dependencies/tutorial003_an.py b/docs_src/dependencies/tutorial003_an.py deleted file mode 100644 index ba8e9f7174..0000000000 --- a/docs_src/dependencies/tutorial003_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Any, Union - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial003.py b/docs_src/dependencies/tutorial003_py39.py similarity index 100% rename from docs_src/dependencies/tutorial003.py rename to docs_src/dependencies/tutorial003_py39.py diff --git a/docs_src/dependencies/tutorial004_an.py b/docs_src/dependencies/tutorial004_an.py deleted file mode 100644 index 78881a354c..0000000000 --- a/docs_src/dependencies/tutorial004_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}] - - -class CommonQueryParams: - def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100): - self.q = q - self.skip = skip - self.limit = limit - - -@app.get("/items/") -async def read_items(commons: Annotated[CommonQueryParams, Depends()]): - response = {} - if commons.q: - response.update({"q": commons.q}) - items = fake_items_db[commons.skip : commons.skip + commons.limit] - response.update({"items": items}) - return response diff --git a/docs_src/dependencies/tutorial004.py b/docs_src/dependencies/tutorial004_py39.py similarity index 100% rename from docs_src/dependencies/tutorial004.py rename to docs_src/dependencies/tutorial004_py39.py diff --git a/docs_src/dependencies/tutorial005_an.py b/docs_src/dependencies/tutorial005_an.py deleted file mode 100644 index 1d78c17a2a..0000000000 --- a/docs_src/dependencies/tutorial005_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Union - -from fastapi import Cookie, Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -def query_extractor(q: Union[str, None] = None): - return q - - -def query_or_cookie_extractor( - q: Annotated[str, Depends(query_extractor)], - last_query: Annotated[Union[str, None], Cookie()] = None, -): - if not q: - return last_query - return q - - -@app.get("/items/") -async def read_query( - query_or_default: Annotated[str, Depends(query_or_cookie_extractor)], -): - return {"q_or_cookie": query_or_default} diff --git a/docs_src/dependencies/tutorial005.py b/docs_src/dependencies/tutorial005_py39.py similarity index 100% rename from docs_src/dependencies/tutorial005.py rename to docs_src/dependencies/tutorial005_py39.py diff --git a/docs_src/dependencies/tutorial006_an.py b/docs_src/dependencies/tutorial006_an.py deleted file mode 100644 index 5aaea04d16..0000000000 --- a/docs_src/dependencies/tutorial006_an.py +++ /dev/null @@ -1,20 +0,0 @@ -from fastapi import Depends, FastAPI, Header, HTTPException -from typing_extensions import Annotated - -app = FastAPI() - - -async def verify_token(x_token: Annotated[str, Header()]): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def verify_key(x_key: Annotated[str, Header()]): - if x_key != "fake-super-secret-key": - raise HTTPException(status_code=400, detail="X-Key header invalid") - return x_key - - -@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)]) -async def read_items(): - return [{"item": "Foo"}, {"item": "Bar"}] diff --git a/docs_src/dependencies/tutorial006.py b/docs_src/dependencies/tutorial006_py39.py similarity index 100% rename from docs_src/dependencies/tutorial006.py rename to docs_src/dependencies/tutorial006_py39.py diff --git a/docs_src/dependencies/tutorial007.py b/docs_src/dependencies/tutorial007_py39.py similarity index 100% rename from docs_src/dependencies/tutorial007.py rename to docs_src/dependencies/tutorial007_py39.py diff --git a/docs_src/dependencies/tutorial008_an.py b/docs_src/dependencies/tutorial008_an.py deleted file mode 100644 index 2de86f042d..0000000000 --- a/docs_src/dependencies/tutorial008_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from fastapi import Depends -from typing_extensions import Annotated - - -async def dependency_a(): - dep_a = generate_dep_a() - try: - yield dep_a - finally: - dep_a.close() - - -async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]): - dep_b = generate_dep_b() - try: - yield dep_b - finally: - dep_b.close(dep_a) - - -async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]): - dep_c = generate_dep_c() - try: - yield dep_c - finally: - dep_c.close(dep_b) diff --git a/docs_src/dependencies/tutorial008.py b/docs_src/dependencies/tutorial008_py39.py similarity index 100% rename from docs_src/dependencies/tutorial008.py rename to docs_src/dependencies/tutorial008_py39.py diff --git a/docs_src/dependencies/tutorial008b_an.py b/docs_src/dependencies/tutorial008b_an.py deleted file mode 100644 index 84d8f12c14..0000000000 --- a/docs_src/dependencies/tutorial008b_an.py +++ /dev/null @@ -1,31 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from typing_extensions import Annotated - -app = FastAPI() - - -data = { - "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"}, - "portal-gun": {"description": "Gun to create portals", "owner": "Rick"}, -} - - -class OwnerError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except OwnerError as e: - raise HTTPException(status_code=400, detail=f"Owner error: {e}") - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): - if item_id not in data: - raise HTTPException(status_code=404, detail="Item not found") - item = data[item_id] - if item["owner"] != username: - raise OwnerError(username) - return item diff --git a/docs_src/dependencies/tutorial008b.py b/docs_src/dependencies/tutorial008b_py39.py similarity index 100% rename from docs_src/dependencies/tutorial008b.py rename to docs_src/dependencies/tutorial008b_py39.py diff --git a/docs_src/dependencies/tutorial008c_an.py b/docs_src/dependencies/tutorial008c_an.py deleted file mode 100644 index 94f59f9aa6..0000000000 --- a/docs_src/dependencies/tutorial008c_an.py +++ /dev/null @@ -1,28 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from typing_extensions import Annotated - -app = FastAPI() - - -class InternalError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except InternalError: - print("Oops, we didn't raise again, Britney 😱") - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): - if item_id == "portal-gun": - raise InternalError( - f"The portal gun is too dangerous to be owned by {username}" - ) - if item_id != "plumbus": - raise HTTPException( - status_code=404, detail="Item not found, there's only a plumbus here" - ) - return item_id diff --git a/docs_src/dependencies/tutorial008c.py b/docs_src/dependencies/tutorial008c_py39.py similarity index 100% rename from docs_src/dependencies/tutorial008c.py rename to docs_src/dependencies/tutorial008c_py39.py diff --git a/docs_src/dependencies/tutorial008d_an.py b/docs_src/dependencies/tutorial008d_an.py deleted file mode 100644 index c354245744..0000000000 --- a/docs_src/dependencies/tutorial008d_an.py +++ /dev/null @@ -1,29 +0,0 @@ -from fastapi import Depends, FastAPI, HTTPException -from typing_extensions import Annotated - -app = FastAPI() - - -class InternalError(Exception): - pass - - -def get_username(): - try: - yield "Rick" - except InternalError: - print("We don't swallow the internal error here, we raise again 😎") - raise - - -@app.get("/items/{item_id}") -def get_item(item_id: str, username: Annotated[str, Depends(get_username)]): - if item_id == "portal-gun": - raise InternalError( - f"The portal gun is too dangerous to be owned by {username}" - ) - if item_id != "plumbus": - raise HTTPException( - status_code=404, detail="Item not found, there's only a plumbus here" - ) - return item_id diff --git a/docs_src/dependencies/tutorial008d.py b/docs_src/dependencies/tutorial008d_py39.py similarity index 100% rename from docs_src/dependencies/tutorial008d.py rename to docs_src/dependencies/tutorial008d_py39.py diff --git a/docs_src/dependencies/tutorial008e_an.py b/docs_src/dependencies/tutorial008e_an.py deleted file mode 100644 index c8a0af2b3b..0000000000 --- a/docs_src/dependencies/tutorial008e_an.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -def get_username(): - try: - yield "Rick" - finally: - print("Cleanup up before response is sent") - - -@app.get("/users/me") -def get_user_me(username: Annotated[str, Depends(get_username, scope="function")]): - return username diff --git a/docs_src/dependencies/tutorial008e.py b/docs_src/dependencies/tutorial008e_py39.py similarity index 100% rename from docs_src/dependencies/tutorial008e.py rename to docs_src/dependencies/tutorial008e_py39.py diff --git a/docs_src/dependencies/tutorial009.py b/docs_src/dependencies/tutorial009.py deleted file mode 100644 index 8472f642de..0000000000 --- a/docs_src/dependencies/tutorial009.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import Depends - - -async def dependency_a(): - dep_a = generate_dep_a() - try: - yield dep_a - finally: - dep_a.close() - - -async def dependency_b(dep_a=Depends(dependency_a)): - dep_b = generate_dep_b() - try: - yield dep_b - finally: - dep_b.close(dep_a) - - -async def dependency_c(dep_b=Depends(dependency_b)): - dep_c = generate_dep_c() - try: - yield dep_c - finally: - dep_c.close(dep_b) diff --git a/docs_src/dependencies/tutorial010.py b/docs_src/dependencies/tutorial010_py39.py similarity index 100% rename from docs_src/dependencies/tutorial010.py rename to docs_src/dependencies/tutorial010_py39.py diff --git a/docs_src/dependencies/tutorial011_an.py b/docs_src/dependencies/tutorial011_an.py deleted file mode 100644 index 6c13d9033f..0000000000 --- a/docs_src/dependencies/tutorial011_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -class FixedContentQueryChecker: - def __init__(self, fixed_content: str): - self.fixed_content = fixed_content - - def __call__(self, q: str = ""): - if q: - return self.fixed_content in q - return False - - -checker = FixedContentQueryChecker("bar") - - -@app.get("/query-checker/") -async def read_query_check(fixed_content_included: Annotated[bool, Depends(checker)]): - return {"fixed_content_in_query": fixed_content_included} diff --git a/docs_src/dependencies/tutorial011.py b/docs_src/dependencies/tutorial011_py39.py similarity index 100% rename from docs_src/dependencies/tutorial011.py rename to docs_src/dependencies/tutorial011_py39.py diff --git a/docs_src/dependencies/tutorial012_an.py b/docs_src/dependencies/tutorial012_an.py deleted file mode 100644 index 7541e6bf41..0000000000 --- a/docs_src/dependencies/tutorial012_an.py +++ /dev/null @@ -1,26 +0,0 @@ -from fastapi import Depends, FastAPI, Header, HTTPException -from typing_extensions import Annotated - - -async def verify_token(x_token: Annotated[str, Header()]): - if x_token != "fake-super-secret-token": - raise HTTPException(status_code=400, detail="X-Token header invalid") - - -async def verify_key(x_key: Annotated[str, Header()]): - if x_key != "fake-super-secret-key": - raise HTTPException(status_code=400, detail="X-Key header invalid") - return x_key - - -app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)]) - - -@app.get("/items/") -async def read_items(): - return [{"item": "Portal Gun"}, {"item": "Plumbus"}] - - -@app.get("/users/") -async def read_users(): - return [{"username": "Rick"}, {"username": "Morty"}] diff --git a/docs_src/dependencies/tutorial012_an_py39.py b/docs_src/dependencies/tutorial012_an_py39.py index 7541e6bf41..6503591fc3 100644 --- a/docs_src/dependencies/tutorial012_an_py39.py +++ b/docs_src/dependencies/tutorial012_an_py39.py @@ -1,5 +1,6 @@ +from typing import Annotated + from fastapi import Depends, FastAPI, Header, HTTPException -from typing_extensions import Annotated async def verify_token(x_token: Annotated[str, Header()]): diff --git a/docs_src/dependencies/tutorial012.py b/docs_src/dependencies/tutorial012_py39.py similarity index 100% rename from docs_src/dependencies/tutorial012.py rename to docs_src/dependencies/tutorial012_py39.py diff --git a/docs_src/dependency_testing/tutorial001_an.py b/docs_src/dependency_testing/tutorial001_an.py deleted file mode 100644 index 4c76a87ff5..0000000000 --- a/docs_src/dependency_testing/tutorial001_an.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from fastapi.testclient import TestClient -from typing_extensions import Annotated - -app = FastAPI() - - -async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 -): - return {"q": q, "skip": skip, "limit": limit} - - -@app.get("/items/") -async def read_items(commons: Annotated[dict, Depends(common_parameters)]): - return {"message": "Hello Items!", "params": commons} - - -@app.get("/users/") -async def read_users(commons: Annotated[dict, Depends(common_parameters)]): - return {"message": "Hello Users!", "params": commons} - - -client = TestClient(app) - - -async def override_dependency(q: Union[str, None] = None): - return {"q": q, "skip": 5, "limit": 10} - - -app.dependency_overrides[common_parameters] = override_dependency - - -def test_override_in_items(): - response = client.get("/items/") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": None, "skip": 5, "limit": 10}, - } - - -def test_override_in_items_with_q(): - response = client.get("/items/?q=foo") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": "foo", "skip": 5, "limit": 10}, - } - - -def test_override_in_items_with_params(): - response = client.get("/items/?q=foo&skip=100&limit=200") - assert response.status_code == 200 - assert response.json() == { - "message": "Hello Items!", - "params": {"q": "foo", "skip": 5, "limit": 10}, - } diff --git a/docs_src/dependency_testing/tutorial001.py b/docs_src/dependency_testing/tutorial001_py39.py similarity index 100% rename from docs_src/dependency_testing/tutorial001.py rename to docs_src/dependency_testing/tutorial001_py39.py diff --git a/docs_src/encoder/tutorial001.py b/docs_src/encoder/tutorial001_py39.py similarity index 100% rename from docs_src/encoder/tutorial001.py rename to docs_src/encoder/tutorial001_py39.py diff --git a/docs_src/events/tutorial001.py b/docs_src/events/tutorial001_py39.py similarity index 100% rename from docs_src/events/tutorial001.py rename to docs_src/events/tutorial001_py39.py diff --git a/docs_src/events/tutorial002.py b/docs_src/events/tutorial002_py39.py similarity index 100% rename from docs_src/events/tutorial002.py rename to docs_src/events/tutorial002_py39.py diff --git a/docs_src/events/tutorial003.py b/docs_src/events/tutorial003_py39.py similarity index 100% rename from docs_src/events/tutorial003.py rename to docs_src/events/tutorial003_py39.py diff --git a/docs_src/extending_openapi/tutorial001.py b/docs_src/extending_openapi/tutorial001_py39.py similarity index 100% rename from docs_src/extending_openapi/tutorial001.py rename to docs_src/extending_openapi/tutorial001_py39.py diff --git a/docs_src/extra_data_types/tutorial001_an.py b/docs_src/extra_data_types/tutorial001_an.py deleted file mode 100644 index 257d0c7c86..0000000000 --- a/docs_src/extra_data_types/tutorial001_an.py +++ /dev/null @@ -1,29 +0,0 @@ -from datetime import datetime, time, timedelta -from typing import Union -from uuid import UUID - -from fastapi import Body, FastAPI -from typing_extensions import Annotated - -app = FastAPI() - - -@app.put("/items/{item_id}") -async def read_items( - item_id: UUID, - start_datetime: Annotated[datetime, Body()], - end_datetime: Annotated[datetime, Body()], - process_after: Annotated[timedelta, Body()], - repeat_at: Annotated[Union[time, None], Body()] = None, -): - start_process = start_datetime + process_after - duration = end_datetime - start_process - return { - "item_id": item_id, - "start_datetime": start_datetime, - "end_datetime": end_datetime, - "process_after": process_after, - "repeat_at": repeat_at, - "start_process": start_process, - "duration": duration, - } diff --git a/docs_src/extra_data_types/tutorial001.py b/docs_src/extra_data_types/tutorial001_py39.py similarity index 100% rename from docs_src/extra_data_types/tutorial001.py rename to docs_src/extra_data_types/tutorial001_py39.py diff --git a/docs_src/extra_models/tutorial001.py b/docs_src/extra_models/tutorial001_py39.py similarity index 100% rename from docs_src/extra_models/tutorial001.py rename to docs_src/extra_models/tutorial001_py39.py diff --git a/docs_src/extra_models/tutorial002.py b/docs_src/extra_models/tutorial002_py39.py similarity index 100% rename from docs_src/extra_models/tutorial002.py rename to docs_src/extra_models/tutorial002_py39.py diff --git a/docs_src/extra_models/tutorial003.py b/docs_src/extra_models/tutorial003_py39.py similarity index 100% rename from docs_src/extra_models/tutorial003.py rename to docs_src/extra_models/tutorial003_py39.py diff --git a/docs_src/extra_models/tutorial004.py b/docs_src/extra_models/tutorial004.py deleted file mode 100644 index a8e0f7af5a..0000000000 --- a/docs_src/extra_models/tutorial004.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: str - - -items = [ - {"name": "Foo", "description": "There comes my hero"}, - {"name": "Red", "description": "It's my aeroplane"}, -] - - -@app.get("/items/", response_model=List[Item]) -async def read_items(): - return items diff --git a/docs_src/extra_models/tutorial005.py b/docs_src/extra_models/tutorial005.py deleted file mode 100644 index a81cbc2c58..0000000000 --- a/docs_src/extra_models/tutorial005.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Dict - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/keyword-weights/", response_model=Dict[str, float]) -async def read_keyword_weights(): - return {"foo": 2.3, "bar": 3.4} diff --git a/docs_src/first_steps/tutorial001.py b/docs_src/first_steps/tutorial001_py39.py similarity index 100% rename from docs_src/first_steps/tutorial001.py rename to docs_src/first_steps/tutorial001_py39.py diff --git a/docs_src/first_steps/tutorial002.py b/docs_src/first_steps/tutorial002.py deleted file mode 100644 index ca7d48cff9..0000000000 --- a/docs_src/first_steps/tutorial002.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -my_awesome_api = FastAPI() - - -@my_awesome_api.get("/") -async def root(): - return {"message": "Hello World"} diff --git a/docs_src/first_steps/tutorial003.py b/docs_src/first_steps/tutorial003_py39.py similarity index 100% rename from docs_src/first_steps/tutorial003.py rename to docs_src/first_steps/tutorial003_py39.py diff --git a/docs_src/generate_clients/tutorial001.py b/docs_src/generate_clients/tutorial001.py deleted file mode 100644 index 2d1f91bc6c..0000000000 --- a/docs_src/generate_clients/tutorial001.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import List - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - - -class ResponseMessage(BaseModel): - message: str - - -@app.post("/items/", response_model=ResponseMessage) -async def create_item(item: Item): - return {"message": "item received"} - - -@app.get("/items/", response_model=List[Item]) -async def get_items(): - return [ - {"name": "Plumbus", "price": 3}, - {"name": "Portal Gun", "price": 9001}, - ] diff --git a/docs_src/generate_clients/tutorial002.py b/docs_src/generate_clients/tutorial002.py deleted file mode 100644 index bd80449af9..0000000000 --- a/docs_src/generate_clients/tutorial002.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import List - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - - -class ResponseMessage(BaseModel): - message: str - - -class User(BaseModel): - username: str - email: str - - -@app.post("/items/", response_model=ResponseMessage, tags=["items"]) -async def create_item(item: Item): - return {"message": "Item received"} - - -@app.get("/items/", response_model=List[Item], tags=["items"]) -async def get_items(): - return [ - {"name": "Plumbus", "price": 3}, - {"name": "Portal Gun", "price": 9001}, - ] - - -@app.post("/users/", response_model=ResponseMessage, tags=["users"]) -async def create_user(user: User): - return {"message": "User received"} diff --git a/docs_src/generate_clients/tutorial003.py b/docs_src/generate_clients/tutorial003.py deleted file mode 100644 index 49eab73a1b..0000000000 --- a/docs_src/generate_clients/tutorial003.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import List - -from fastapi import FastAPI -from fastapi.routing import APIRoute -from pydantic import BaseModel - - -def custom_generate_unique_id(route: APIRoute): - return f"{route.tags[0]}-{route.name}" - - -app = FastAPI(generate_unique_id_function=custom_generate_unique_id) - - -class Item(BaseModel): - name: str - price: float - - -class ResponseMessage(BaseModel): - message: str - - -class User(BaseModel): - username: str - email: str - - -@app.post("/items/", response_model=ResponseMessage, tags=["items"]) -async def create_item(item: Item): - return {"message": "Item received"} - - -@app.get("/items/", response_model=List[Item], tags=["items"]) -async def get_items(): - return [ - {"name": "Plumbus", "price": 3}, - {"name": "Portal Gun", "price": 9001}, - ] - - -@app.post("/users/", response_model=ResponseMessage, tags=["users"]) -async def create_user(user: User): - return {"message": "User received"} diff --git a/docs_src/generate_clients/tutorial004.py b/docs_src/generate_clients/tutorial004_py39.py similarity index 100% rename from docs_src/generate_clients/tutorial004.py rename to docs_src/generate_clients/tutorial004_py39.py diff --git a/docs_src/graphql/tutorial001.py b/docs_src/graphql/tutorial001_py39.py similarity index 100% rename from docs_src/graphql/tutorial001.py rename to docs_src/graphql/tutorial001_py39.py diff --git a/docs_src/handling_errors/tutorial001.py b/docs_src/handling_errors/tutorial001_py39.py similarity index 100% rename from docs_src/handling_errors/tutorial001.py rename to docs_src/handling_errors/tutorial001_py39.py diff --git a/docs_src/handling_errors/tutorial002.py b/docs_src/handling_errors/tutorial002_py39.py similarity index 100% rename from docs_src/handling_errors/tutorial002.py rename to docs_src/handling_errors/tutorial002_py39.py diff --git a/docs_src/handling_errors/tutorial003.py b/docs_src/handling_errors/tutorial003_py39.py similarity index 100% rename from docs_src/handling_errors/tutorial003.py rename to docs_src/handling_errors/tutorial003_py39.py diff --git a/docs_src/handling_errors/tutorial004.py b/docs_src/handling_errors/tutorial004_py39.py similarity index 100% rename from docs_src/handling_errors/tutorial004.py rename to docs_src/handling_errors/tutorial004_py39.py diff --git a/docs_src/handling_errors/tutorial005.py b/docs_src/handling_errors/tutorial005_py39.py similarity index 100% rename from docs_src/handling_errors/tutorial005.py rename to docs_src/handling_errors/tutorial005_py39.py diff --git a/docs_src/handling_errors/tutorial006.py b/docs_src/handling_errors/tutorial006_py39.py similarity index 100% rename from docs_src/handling_errors/tutorial006.py rename to docs_src/handling_errors/tutorial006_py39.py diff --git a/docs_src/header_param_models/tutorial001.py b/docs_src/header_param_models/tutorial001.py deleted file mode 100644 index 4caaba87b9..0000000000 --- a/docs_src/header_param_models/tutorial001.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/header_param_models/tutorial001_an.py b/docs_src/header_param_models/tutorial001_an.py deleted file mode 100644 index b55c6b56b1..0000000000 --- a/docs_src/header_param_models/tutorial001_an.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial002.py b/docs_src/header_param_models/tutorial002.py deleted file mode 100644 index 3f9aac58d2..0000000000 --- a/docs_src/header_param_models/tutorial002.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - model_config = {"extra": "forbid"} - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/header_param_models/tutorial002_an.py b/docs_src/header_param_models/tutorial002_an.py deleted file mode 100644 index 771135d770..0000000000 --- a/docs_src/header_param_models/tutorial002_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class CommonHeaders(BaseModel): - model_config = {"extra": "forbid"} - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial002_pv1.py b/docs_src/header_param_models/tutorial002_pv1.py deleted file mode 100644 index 7e56cd993b..0000000000 --- a/docs_src/header_param_models/tutorial002_pv1.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - class Config: - extra = "forbid" - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an.py b/docs_src/header_param_models/tutorial002_pv1_an.py deleted file mode 100644 index 236778231a..0000000000 --- a/docs_src/header_param_models/tutorial002_pv1_an.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class CommonHeaders(BaseModel): - class Config: - extra = "forbid" - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial003.py b/docs_src/header_param_models/tutorial003.py deleted file mode 100644 index dc2eb74bd1..0000000000 --- a/docs_src/header_param_models/tutorial003.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header(convert_underscores=False)): - return headers diff --git a/docs_src/header_param_models/tutorial003_an.py b/docs_src/header_param_models/tutorial003_an.py deleted file mode 100644 index e3edb11891..0000000000 --- a/docs_src/header_param_models/tutorial003_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class CommonHeaders(BaseModel): - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: List[str] = [] - - -@app.get("/items/") -async def read_items( - headers: Annotated[CommonHeaders, Header(convert_underscores=False)], -): - return headers diff --git a/docs_src/header_params/tutorial001_an.py b/docs_src/header_params/tutorial001_an.py deleted file mode 100644 index 816c000862..0000000000 --- a/docs_src/header_params/tutorial001_an.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(user_agent: Annotated[Union[str, None], Header()] = None): - return {"User-Agent": user_agent} diff --git a/docs_src/header_params/tutorial001.py b/docs_src/header_params/tutorial001_py39.py similarity index 100% rename from docs_src/header_params/tutorial001.py rename to docs_src/header_params/tutorial001_py39.py diff --git a/docs_src/header_params/tutorial002_an.py b/docs_src/header_params/tutorial002_an.py deleted file mode 100644 index 82fe49ba2b..0000000000 --- a/docs_src/header_params/tutorial002_an.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - strange_header: Annotated[ - Union[str, None], Header(convert_underscores=False) - ] = None, -): - return {"strange_header": strange_header} diff --git a/docs_src/header_params/tutorial002.py b/docs_src/header_params/tutorial002_py39.py similarity index 100% rename from docs_src/header_params/tutorial002.py rename to docs_src/header_params/tutorial002_py39.py diff --git a/docs_src/header_params/tutorial003.py b/docs_src/header_params/tutorial003.py deleted file mode 100644 index a61314aedb..0000000000 --- a/docs_src/header_params/tutorial003.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header - -app = FastAPI() - - -@app.get("/items/") -async def read_items(x_token: Union[List[str], None] = Header(default=None)): - return {"X-Token values": x_token} diff --git a/docs_src/header_params/tutorial003_an.py b/docs_src/header_params/tutorial003_an.py deleted file mode 100644 index 5406fd1f88..0000000000 --- a/docs_src/header_params/tutorial003_an.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Header -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None): - return {"X-Token values": x_token} diff --git a/docs_src/metadata/tutorial001_1.py b/docs_src/metadata/tutorial001_1_py39.py similarity index 100% rename from docs_src/metadata/tutorial001_1.py rename to docs_src/metadata/tutorial001_1_py39.py diff --git a/docs_src/metadata/tutorial001.py b/docs_src/metadata/tutorial001_py39.py similarity index 100% rename from docs_src/metadata/tutorial001.py rename to docs_src/metadata/tutorial001_py39.py diff --git a/docs_src/metadata/tutorial002.py b/docs_src/metadata/tutorial002_py39.py similarity index 100% rename from docs_src/metadata/tutorial002.py rename to docs_src/metadata/tutorial002_py39.py diff --git a/docs_src/metadata/tutorial003.py b/docs_src/metadata/tutorial003_py39.py similarity index 100% rename from docs_src/metadata/tutorial003.py rename to docs_src/metadata/tutorial003_py39.py diff --git a/docs_src/metadata/tutorial004.py b/docs_src/metadata/tutorial004_py39.py similarity index 100% rename from docs_src/metadata/tutorial004.py rename to docs_src/metadata/tutorial004_py39.py diff --git a/docs_src/middleware/tutorial001.py b/docs_src/middleware/tutorial001_py39.py similarity index 100% rename from docs_src/middleware/tutorial001.py rename to docs_src/middleware/tutorial001_py39.py diff --git a/docs_src/openapi_callbacks/tutorial001.py b/docs_src/openapi_callbacks/tutorial001_py39.py similarity index 100% rename from docs_src/openapi_callbacks/tutorial001.py rename to docs_src/openapi_callbacks/tutorial001_py39.py diff --git a/docs_src/openapi_webhooks/tutorial001.py b/docs_src/openapi_webhooks/tutorial001_py39.py similarity index 100% rename from docs_src/openapi_webhooks/tutorial001.py rename to docs_src/openapi_webhooks/tutorial001_py39.py diff --git a/docs_src/path_operation_advanced_configuration/tutorial001.py b/docs_src/path_operation_advanced_configuration/tutorial001_py39.py similarity index 100% rename from docs_src/path_operation_advanced_configuration/tutorial001.py rename to docs_src/path_operation_advanced_configuration/tutorial001_py39.py diff --git a/docs_src/path_operation_advanced_configuration/tutorial002.py b/docs_src/path_operation_advanced_configuration/tutorial002_py39.py similarity index 100% rename from docs_src/path_operation_advanced_configuration/tutorial002.py rename to docs_src/path_operation_advanced_configuration/tutorial002_py39.py diff --git a/docs_src/path_operation_advanced_configuration/tutorial003.py b/docs_src/path_operation_advanced_configuration/tutorial003_py39.py similarity index 100% rename from docs_src/path_operation_advanced_configuration/tutorial003.py rename to docs_src/path_operation_advanced_configuration/tutorial003_py39.py diff --git a/docs_src/path_operation_advanced_configuration/tutorial004.py b/docs_src/path_operation_advanced_configuration/tutorial004.py deleted file mode 100644 index a3aad4ac40..0000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial004.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.post("/items/", response_model=Item, summary="Create an item") -async def create_item(item: Item): - """ - Create an item with all the information: - - - **name**: each item must have a name - - **description**: a long description - - **price**: required - - **tax**: if the item doesn't have tax, you can omit this - - **tags**: a set of unique tag strings for this item - \f - :param item: User input. - """ - return item diff --git a/docs_src/path_operation_advanced_configuration/tutorial005.py b/docs_src/path_operation_advanced_configuration/tutorial005_py39.py similarity index 100% rename from docs_src/path_operation_advanced_configuration/tutorial005.py rename to docs_src/path_operation_advanced_configuration/tutorial005_py39.py diff --git a/docs_src/path_operation_advanced_configuration/tutorial006.py b/docs_src/path_operation_advanced_configuration/tutorial006_py39.py similarity index 100% rename from docs_src/path_operation_advanced_configuration/tutorial006.py rename to docs_src/path_operation_advanced_configuration/tutorial006_py39.py diff --git a/docs_src/path_operation_advanced_configuration/tutorial007.py b/docs_src/path_operation_advanced_configuration/tutorial007.py deleted file mode 100644 index 54e2e9399e..0000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial007.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import List - -import yaml -from fastapi import FastAPI, HTTPException, Request -from pydantic import BaseModel, ValidationError - -app = FastAPI() - - -class Item(BaseModel): - name: str - tags: List[str] - - -@app.post( - "/items/", - openapi_extra={ - "requestBody": { - "content": {"application/x-yaml": {"schema": Item.model_json_schema()}}, - "required": True, - }, - }, -) -async def create_item(request: Request): - raw_body = await request.body() - try: - data = yaml.safe_load(raw_body) - except yaml.YAMLError: - raise HTTPException(status_code=422, detail="Invalid YAML") - try: - item = Item.model_validate(data) - except ValidationError as e: - raise HTTPException(status_code=422, detail=e.errors(include_url=False)) - return item diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1.py b/docs_src/path_operation_advanced_configuration/tutorial007_pv1.py deleted file mode 100644 index d51752bb87..0000000000 --- a/docs_src/path_operation_advanced_configuration/tutorial007_pv1.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import List - -import yaml -from fastapi import FastAPI, HTTPException, Request -from pydantic import BaseModel, ValidationError - -app = FastAPI() - - -class Item(BaseModel): - name: str - tags: List[str] - - -@app.post( - "/items/", - openapi_extra={ - "requestBody": { - "content": {"application/x-yaml": {"schema": Item.schema()}}, - "required": True, - }, - }, -) -async def create_item(request: Request): - raw_body = await request.body() - try: - data = yaml.safe_load(raw_body) - except yaml.YAMLError: - raise HTTPException(status_code=422, detail="Invalid YAML") - try: - item = Item.parse_obj(data) - except ValidationError as e: - raise HTTPException(status_code=422, detail=e.errors()) - return item diff --git a/docs_src/path_operation_configuration/tutorial001.py b/docs_src/path_operation_configuration/tutorial001.py deleted file mode 100644 index 83fd8377ab..0000000000 --- a/docs_src/path_operation_configuration/tutorial001.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI, status -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.post("/items/", response_model=Item, status_code=status.HTTP_201_CREATED) -async def create_item(item: Item): - return item diff --git a/docs_src/path_operation_configuration/tutorial002.py b/docs_src/path_operation_configuration/tutorial002.py deleted file mode 100644 index 798b0c2311..0000000000 --- a/docs_src/path_operation_configuration/tutorial002.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.post("/items/", response_model=Item, tags=["items"]) -async def create_item(item: Item): - return item - - -@app.get("/items/", tags=["items"]) -async def read_items(): - return [{"name": "Foo", "price": 42}] - - -@app.get("/users/", tags=["users"]) -async def read_users(): - return [{"username": "johndoe"}] diff --git a/docs_src/path_operation_configuration/tutorial002b.py b/docs_src/path_operation_configuration/tutorial002b_py39.py similarity index 100% rename from docs_src/path_operation_configuration/tutorial002b.py rename to docs_src/path_operation_configuration/tutorial002b_py39.py diff --git a/docs_src/path_operation_configuration/tutorial003.py b/docs_src/path_operation_configuration/tutorial003.py deleted file mode 100644 index 26bf7dabae..0000000000 --- a/docs_src/path_operation_configuration/tutorial003.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.post( - "/items/", - response_model=Item, - summary="Create an item", - description="Create an item with all the information, name, description, price, tax and a set of unique tags", -) -async def create_item(item: Item): - return item diff --git a/docs_src/path_operation_configuration/tutorial004.py b/docs_src/path_operation_configuration/tutorial004.py deleted file mode 100644 index 8f865c58a6..0000000000 --- a/docs_src/path_operation_configuration/tutorial004.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.post("/items/", response_model=Item, summary="Create an item") -async def create_item(item: Item): - """ - Create an item with all the information: - - - **name**: each item must have a name - - **description**: a long description - - **price**: required - - **tax**: if the item doesn't have tax, you can omit this - - **tags**: a set of unique tag strings for this item - """ - return item diff --git a/docs_src/path_operation_configuration/tutorial005.py b/docs_src/path_operation_configuration/tutorial005.py deleted file mode 100644 index 2c1be4a34b..0000000000 --- a/docs_src/path_operation_configuration/tutorial005.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Set, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: Set[str] = set() - - -@app.post( - "/items/", - response_model=Item, - summary="Create an item", - response_description="The created item", -) -async def create_item(item: Item): - """ - Create an item with all the information: - - - **name**: each item must have a name - - **description**: a long description - - **price**: required - - **tax**: if the item doesn't have tax, you can omit this - - **tags**: a set of unique tag strings for this item - """ - return item diff --git a/docs_src/path_operation_configuration/tutorial006.py b/docs_src/path_operation_configuration/tutorial006_py39.py similarity index 100% rename from docs_src/path_operation_configuration/tutorial006.py rename to docs_src/path_operation_configuration/tutorial006_py39.py diff --git a/docs_src/path_params/tutorial001.py b/docs_src/path_params/tutorial001_py39.py similarity index 100% rename from docs_src/path_params/tutorial001.py rename to docs_src/path_params/tutorial001_py39.py diff --git a/docs_src/path_params/tutorial002.py b/docs_src/path_params/tutorial002_py39.py similarity index 100% rename from docs_src/path_params/tutorial002.py rename to docs_src/path_params/tutorial002_py39.py diff --git a/docs_src/path_params/tutorial003.py b/docs_src/path_params/tutorial003_py39.py similarity index 100% rename from docs_src/path_params/tutorial003.py rename to docs_src/path_params/tutorial003_py39.py diff --git a/docs_src/path_params/tutorial003b.py b/docs_src/path_params/tutorial003b_py39.py similarity index 100% rename from docs_src/path_params/tutorial003b.py rename to docs_src/path_params/tutorial003b_py39.py diff --git a/docs_src/path_params/tutorial004.py b/docs_src/path_params/tutorial004_py39.py similarity index 100% rename from docs_src/path_params/tutorial004.py rename to docs_src/path_params/tutorial004_py39.py diff --git a/docs_src/path_params/tutorial005.py b/docs_src/path_params/tutorial005_py39.py similarity index 100% rename from docs_src/path_params/tutorial005.py rename to docs_src/path_params/tutorial005_py39.py diff --git a/docs_src/path_params_numeric_validations/tutorial001_an.py b/docs_src/path_params_numeric_validations/tutorial001_an.py deleted file mode 100644 index 621be7b045..0000000000 --- a/docs_src/path_params_numeric_validations/tutorial001_an.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Path, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get")], - q: Annotated[Union[str, None], Query(alias="item-query")] = None, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial001.py b/docs_src/path_params_numeric_validations/tutorial001_py39.py similarity index 100% rename from docs_src/path_params_numeric_validations/tutorial001.py rename to docs_src/path_params_numeric_validations/tutorial001_py39.py diff --git a/docs_src/path_params_numeric_validations/tutorial002_an.py b/docs_src/path_params_numeric_validations/tutorial002_an.py deleted file mode 100644 index 322f8cf0bb..0000000000 --- a/docs_src/path_params_numeric_validations/tutorial002_an.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, Path -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - q: str, item_id: Annotated[int, Path(title="The ID of the item to get")] -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial002.py b/docs_src/path_params_numeric_validations/tutorial002_py39.py similarity index 100% rename from docs_src/path_params_numeric_validations/tutorial002.py rename to docs_src/path_params_numeric_validations/tutorial002_py39.py diff --git a/docs_src/path_params_numeric_validations/tutorial003_an.py b/docs_src/path_params_numeric_validations/tutorial003_an.py deleted file mode 100644 index d0fa8b3db9..0000000000 --- a/docs_src/path_params_numeric_validations/tutorial003_an.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, Path -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get")], q: str -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial003.py b/docs_src/path_params_numeric_validations/tutorial003_py39.py similarity index 100% rename from docs_src/path_params_numeric_validations/tutorial003.py rename to docs_src/path_params_numeric_validations/tutorial003_py39.py diff --git a/docs_src/path_params_numeric_validations/tutorial004_an.py b/docs_src/path_params_numeric_validations/tutorial004_an.py deleted file mode 100644 index ffc50f6c5b..0000000000 --- a/docs_src/path_params_numeric_validations/tutorial004_an.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, Path -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)], q: str -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial004.py b/docs_src/path_params_numeric_validations/tutorial004_py39.py similarity index 100% rename from docs_src/path_params_numeric_validations/tutorial004.py rename to docs_src/path_params_numeric_validations/tutorial004_py39.py diff --git a/docs_src/path_params_numeric_validations/tutorial005_an.py b/docs_src/path_params_numeric_validations/tutorial005_an.py deleted file mode 100644 index 433c69129f..0000000000 --- a/docs_src/path_params_numeric_validations/tutorial005_an.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, Path -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get", gt=0, le=1000)], - q: str, -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial005.py b/docs_src/path_params_numeric_validations/tutorial005_py39.py similarity index 100% rename from docs_src/path_params_numeric_validations/tutorial005.py rename to docs_src/path_params_numeric_validations/tutorial005_py39.py diff --git a/docs_src/path_params_numeric_validations/tutorial006_an.py b/docs_src/path_params_numeric_validations/tutorial006_an.py deleted file mode 100644 index ac47325732..0000000000 --- a/docs_src/path_params_numeric_validations/tutorial006_an.py +++ /dev/null @@ -1,19 +0,0 @@ -from fastapi import FastAPI, Path, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_items( - *, - item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)], - q: str, - size: Annotated[float, Query(gt=0, lt=10.5)], -): - results = {"item_id": item_id} - if q: - results.update({"q": q}) - if size: - results.update({"size": size}) - return results diff --git a/docs_src/path_params_numeric_validations/tutorial006.py b/docs_src/path_params_numeric_validations/tutorial006_py39.py similarity index 100% rename from docs_src/path_params_numeric_validations/tutorial006.py rename to docs_src/path_params_numeric_validations/tutorial006_py39.py diff --git a/docs_src/pydantic_v1_in_v2/tutorial001_an.py b/docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py similarity index 100% rename from docs_src/pydantic_v1_in_v2/tutorial001_an.py rename to docs_src/pydantic_v1_in_v2/tutorial001_an_py39.py diff --git a/docs_src/pydantic_v1_in_v2/tutorial002_an.py b/docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py similarity index 100% rename from docs_src/pydantic_v1_in_v2/tutorial002_an.py rename to docs_src/pydantic_v1_in_v2/tutorial002_an_py39.py diff --git a/docs_src/pydantic_v1_in_v2/tutorial003_an.py b/docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py similarity index 100% rename from docs_src/pydantic_v1_in_v2/tutorial003_an.py rename to docs_src/pydantic_v1_in_v2/tutorial003_an_py39.py diff --git a/docs_src/pydantic_v1_in_v2/tutorial004_an.py b/docs_src/pydantic_v1_in_v2/tutorial004_an.py deleted file mode 100644 index cca8a9ea80..0000000000 --- a/docs_src/pydantic_v1_in_v2/tutorial004_an.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import FastAPI -from fastapi.temp_pydantic_v1_params import Body -from pydantic.v1 import BaseModel -from typing_extensions import Annotated - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - size: float - - -app = FastAPI() - - -@app.post("/items/") -async def create_item(item: Annotated[Item, Body(embed=True)]) -> Item: - return item diff --git a/docs_src/python_types/tutorial001.py b/docs_src/python_types/tutorial001_py39.py similarity index 100% rename from docs_src/python_types/tutorial001.py rename to docs_src/python_types/tutorial001_py39.py diff --git a/docs_src/python_types/tutorial002.py b/docs_src/python_types/tutorial002_py39.py similarity index 100% rename from docs_src/python_types/tutorial002.py rename to docs_src/python_types/tutorial002_py39.py diff --git a/docs_src/python_types/tutorial003.py b/docs_src/python_types/tutorial003_py39.py similarity index 100% rename from docs_src/python_types/tutorial003.py rename to docs_src/python_types/tutorial003_py39.py diff --git a/docs_src/python_types/tutorial004.py b/docs_src/python_types/tutorial004_py39.py similarity index 100% rename from docs_src/python_types/tutorial004.py rename to docs_src/python_types/tutorial004_py39.py diff --git a/docs_src/python_types/tutorial005.py b/docs_src/python_types/tutorial005_py39.py similarity index 100% rename from docs_src/python_types/tutorial005.py rename to docs_src/python_types/tutorial005_py39.py diff --git a/docs_src/python_types/tutorial006.py b/docs_src/python_types/tutorial006.py deleted file mode 100644 index 87394ecb0c..0000000000 --- a/docs_src/python_types/tutorial006.py +++ /dev/null @@ -1,6 +0,0 @@ -from typing import List - - -def process_items(items: List[str]): - for item in items: - print(item) diff --git a/docs_src/python_types/tutorial007.py b/docs_src/python_types/tutorial007.py deleted file mode 100644 index 5b13f15494..0000000000 --- a/docs_src/python_types/tutorial007.py +++ /dev/null @@ -1,5 +0,0 @@ -from typing import Set, Tuple - - -def process_items(items_t: Tuple[int, int, str], items_s: Set[bytes]): - return items_t, items_s diff --git a/docs_src/python_types/tutorial008.py b/docs_src/python_types/tutorial008.py deleted file mode 100644 index 9fb1043bb8..0000000000 --- a/docs_src/python_types/tutorial008.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import Dict - - -def process_items(prices: Dict[str, float]): - for item_name, item_price in prices.items(): - print(item_name) - print(item_price) diff --git a/docs_src/python_types/tutorial008b.py b/docs_src/python_types/tutorial008b_py39.py similarity index 100% rename from docs_src/python_types/tutorial008b.py rename to docs_src/python_types/tutorial008b_py39.py diff --git a/docs_src/python_types/tutorial009.py b/docs_src/python_types/tutorial009_py39.py similarity index 100% rename from docs_src/python_types/tutorial009.py rename to docs_src/python_types/tutorial009_py39.py diff --git a/docs_src/python_types/tutorial009b.py b/docs_src/python_types/tutorial009b_py39.py similarity index 100% rename from docs_src/python_types/tutorial009b.py rename to docs_src/python_types/tutorial009b_py39.py diff --git a/docs_src/python_types/tutorial009c.py b/docs_src/python_types/tutorial009c_py39.py similarity index 100% rename from docs_src/python_types/tutorial009c.py rename to docs_src/python_types/tutorial009c_py39.py diff --git a/docs_src/python_types/tutorial010.py b/docs_src/python_types/tutorial010_py39.py similarity index 100% rename from docs_src/python_types/tutorial010.py rename to docs_src/python_types/tutorial010_py39.py diff --git a/docs_src/python_types/tutorial011.py b/docs_src/python_types/tutorial011.py deleted file mode 100644 index 297a84db68..0000000000 --- a/docs_src/python_types/tutorial011.py +++ /dev/null @@ -1,23 +0,0 @@ -from datetime import datetime -from typing import List, Union - -from pydantic import BaseModel - - -class User(BaseModel): - id: int - name: str = "John Doe" - signup_ts: Union[datetime, None] = None - friends: List[int] = [] - - -external_data = { - "id": "123", - "signup_ts": "2017-06-01 12:22", - "friends": [1, "2", b"3"], -} -user = User(**external_data) -print(user) -# > User id=123 name='John Doe' signup_ts=datetime.datetime(2017, 6, 1, 12, 22) friends=[1, 2, 3] -print(user.id) -# > 123 diff --git a/docs_src/python_types/tutorial012.py b/docs_src/python_types/tutorial012_py39.py similarity index 100% rename from docs_src/python_types/tutorial012.py rename to docs_src/python_types/tutorial012_py39.py diff --git a/docs_src/python_types/tutorial013.py b/docs_src/python_types/tutorial013.py deleted file mode 100644 index 0ec7735199..0000000000 --- a/docs_src/python_types/tutorial013.py +++ /dev/null @@ -1,5 +0,0 @@ -from typing_extensions import Annotated - - -def say_hello(name: Annotated[str, "this is just metadata"]) -> str: - return f"Hello {name}" diff --git a/docs_src/query_param_models/tutorial001.py b/docs_src/query_param_models/tutorial001.py deleted file mode 100644 index 0c0ab315e8..0000000000 --- a/docs_src/query_param_models/tutorial001.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field -from typing_extensions import Literal - -app = FastAPI() - - -class FilterParams(BaseModel): - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: List[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/query_param_models/tutorial001_an.py b/docs_src/query_param_models/tutorial001_an.py deleted file mode 100644 index 28375057c1..0000000000 --- a/docs_src/query_param_models/tutorial001_an.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal - -app = FastAPI() - - -class FilterParams(BaseModel): - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: List[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial001_an_py39.py b/docs_src/query_param_models/tutorial001_an_py39.py index ba690d3e3f..71427acae1 100644 --- a/docs_src/query_param_models/tutorial001_an_py39.py +++ b/docs_src/query_param_models/tutorial001_an_py39.py @@ -1,6 +1,7 @@ +from typing import Annotated, Literal + from fastapi import FastAPI, Query from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal app = FastAPI() diff --git a/docs_src/query_param_models/tutorial001_py39.py b/docs_src/query_param_models/tutorial001_py39.py index 54b52a054c..3ebf9f4d70 100644 --- a/docs_src/query_param_models/tutorial001_py39.py +++ b/docs_src/query_param_models/tutorial001_py39.py @@ -1,6 +1,7 @@ +from typing import Literal + from fastapi import FastAPI, Query from pydantic import BaseModel, Field -from typing_extensions import Literal app = FastAPI() diff --git a/docs_src/query_param_models/tutorial002.py b/docs_src/query_param_models/tutorial002.py deleted file mode 100644 index 1633bc4644..0000000000 --- a/docs_src/query_param_models/tutorial002.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field -from typing_extensions import Literal - -app = FastAPI() - - -class FilterParams(BaseModel): - model_config = {"extra": "forbid"} - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: List[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_an.py b/docs_src/query_param_models/tutorial002_an.py deleted file mode 100644 index 69705d4b4b..0000000000 --- a/docs_src/query_param_models/tutorial002_an.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal - -app = FastAPI() - - -class FilterParams(BaseModel): - model_config = {"extra": "forbid"} - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: List[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_an_py39.py b/docs_src/query_param_models/tutorial002_an_py39.py index 2d4c1a62b5..9759565023 100644 --- a/docs_src/query_param_models/tutorial002_an_py39.py +++ b/docs_src/query_param_models/tutorial002_an_py39.py @@ -1,6 +1,7 @@ +from typing import Annotated, Literal + from fastapi import FastAPI, Query from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal app = FastAPI() diff --git a/docs_src/query_param_models/tutorial002_pv1.py b/docs_src/query_param_models/tutorial002_pv1.py deleted file mode 100644 index 71ccd961d3..0000000000 --- a/docs_src/query_param_models/tutorial002_pv1.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field -from typing_extensions import Literal - -app = FastAPI() - - -class FilterParams(BaseModel): - class Config: - extra = "forbid" - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: List[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an.py b/docs_src/query_param_models/tutorial002_pv1_an.py deleted file mode 100644 index 1dd29157a4..0000000000 --- a/docs_src/query_param_models/tutorial002_pv1_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal - -app = FastAPI() - - -class FilterParams(BaseModel): - class Config: - extra = "forbid" - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: List[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py39.py b/docs_src/query_param_models/tutorial002_pv1_an_py39.py index 494fef11fc..d635aae88f 100644 --- a/docs_src/query_param_models/tutorial002_pv1_an_py39.py +++ b/docs_src/query_param_models/tutorial002_pv1_an_py39.py @@ -1,6 +1,7 @@ +from typing import Annotated, Literal + from fastapi import FastAPI, Query from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal app = FastAPI() diff --git a/docs_src/query_param_models/tutorial002_pv1_py39.py b/docs_src/query_param_models/tutorial002_pv1_py39.py index 7fa456a791..9ffdeefc06 100644 --- a/docs_src/query_param_models/tutorial002_pv1_py39.py +++ b/docs_src/query_param_models/tutorial002_pv1_py39.py @@ -1,6 +1,7 @@ +from typing import Literal + from fastapi import FastAPI, Query from pydantic import BaseModel, Field -from typing_extensions import Literal app = FastAPI() diff --git a/docs_src/query_param_models/tutorial002_py39.py b/docs_src/query_param_models/tutorial002_py39.py index f9bba028c2..6ec4184991 100644 --- a/docs_src/query_param_models/tutorial002_py39.py +++ b/docs_src/query_param_models/tutorial002_py39.py @@ -1,6 +1,7 @@ +from typing import Literal + from fastapi import FastAPI, Query from pydantic import BaseModel, Field -from typing_extensions import Literal app = FastAPI() diff --git a/docs_src/query_params/tutorial001.py b/docs_src/query_params/tutorial001_py39.py similarity index 100% rename from docs_src/query_params/tutorial001.py rename to docs_src/query_params/tutorial001_py39.py diff --git a/docs_src/query_params/tutorial002.py b/docs_src/query_params/tutorial002_py39.py similarity index 100% rename from docs_src/query_params/tutorial002.py rename to docs_src/query_params/tutorial002_py39.py diff --git a/docs_src/query_params/tutorial003.py b/docs_src/query_params/tutorial003_py39.py similarity index 100% rename from docs_src/query_params/tutorial003.py rename to docs_src/query_params/tutorial003_py39.py diff --git a/docs_src/query_params/tutorial004.py b/docs_src/query_params/tutorial004_py39.py similarity index 100% rename from docs_src/query_params/tutorial004.py rename to docs_src/query_params/tutorial004_py39.py diff --git a/docs_src/query_params/tutorial005.py b/docs_src/query_params/tutorial005_py39.py similarity index 100% rename from docs_src/query_params/tutorial005.py rename to docs_src/query_params/tutorial005_py39.py diff --git a/docs_src/query_params/tutorial006.py b/docs_src/query_params/tutorial006_py39.py similarity index 100% rename from docs_src/query_params/tutorial006.py rename to docs_src/query_params/tutorial006_py39.py diff --git a/docs_src/query_params/tutorial006b.py b/docs_src/query_params/tutorial006b.py deleted file mode 100644 index f0dbfe08fe..0000000000 --- a/docs_src/query_params/tutorial006b.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Union - -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/items/{item_id}") -async def read_user_item( - item_id: str, needy: str, skip: int = 0, limit: Union[int, None] = None -): - item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit} - return item diff --git a/docs_src/query_params_str_validations/tutorial001.py b/docs_src/query_params_str_validations/tutorial001_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial001.py rename to docs_src/query_params_str_validations/tutorial001_py39.py diff --git a/docs_src/query_params_str_validations/tutorial002_an.py b/docs_src/query_params_str_validations/tutorial002_an_py39.py similarity index 81% rename from docs_src/query_params_str_validations/tutorial002_an.py rename to docs_src/query_params_str_validations/tutorial002_an_py39.py index cb1b38940c..2d8fc97985 100644 --- a/docs_src/query_params_str_validations/tutorial002_an.py +++ b/docs_src/query_params_str_validations/tutorial002_an_py39.py @@ -1,7 +1,6 @@ -from typing import Union +from typing import Annotated, Union from fastapi import FastAPI, Query -from typing_extensions import Annotated app = FastAPI() diff --git a/docs_src/query_params_str_validations/tutorial002.py b/docs_src/query_params_str_validations/tutorial002_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial002.py rename to docs_src/query_params_str_validations/tutorial002_py39.py diff --git a/docs_src/query_params_str_validations/tutorial003_an.py b/docs_src/query_params_str_validations/tutorial003_an.py deleted file mode 100644 index 0dd14086cf..0000000000 --- a/docs_src/query_params_str_validations/tutorial003_an.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[Union[str, None], Query(min_length=3, max_length=50)] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial003.py b/docs_src/query_params_str_validations/tutorial003_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial003.py rename to docs_src/query_params_str_validations/tutorial003_py39.py diff --git a/docs_src/query_params_str_validations/tutorial004_an.py b/docs_src/query_params_str_validations/tutorial004_an.py deleted file mode 100644 index c75d45d63e..0000000000 --- a/docs_src/query_params_str_validations/tutorial004_an.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - Union[str, None], Query(min_length=3, max_length=50, pattern="^fixedquery$") - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial004.py b/docs_src/query_params_str_validations/tutorial004_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial004.py rename to docs_src/query_params_str_validations/tutorial004_py39.py diff --git a/docs_src/query_params_str_validations/tutorial005_an.py b/docs_src/query_params_str_validations/tutorial005_an.py deleted file mode 100644 index 452d4d38d7..0000000000 --- a/docs_src/query_params_str_validations/tutorial005_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial005.py b/docs_src/query_params_str_validations/tutorial005_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial005.py rename to docs_src/query_params_str_validations/tutorial005_py39.py diff --git a/docs_src/query_params_str_validations/tutorial006_an.py b/docs_src/query_params_str_validations/tutorial006_an.py deleted file mode 100644 index 559480d2bf..0000000000 --- a/docs_src/query_params_str_validations/tutorial006_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[str, Query(min_length=3)]): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006.py b/docs_src/query_params_str_validations/tutorial006_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial006.py rename to docs_src/query_params_str_validations/tutorial006_py39.py diff --git a/docs_src/query_params_str_validations/tutorial006c_an.py b/docs_src/query_params_str_validations/tutorial006c_an.py deleted file mode 100644 index 55c4f4adca..0000000000 --- a/docs_src/query_params_str_validations/tutorial006c_an.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial006c.py b/docs_src/query_params_str_validations/tutorial006c_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial006c.py rename to docs_src/query_params_str_validations/tutorial006c_py39.py diff --git a/docs_src/query_params_str_validations/tutorial007_an.py b/docs_src/query_params_str_validations/tutorial007_an.py deleted file mode 100644 index 4b3c8de4b9..0000000000 --- a/docs_src/query_params_str_validations/tutorial007_an.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[Union[str, None], Query(title="Query string", min_length=3)] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial007.py b/docs_src/query_params_str_validations/tutorial007_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial007.py rename to docs_src/query_params_str_validations/tutorial007_py39.py diff --git a/docs_src/query_params_str_validations/tutorial008_an.py b/docs_src/query_params_str_validations/tutorial008_an.py deleted file mode 100644 index 01606a9203..0000000000 --- a/docs_src/query_params_str_validations/tutorial008_an.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - Union[str, None], - Query( - title="Query string", - description="Query string for the items to search in the database that have a good match", - min_length=3, - ), - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial008.py b/docs_src/query_params_str_validations/tutorial008_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial008.py rename to docs_src/query_params_str_validations/tutorial008_py39.py diff --git a/docs_src/query_params_str_validations/tutorial009_an.py b/docs_src/query_params_str_validations/tutorial009_an.py deleted file mode 100644 index 2894e2d51a..0000000000 --- a/docs_src/query_params_str_validations/tutorial009_an.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[Union[str, None], Query(alias="item-query")] = None): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial009.py b/docs_src/query_params_str_validations/tutorial009_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial009.py rename to docs_src/query_params_str_validations/tutorial009_py39.py diff --git a/docs_src/query_params_str_validations/tutorial010_an.py b/docs_src/query_params_str_validations/tutorial010_an.py deleted file mode 100644 index ed343230f4..0000000000 --- a/docs_src/query_params_str_validations/tutorial010_an.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - q: Annotated[ - Union[str, None], - Query( - alias="item-query", - title="Query string", - description="Query string for the items to search in the database that have a good match", - min_length=3, - max_length=50, - pattern="^fixedquery$", - deprecated=True, - ), - ] = None, -): - results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} - if q: - results.update({"q": q}) - return results diff --git a/docs_src/query_params_str_validations/tutorial010.py b/docs_src/query_params_str_validations/tutorial010_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial010.py rename to docs_src/query_params_str_validations/tutorial010_py39.py diff --git a/docs_src/query_params_str_validations/tutorial011.py b/docs_src/query_params_str_validations/tutorial011.py deleted file mode 100644 index 65bbce781a..0000000000 --- a/docs_src/query_params_str_validations/tutorial011.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Union[List[str], None] = Query(default=None)): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial011_an.py b/docs_src/query_params_str_validations/tutorial011_an.py deleted file mode 100644 index 8ed699337d..0000000000 --- a/docs_src/query_params_str_validations/tutorial011_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[Union[List[str], None], Query()] = None): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial012.py b/docs_src/query_params_str_validations/tutorial012.py deleted file mode 100644 index e77d56974d..0000000000 --- a/docs_src/query_params_str_validations/tutorial012.py +++ /dev/null @@ -1,11 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: List[str] = Query(default=["foo", "bar"])): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial012_an.py b/docs_src/query_params_str_validations/tutorial012_an.py deleted file mode 100644 index 261af250a1..0000000000 --- a/docs_src/query_params_str_validations/tutorial012_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import List - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[List[str], Query()] = ["foo", "bar"]): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial013_an.py b/docs_src/query_params_str_validations/tutorial013_an.py deleted file mode 100644 index f12a250559..0000000000 --- a/docs_src/query_params_str_validations/tutorial013_an.py +++ /dev/null @@ -1,10 +0,0 @@ -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items(q: Annotated[list, Query()] = []): - query_items = {"q": q} - return query_items diff --git a/docs_src/query_params_str_validations/tutorial013.py b/docs_src/query_params_str_validations/tutorial013_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial013.py rename to docs_src/query_params_str_validations/tutorial013_py39.py diff --git a/docs_src/query_params_str_validations/tutorial014_an.py b/docs_src/query_params_str_validations/tutorial014_an.py deleted file mode 100644 index 2eaa585407..0000000000 --- a/docs_src/query_params_str_validations/tutorial014_an.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Query -from typing_extensions import Annotated - -app = FastAPI() - - -@app.get("/items/") -async def read_items( - hidden_query: Annotated[Union[str, None], Query(include_in_schema=False)] = None, -): - if hidden_query: - return {"hidden_query": hidden_query} - else: - return {"hidden_query": "Not found"} diff --git a/docs_src/query_params_str_validations/tutorial014.py b/docs_src/query_params_str_validations/tutorial014_py39.py similarity index 100% rename from docs_src/query_params_str_validations/tutorial014.py rename to docs_src/query_params_str_validations/tutorial014_py39.py diff --git a/docs_src/query_params_str_validations/tutorial015_an.py b/docs_src/query_params_str_validations/tutorial015_an.py deleted file mode 100644 index f2ec6db123..0000000000 --- a/docs_src/query_params_str_validations/tutorial015_an.py +++ /dev/null @@ -1,31 +0,0 @@ -import random -from typing import Union - -from fastapi import FastAPI -from pydantic import AfterValidator -from typing_extensions import Annotated - -app = FastAPI() - -data = { - "isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy", - "imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy", - "isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2", -} - - -def check_valid_id(id: str): - if not id.startswith(("isbn-", "imdb-")): - raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"') - return id - - -@app.get("/items/") -async def read_items( - id: Annotated[Union[str, None], AfterValidator(check_valid_id)] = None, -): - if id: - item = data.get(id) - else: - id, item = random.choice(list(data.items())) - return {"id": id, "name": item} diff --git a/docs_src/request_files/tutorial001_02_an.py b/docs_src/request_files/tutorial001_02_an.py deleted file mode 100644 index 5007fef159..0000000000 --- a/docs_src/request_files/tutorial001_02_an.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, File, UploadFile -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Annotated[Union[bytes, None], File()] = None): - if not file: - return {"message": "No file sent"} - else: - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file(file: Union[UploadFile, None] = None): - if not file: - return {"message": "No upload file sent"} - else: - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_02.py b/docs_src/request_files/tutorial001_02_py39.py similarity index 100% rename from docs_src/request_files/tutorial001_02.py rename to docs_src/request_files/tutorial001_02_py39.py diff --git a/docs_src/request_files/tutorial001_03_an.py b/docs_src/request_files/tutorial001_03_an.py deleted file mode 100644 index 8a6b0a2455..0000000000 --- a/docs_src/request_files/tutorial001_03_an.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import FastAPI, File, UploadFile -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]): - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file( - file: Annotated[UploadFile, File(description="A file read as UploadFile")], -): - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001_03.py b/docs_src/request_files/tutorial001_03_py39.py similarity index 100% rename from docs_src/request_files/tutorial001_03.py rename to docs_src/request_files/tutorial001_03_py39.py diff --git a/docs_src/request_files/tutorial001_an.py b/docs_src/request_files/tutorial001_an.py deleted file mode 100644 index ca2f76d5c6..0000000000 --- a/docs_src/request_files/tutorial001_an.py +++ /dev/null @@ -1,14 +0,0 @@ -from fastapi import FastAPI, File, UploadFile -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/files/") -async def create_file(file: Annotated[bytes, File()]): - return {"file_size": len(file)} - - -@app.post("/uploadfile/") -async def create_upload_file(file: UploadFile): - return {"filename": file.filename} diff --git a/docs_src/request_files/tutorial001.py b/docs_src/request_files/tutorial001_py39.py similarity index 100% rename from docs_src/request_files/tutorial001.py rename to docs_src/request_files/tutorial001_py39.py diff --git a/docs_src/request_files/tutorial002.py b/docs_src/request_files/tutorial002.py deleted file mode 100644 index b4d0acc68f..0000000000 --- a/docs_src/request_files/tutorial002.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import List - -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.post("/files/") -async def create_files(files: List[bytes] = File()): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files(files: List[UploadFile]): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial002_an.py b/docs_src/request_files/tutorial002_an.py deleted file mode 100644 index eaa90da2b6..0000000000 --- a/docs_src/request_files/tutorial002_an.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import List - -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/files/") -async def create_files(files: Annotated[List[bytes], File()]): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files(files: List[UploadFile]): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial003.py b/docs_src/request_files/tutorial003.py deleted file mode 100644 index e3f805f605..0000000000 --- a/docs_src/request_files/tutorial003.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List - -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse - -app = FastAPI() - - -@app.post("/files/") -async def create_files( - files: List[bytes] = File(description="Multiple files as bytes"), -): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files( - files: List[UploadFile] = File(description="Multiple files as UploadFile"), -): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_files/tutorial003_an.py b/docs_src/request_files/tutorial003_an.py deleted file mode 100644 index 2238e3c94b..0000000000 --- a/docs_src/request_files/tutorial003_an.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import List - -from fastapi import FastAPI, File, UploadFile -from fastapi.responses import HTMLResponse -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/files/") -async def create_files( - files: Annotated[List[bytes], File(description="Multiple files as bytes")], -): - return {"file_sizes": [len(file) for file in files]} - - -@app.post("/uploadfiles/") -async def create_upload_files( - files: Annotated[ - List[UploadFile], File(description="Multiple files as UploadFile") - ], -): - return {"filenames": [file.filename for file in files]} - - -@app.get("/") -async def main(): - content = """ - -
- - -
-
- - -
- - """ - return HTMLResponse(content=content) diff --git a/docs_src/request_form_models/tutorial001_an.py b/docs_src/request_form_models/tutorial001_an.py deleted file mode 100644 index 30483d4455..0000000000 --- a/docs_src/request_form_models/tutorial001_an.py +++ /dev/null @@ -1,15 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial001.py b/docs_src/request_form_models/tutorial001_py39.py similarity index 100% rename from docs_src/request_form_models/tutorial001.py rename to docs_src/request_form_models/tutorial001_py39.py diff --git a/docs_src/request_form_models/tutorial002_an.py b/docs_src/request_form_models/tutorial002_an.py deleted file mode 100644 index bcb0227959..0000000000 --- a/docs_src/request_form_models/tutorial002_an.py +++ /dev/null @@ -1,16 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - model_config = {"extra": "forbid"} - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial002_pv1_an.py b/docs_src/request_form_models/tutorial002_pv1_an.py deleted file mode 100644 index fe9dbc344b..0000000000 --- a/docs_src/request_form_models/tutorial002_pv1_an.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class FormData(BaseModel): - username: str - password: str - - class Config: - extra = "forbid" - - -@app.post("/login/") -async def login(data: Annotated[FormData, Form()]): - return data diff --git a/docs_src/request_form_models/tutorial002_pv1.py b/docs_src/request_form_models/tutorial002_pv1_py39.py similarity index 100% rename from docs_src/request_form_models/tutorial002_pv1.py rename to docs_src/request_form_models/tutorial002_pv1_py39.py diff --git a/docs_src/request_form_models/tutorial002.py b/docs_src/request_form_models/tutorial002_py39.py similarity index 100% rename from docs_src/request_form_models/tutorial002.py rename to docs_src/request_form_models/tutorial002_py39.py diff --git a/docs_src/request_forms/tutorial001_an.py b/docs_src/request_forms/tutorial001_an.py deleted file mode 100644 index 677fbf2db8..0000000000 --- a/docs_src/request_forms/tutorial001_an.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import FastAPI, Form -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/login/") -async def login(username: Annotated[str, Form()], password: Annotated[str, Form()]): - return {"username": username} diff --git a/docs_src/request_forms/tutorial001.py b/docs_src/request_forms/tutorial001_py39.py similarity index 100% rename from docs_src/request_forms/tutorial001.py rename to docs_src/request_forms/tutorial001_py39.py diff --git a/docs_src/request_forms_and_files/tutorial001_an.py b/docs_src/request_forms_and_files/tutorial001_an.py deleted file mode 100644 index 0ea285ac87..0000000000 --- a/docs_src/request_forms_and_files/tutorial001_an.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi import FastAPI, File, Form, UploadFile -from typing_extensions import Annotated - -app = FastAPI() - - -@app.post("/files/") -async def create_file( - file: Annotated[bytes, File()], - fileb: Annotated[UploadFile, File()], - token: Annotated[str, Form()], -): - return { - "file_size": len(file), - "token": token, - "fileb_content_type": fileb.content_type, - } diff --git a/docs_src/request_forms_and_files/tutorial001.py b/docs_src/request_forms_and_files/tutorial001_py39.py similarity index 100% rename from docs_src/request_forms_and_files/tutorial001.py rename to docs_src/request_forms_and_files/tutorial001_py39.py diff --git a/docs_src/response_change_status_code/tutorial001.py b/docs_src/response_change_status_code/tutorial001_py39.py similarity index 100% rename from docs_src/response_change_status_code/tutorial001.py rename to docs_src/response_change_status_code/tutorial001_py39.py diff --git a/docs_src/response_cookies/tutorial001.py b/docs_src/response_cookies/tutorial001_py39.py similarity index 100% rename from docs_src/response_cookies/tutorial001.py rename to docs_src/response_cookies/tutorial001_py39.py diff --git a/docs_src/response_cookies/tutorial002.py b/docs_src/response_cookies/tutorial002_py39.py similarity index 100% rename from docs_src/response_cookies/tutorial002.py rename to docs_src/response_cookies/tutorial002_py39.py diff --git a/docs_src/response_directly/tutorial001.py b/docs_src/response_directly/tutorial001_py39.py similarity index 100% rename from docs_src/response_directly/tutorial001.py rename to docs_src/response_directly/tutorial001_py39.py diff --git a/docs_src/response_directly/tutorial002.py b/docs_src/response_directly/tutorial002_py39.py similarity index 100% rename from docs_src/response_directly/tutorial002.py rename to docs_src/response_directly/tutorial002_py39.py diff --git a/docs_src/response_headers/tutorial001.py b/docs_src/response_headers/tutorial001_py39.py similarity index 100% rename from docs_src/response_headers/tutorial001.py rename to docs_src/response_headers/tutorial001_py39.py diff --git a/docs_src/response_headers/tutorial002.py b/docs_src/response_headers/tutorial002_py39.py similarity index 100% rename from docs_src/response_headers/tutorial002.py rename to docs_src/response_headers/tutorial002_py39.py diff --git a/docs_src/response_model/tutorial001.py b/docs_src/response_model/tutorial001.py deleted file mode 100644 index fd1c902a52..0000000000 --- a/docs_src/response_model/tutorial001.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Any, List, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: List[str] = [] - - -@app.post("/items/", response_model=Item) -async def create_item(item: Item) -> Any: - return item - - -@app.get("/items/", response_model=List[Item]) -async def read_items() -> Any: - return [ - {"name": "Portal Gun", "price": 42.0}, - {"name": "Plumbus", "price": 32.0}, - ] diff --git a/docs_src/response_model/tutorial001_01.py b/docs_src/response_model/tutorial001_01.py deleted file mode 100644 index 98d30d540f..0000000000 --- a/docs_src/response_model/tutorial001_01.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - tags: List[str] = [] - - -@app.post("/items/") -async def create_item(item: Item) -> Item: - return item - - -@app.get("/items/") -async def read_items() -> List[Item]: - return [ - Item(name="Portal Gun", price=42.0), - Item(name="Plumbus", price=32.0), - ] diff --git a/docs_src/response_model/tutorial002.py b/docs_src/response_model/tutorial002_py39.py similarity index 100% rename from docs_src/response_model/tutorial002.py rename to docs_src/response_model/tutorial002_py39.py diff --git a/docs_src/response_model/tutorial003_01.py b/docs_src/response_model/tutorial003_01_py39.py similarity index 100% rename from docs_src/response_model/tutorial003_01.py rename to docs_src/response_model/tutorial003_01_py39.py diff --git a/docs_src/response_model/tutorial003_02.py b/docs_src/response_model/tutorial003_02_py39.py similarity index 100% rename from docs_src/response_model/tutorial003_02.py rename to docs_src/response_model/tutorial003_02_py39.py diff --git a/docs_src/response_model/tutorial003_03.py b/docs_src/response_model/tutorial003_03_py39.py similarity index 100% rename from docs_src/response_model/tutorial003_03.py rename to docs_src/response_model/tutorial003_03_py39.py diff --git a/docs_src/response_model/tutorial003_04.py b/docs_src/response_model/tutorial003_04_py39.py similarity index 100% rename from docs_src/response_model/tutorial003_04.py rename to docs_src/response_model/tutorial003_04_py39.py diff --git a/docs_src/response_model/tutorial003_05.py b/docs_src/response_model/tutorial003_05_py39.py similarity index 100% rename from docs_src/response_model/tutorial003_05.py rename to docs_src/response_model/tutorial003_05_py39.py diff --git a/docs_src/response_model/tutorial003.py b/docs_src/response_model/tutorial003_py39.py similarity index 100% rename from docs_src/response_model/tutorial003.py rename to docs_src/response_model/tutorial003_py39.py diff --git a/docs_src/response_model/tutorial004.py b/docs_src/response_model/tutorial004.py deleted file mode 100644 index 10b48039ae..0000000000 --- a/docs_src/response_model/tutorial004.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: float = 10.5 - tags: List[str] = [] - - -items = { - "foo": {"name": "Foo", "price": 50.2}, - "bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2}, - "baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []}, -} - - -@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True) -async def read_item(item_id: str): - return items[item_id] diff --git a/docs_src/response_model/tutorial005.py b/docs_src/response_model/tutorial005_py39.py similarity index 100% rename from docs_src/response_model/tutorial005.py rename to docs_src/response_model/tutorial005_py39.py diff --git a/docs_src/response_model/tutorial006.py b/docs_src/response_model/tutorial006_py39.py similarity index 100% rename from docs_src/response_model/tutorial006.py rename to docs_src/response_model/tutorial006_py39.py diff --git a/docs_src/response_status_code/tutorial001.py b/docs_src/response_status_code/tutorial001_py39.py similarity index 100% rename from docs_src/response_status_code/tutorial001.py rename to docs_src/response_status_code/tutorial001_py39.py diff --git a/docs_src/response_status_code/tutorial002.py b/docs_src/response_status_code/tutorial002_py39.py similarity index 100% rename from docs_src/response_status_code/tutorial002.py rename to docs_src/response_status_code/tutorial002_py39.py diff --git a/docs_src/schema_extra_example/tutorial001_pv1.py b/docs_src/schema_extra_example/tutorial001_pv1_py39.py similarity index 100% rename from docs_src/schema_extra_example/tutorial001_pv1.py rename to docs_src/schema_extra_example/tutorial001_pv1_py39.py diff --git a/docs_src/schema_extra_example/tutorial001.py b/docs_src/schema_extra_example/tutorial001_py39.py similarity index 100% rename from docs_src/schema_extra_example/tutorial001.py rename to docs_src/schema_extra_example/tutorial001_py39.py diff --git a/docs_src/schema_extra_example/tutorial002.py b/docs_src/schema_extra_example/tutorial002_py39.py similarity index 100% rename from docs_src/schema_extra_example/tutorial002.py rename to docs_src/schema_extra_example/tutorial002_py39.py diff --git a/docs_src/schema_extra_example/tutorial003_an.py b/docs_src/schema_extra_example/tutorial003_an.py deleted file mode 100644 index 23675aba14..0000000000 --- a/docs_src/schema_extra_example/tutorial003_an.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - item_id: int, - item: Annotated[ - Item, - Body( - examples=[ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ], - ), - ], -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial003.py b/docs_src/schema_extra_example/tutorial003_py39.py similarity index 100% rename from docs_src/schema_extra_example/tutorial003.py rename to docs_src/schema_extra_example/tutorial003_py39.py diff --git a/docs_src/schema_extra_example/tutorial004_an.py b/docs_src/schema_extra_example/tutorial004_an.py deleted file mode 100644 index e817302a22..0000000000 --- a/docs_src/schema_extra_example/tutorial004_an.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Annotated[ - Item, - Body( - examples=[ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - { - "name": "Bar", - "price": "35.4", - }, - { - "name": "Baz", - "price": "thirty five point four", - }, - ], - ), - ], -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial004.py b/docs_src/schema_extra_example/tutorial004_py39.py similarity index 100% rename from docs_src/schema_extra_example/tutorial004.py rename to docs_src/schema_extra_example/tutorial004_py39.py diff --git a/docs_src/schema_extra_example/tutorial005_an.py b/docs_src/schema_extra_example/tutorial005_an.py deleted file mode 100644 index 4b2d9c662b..0000000000 --- a/docs_src/schema_extra_example/tutorial005_an.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Union - -from fastapi import Body, FastAPI -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - price: float - tax: Union[float, None] = None - - -@app.put("/items/{item_id}") -async def update_item( - *, - item_id: int, - item: Annotated[ - Item, - Body( - openapi_examples={ - "normal": { - "summary": "A normal example", - "description": "A **normal** item works correctly.", - "value": { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - }, - }, - "converted": { - "summary": "An example with converted data", - "description": "FastAPI can convert price `strings` to actual `numbers` automatically", - "value": { - "name": "Bar", - "price": "35.4", - }, - }, - "invalid": { - "summary": "Invalid data is rejected with an error", - "value": { - "name": "Baz", - "price": "thirty five point four", - }, - }, - }, - ), - ], -): - results = {"item_id": item_id, "item": item} - return results diff --git a/docs_src/schema_extra_example/tutorial005.py b/docs_src/schema_extra_example/tutorial005_py39.py similarity index 100% rename from docs_src/schema_extra_example/tutorial005.py rename to docs_src/schema_extra_example/tutorial005_py39.py diff --git a/docs_src/security/tutorial001_an.py b/docs_src/security/tutorial001_an.py deleted file mode 100644 index dac915b7ca..0000000000 --- a/docs_src/security/tutorial001_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import Depends, FastAPI -from fastapi.security import OAuth2PasswordBearer -from typing_extensions import Annotated - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -@app.get("/items/") -async def read_items(token: Annotated[str, Depends(oauth2_scheme)]): - return {"token": token} diff --git a/docs_src/security/tutorial001.py b/docs_src/security/tutorial001_py39.py similarity index 100% rename from docs_src/security/tutorial001.py rename to docs_src/security/tutorial001_py39.py diff --git a/docs_src/security/tutorial002_an.py b/docs_src/security/tutorial002_an.py deleted file mode 100644 index 291b3bf530..0000000000 --- a/docs_src/security/tutorial002_an.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI -from fastapi.security import OAuth2PasswordBearer -from pydantic import BaseModel -from typing_extensions import Annotated - -app = FastAPI() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -def fake_decode_token(token): - return User( - username=token + "fakedecoded", email="john@example.com", full_name="John Doe" - ) - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - user = fake_decode_token(token) - return user - - -@app.get("/users/me") -async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]): - return current_user diff --git a/docs_src/security/tutorial002.py b/docs_src/security/tutorial002_py39.py similarity index 100% rename from docs_src/security/tutorial002.py rename to docs_src/security/tutorial002_py39.py diff --git a/docs_src/security/tutorial003_an.py b/docs_src/security/tutorial003_an.py deleted file mode 100644 index 1b7056a209..0000000000 --- a/docs_src/security/tutorial003_an.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import Union - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from pydantic import BaseModel -from typing_extensions import Annotated - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "fakehashedsecret", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Wonderson", - "email": "alice@example.com", - "hashed_password": "fakehashedsecret2", - "disabled": True, - }, -} - -app = FastAPI() - - -def fake_hash_password(password: str): - return "fakehashed" + password - - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def fake_decode_token(token): - # This doesn't provide any security at all - # Check the next version - user = get_user(fake_users_db, token) - return user - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - user = fake_decode_token(token) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers={"WWW-Authenticate": "Bearer"}, - ) - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)], -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): - user_dict = fake_users_db.get(form_data.username) - if not user_dict: - raise HTTPException(status_code=400, detail="Incorrect username or password") - user = UserInDB(**user_dict) - hashed_password = fake_hash_password(form_data.password) - if not hashed_password == user.hashed_password: - raise HTTPException(status_code=400, detail="Incorrect username or password") - - return {"access_token": user.username, "token_type": "bearer"} - - -@app.get("/users/me") -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)], -): - return current_user diff --git a/docs_src/security/tutorial003.py b/docs_src/security/tutorial003_py39.py similarity index 100% rename from docs_src/security/tutorial003.py rename to docs_src/security/tutorial003_py39.py diff --git a/docs_src/security/tutorial004_an.py b/docs_src/security/tutorial004_an.py deleted file mode 100644 index 018234e300..0000000000 --- a/docs_src/security/tutorial004_an.py +++ /dev/null @@ -1,148 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel -from typing_extensions import Annotated - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - } -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username = payload.get("sub") - if username is None: - raise credentials_exception - token_data = TokenData(username=username) - except InvalidTokenError: - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - return user - - -async def get_current_active_user( - current_user: Annotated[User, Depends(get_current_user)], -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: Annotated[OAuth2PasswordRequestForm, Depends()], -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Bearer"}, - ) - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, expires_delta=access_token_expires - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/", response_model=User) -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)], -): - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: Annotated[User, Depends(get_current_active_user)], -): - return [{"item_id": "Foo", "owner": current_user.username}] diff --git a/docs_src/security/tutorial004.py b/docs_src/security/tutorial004_py39.py similarity index 100% rename from docs_src/security/tutorial004.py rename to docs_src/security/tutorial004_py39.py diff --git a/docs_src/security/tutorial005.py b/docs_src/security/tutorial005.py deleted file mode 100644 index fdd73bcd8a..0000000000 --- a/docs_src/security/tutorial005.py +++ /dev/null @@ -1,177 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import List, Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, Security, status -from fastapi.security import ( - OAuth2PasswordBearer, - OAuth2PasswordRequestForm, - SecurityScopes, -) -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel, ValidationError - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Chains", - "email": "alicechains@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", - "disabled": True, - }, -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - scopes: List[str] = [] - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer( - tokenUrl="token", - scopes={"me": "Read information about the current user.", "items": "Read items."}, -) - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user( - security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme) -): - if security_scopes.scopes: - authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' - else: - authenticate_value = "Bearer" - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": authenticate_value}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username: str = payload.get("sub") - if username is None: - raise credentials_exception - scope: str = payload.get("scope", "") - token_scopes = scope.split(" ") - token_data = TokenData(scopes=token_scopes, username=username) - except (InvalidTokenError, ValidationError): - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - for scope in security_scopes.scopes: - if scope not in token_data.scopes: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not enough permissions", - headers={"WWW-Authenticate": authenticate_value}, - ) - return user - - -async def get_current_active_user( - current_user: User = Security(get_current_user, scopes=["me"]), -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: OAuth2PasswordRequestForm = Depends(), -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException(status_code=400, detail="Incorrect username or password") - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username, "scope": " ".join(form_data.scopes)}, - expires_delta=access_token_expires, - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/", response_model=User) -async def read_users_me(current_user: User = Depends(get_current_active_user)): - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: User = Security(get_current_active_user, scopes=["items"]), -): - return [{"item_id": "Foo", "owner": current_user.username}] - - -@app.get("/status/") -async def read_system_status(current_user: User = Depends(get_current_user)): - return {"status": "ok"} diff --git a/docs_src/security/tutorial005_an.py b/docs_src/security/tutorial005_an.py deleted file mode 100644 index e1d7b4f62a..0000000000 --- a/docs_src/security/tutorial005_an.py +++ /dev/null @@ -1,180 +0,0 @@ -from datetime import datetime, timedelta, timezone -from typing import List, Union - -import jwt -from fastapi import Depends, FastAPI, HTTPException, Security, status -from fastapi.security import ( - OAuth2PasswordBearer, - OAuth2PasswordRequestForm, - SecurityScopes, -) -from jwt.exceptions import InvalidTokenError -from pwdlib import PasswordHash -from pydantic import BaseModel, ValidationError -from typing_extensions import Annotated - -# to get a string like this run: -# openssl rand -hex 32 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" -ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 - - -fake_users_db = { - "johndoe": { - "username": "johndoe", - "full_name": "John Doe", - "email": "johndoe@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$wagCPXjifgvUFBzq4hqe3w$CYaIb8sB+wtD+Vu/P4uod1+Qof8h+1g7bbDlBID48Rc", - "disabled": False, - }, - "alice": { - "username": "alice", - "full_name": "Alice Chains", - "email": "alicechains@example.com", - "hashed_password": "$argon2id$v=19$m=65536,t=3,p=4$g2/AV1zwopqUntPKJavBFw$BwpRGDCyUHLvHICnwijyX8ROGoiUPwNKZ7915MeYfCE", - "disabled": True, - }, -} - - -class Token(BaseModel): - access_token: str - token_type: str - - -class TokenData(BaseModel): - username: Union[str, None] = None - scopes: List[str] = [] - - -class User(BaseModel): - username: str - email: Union[str, None] = None - full_name: Union[str, None] = None - disabled: Union[bool, None] = None - - -class UserInDB(User): - hashed_password: str - - -password_hash = PasswordHash.recommended() - -oauth2_scheme = OAuth2PasswordBearer( - tokenUrl="token", - scopes={"me": "Read information about the current user.", "items": "Read items."}, -) - -app = FastAPI() - - -def verify_password(plain_password, hashed_password): - return password_hash.verify(plain_password, hashed_password) - - -def get_password_hash(password): - return password_hash.hash(password) - - -def get_user(db, username: str): - if username in db: - user_dict = db[username] - return UserInDB(**user_dict) - - -def authenticate_user(fake_db, username: str, password: str): - user = get_user(fake_db, username) - if not user: - return False - if not verify_password(password, user.hashed_password): - return False - return user - - -def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): - to_encode = data.copy() - if expires_delta: - expire = datetime.now(timezone.utc) + expires_delta - else: - expire = datetime.now(timezone.utc) + timedelta(minutes=15) - to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt - - -async def get_current_user( - security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)] -): - if security_scopes.scopes: - authenticate_value = f'Bearer scope="{security_scopes.scope_str}"' - else: - authenticate_value = "Bearer" - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": authenticate_value}, - ) - try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) - username = payload.get("sub") - if username is None: - raise credentials_exception - scope: str = payload.get("scope", "") - token_scopes = scope.split(" ") - token_data = TokenData(scopes=token_scopes, username=username) - except (InvalidTokenError, ValidationError): - raise credentials_exception - user = get_user(fake_users_db, username=token_data.username) - if user is None: - raise credentials_exception - for scope in security_scopes.scopes: - if scope not in token_data.scopes: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Not enough permissions", - headers={"WWW-Authenticate": authenticate_value}, - ) - return user - - -async def get_current_active_user( - current_user: Annotated[User, Security(get_current_user, scopes=["me"])], -): - if current_user.disabled: - raise HTTPException(status_code=400, detail="Inactive user") - return current_user - - -@app.post("/token") -async def login_for_access_token( - form_data: Annotated[OAuth2PasswordRequestForm, Depends()], -) -> Token: - user = authenticate_user(fake_users_db, form_data.username, form_data.password) - if not user: - raise HTTPException(status_code=400, detail="Incorrect username or password") - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username, "scope": " ".join(form_data.scopes)}, - expires_delta=access_token_expires, - ) - return Token(access_token=access_token, token_type="bearer") - - -@app.get("/users/me/", response_model=User) -async def read_users_me( - current_user: Annotated[User, Depends(get_current_active_user)], -): - return current_user - - -@app.get("/users/me/items/") -async def read_own_items( - current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])], -): - return [{"item_id": "Foo", "owner": current_user.username}] - - -@app.get("/status/") -async def read_system_status(current_user: Annotated[User, Depends(get_current_user)]): - return {"status": "ok"} diff --git a/docs_src/security/tutorial006_an.py b/docs_src/security/tutorial006_an.py deleted file mode 100644 index 985e4b2ad2..0000000000 --- a/docs_src/security/tutorial006_an.py +++ /dev/null @@ -1,12 +0,0 @@ -from fastapi import Depends, FastAPI -from fastapi.security import HTTPBasic, HTTPBasicCredentials -from typing_extensions import Annotated - -app = FastAPI() - -security = HTTPBasic() - - -@app.get("/users/me") -def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): - return {"username": credentials.username, "password": credentials.password} diff --git a/docs_src/security/tutorial006.py b/docs_src/security/tutorial006_py39.py similarity index 100% rename from docs_src/security/tutorial006.py rename to docs_src/security/tutorial006_py39.py diff --git a/docs_src/security/tutorial007_an.py b/docs_src/security/tutorial007_an.py deleted file mode 100644 index 0d211dfde5..0000000000 --- a/docs_src/security/tutorial007_an.py +++ /dev/null @@ -1,36 +0,0 @@ -import secrets - -from fastapi import Depends, FastAPI, HTTPException, status -from fastapi.security import HTTPBasic, HTTPBasicCredentials -from typing_extensions import Annotated - -app = FastAPI() - -security = HTTPBasic() - - -def get_current_username( - credentials: Annotated[HTTPBasicCredentials, Depends(security)], -): - current_username_bytes = credentials.username.encode("utf8") - correct_username_bytes = b"stanleyjobson" - is_correct_username = secrets.compare_digest( - current_username_bytes, correct_username_bytes - ) - current_password_bytes = credentials.password.encode("utf8") - correct_password_bytes = b"swordfish" - is_correct_password = secrets.compare_digest( - current_password_bytes, correct_password_bytes - ) - if not (is_correct_username and is_correct_password): - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Incorrect username or password", - headers={"WWW-Authenticate": "Basic"}, - ) - return credentials.username - - -@app.get("/users/me") -def read_current_user(username: Annotated[str, Depends(get_current_username)]): - return {"username": username} diff --git a/docs_src/security/tutorial007.py b/docs_src/security/tutorial007_py39.py similarity index 100% rename from docs_src/security/tutorial007.py rename to docs_src/security/tutorial007_py39.py diff --git a/docs_src/separate_openapi_schemas/tutorial001.py b/docs_src/separate_openapi_schemas/tutorial001.py deleted file mode 100644 index 415eef8e28..0000000000 --- a/docs_src/separate_openapi_schemas/tutorial001.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - - -app = FastAPI() - - -@app.post("/items/") -def create_item(item: Item): - return item - - -@app.get("/items/") -def read_items() -> List[Item]: - return [ - Item( - name="Portal Gun", - description="Device to travel through the multi-rick-verse", - ), - Item(name="Plumbus"), - ] diff --git a/docs_src/separate_openapi_schemas/tutorial002.py b/docs_src/separate_openapi_schemas/tutorial002.py deleted file mode 100644 index 7df93783b9..0000000000 --- a/docs_src/separate_openapi_schemas/tutorial002.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import List, Union - -from fastapi import FastAPI -from pydantic import BaseModel - - -class Item(BaseModel): - name: str - description: Union[str, None] = None - - -app = FastAPI(separate_input_output_schemas=False) - - -@app.post("/items/") -def create_item(item: Item): - return item - - -@app.get("/items/") -def read_items() -> List[Item]: - return [ - Item( - name="Portal Gun", - description="Device to travel through the multi-rick-verse", - ), - Item(name="Plumbus"), - ] diff --git a/docs_src/bigger_applications/app_an/internal/__init__.py b/docs_src/settings/app01_py39/__init__.py similarity index 100% rename from docs_src/bigger_applications/app_an/internal/__init__.py rename to docs_src/settings/app01_py39/__init__.py diff --git a/docs_src/settings/app01/config.py b/docs_src/settings/app01_py39/config.py similarity index 100% rename from docs_src/settings/app01/config.py rename to docs_src/settings/app01_py39/config.py diff --git a/docs_src/settings/app01/main.py b/docs_src/settings/app01_py39/main.py similarity index 100% rename from docs_src/settings/app01/main.py rename to docs_src/settings/app01_py39/main.py diff --git a/docs_src/settings/app02/__init__.py b/docs_src/settings/app02/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs_src/settings/app02_an/__init__.py b/docs_src/settings/app02_an/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs_src/settings/app02_an/config.py b/docs_src/settings/app02_an/config.py deleted file mode 100644 index e17b5035dc..0000000000 --- a/docs_src/settings/app02_an/config.py +++ /dev/null @@ -1,7 +0,0 @@ -from pydantic_settings import BaseSettings - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 diff --git a/docs_src/settings/app02_an/main.py b/docs_src/settings/app02_an/main.py deleted file mode 100644 index 3a578cc338..0000000000 --- a/docs_src/settings/app02_an/main.py +++ /dev/null @@ -1,22 +0,0 @@ -from functools import lru_cache - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -from .config import Settings - -app = FastAPI() - - -@lru_cache -def get_settings(): - return Settings() - - -@app.get("/info") -async def info(settings: Annotated[Settings, Depends(get_settings)]): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/app02_an/test_main.py b/docs_src/settings/app02_an/test_main.py deleted file mode 100644 index 7a04d7e8ee..0000000000 --- a/docs_src/settings/app02_an/test_main.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi.testclient import TestClient - -from .config import Settings -from .main import app, get_settings - -client = TestClient(app) - - -def get_settings_override(): - return Settings(admin_email="testing_admin@example.com") - - -app.dependency_overrides[get_settings] = get_settings_override - - -def test_app(): - response = client.get("/info") - data = response.json() - assert data == { - "app_name": "Awesome API", - "admin_email": "testing_admin@example.com", - "items_per_user": 50, - } diff --git a/docs_src/bigger_applications/app_an/routers/__init__.py b/docs_src/settings/app02_py39/__init__.py similarity index 100% rename from docs_src/bigger_applications/app_an/routers/__init__.py rename to docs_src/settings/app02_py39/__init__.py diff --git a/docs_src/settings/app02/config.py b/docs_src/settings/app02_py39/config.py similarity index 100% rename from docs_src/settings/app02/config.py rename to docs_src/settings/app02_py39/config.py diff --git a/docs_src/settings/app02/main.py b/docs_src/settings/app02_py39/main.py similarity index 100% rename from docs_src/settings/app02/main.py rename to docs_src/settings/app02_py39/main.py diff --git a/docs_src/settings/app02/test_main.py b/docs_src/settings/app02_py39/test_main.py similarity index 100% rename from docs_src/settings/app02/test_main.py rename to docs_src/settings/app02_py39/test_main.py diff --git a/docs_src/settings/app03/__init__.py b/docs_src/settings/app03/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs_src/settings/app03_an/__init__.py b/docs_src/settings/app03_an/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/docs_src/settings/app03_an/config.py b/docs_src/settings/app03_an/config.py deleted file mode 100644 index 08f8f88c28..0000000000 --- a/docs_src/settings/app03_an/config.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic_settings import BaseSettings, SettingsConfigDict - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 - - model_config = SettingsConfigDict(env_file=".env") diff --git a/docs_src/settings/app03_an/config_pv1.py b/docs_src/settings/app03_an/config_pv1.py deleted file mode 100644 index e1c3ee3006..0000000000 --- a/docs_src/settings/app03_an/config_pv1.py +++ /dev/null @@ -1,10 +0,0 @@ -from pydantic import BaseSettings - - -class Settings(BaseSettings): - app_name: str = "Awesome API" - admin_email: str - items_per_user: int = 50 - - class Config: - env_file = ".env" diff --git a/docs_src/settings/app03_an/main.py b/docs_src/settings/app03_an/main.py deleted file mode 100644 index 62f3476396..0000000000 --- a/docs_src/settings/app03_an/main.py +++ /dev/null @@ -1,22 +0,0 @@ -from functools import lru_cache - -from fastapi import Depends, FastAPI -from typing_extensions import Annotated - -from . import config - -app = FastAPI() - - -@lru_cache -def get_settings(): - return config.Settings() - - -@app.get("/info") -async def info(settings: Annotated[config.Settings, Depends(get_settings)]): - return { - "app_name": settings.app_name, - "admin_email": settings.admin_email, - "items_per_user": settings.items_per_user, - } diff --git a/docs_src/settings/app01/__init__.py b/docs_src/settings/app03_py39/__init__.py similarity index 100% rename from docs_src/settings/app01/__init__.py rename to docs_src/settings/app03_py39/__init__.py diff --git a/docs_src/settings/app03/config.py b/docs_src/settings/app03_py39/config.py similarity index 100% rename from docs_src/settings/app03/config.py rename to docs_src/settings/app03_py39/config.py diff --git a/docs_src/settings/app03/config_pv1.py b/docs_src/settings/app03_py39/config_pv1.py similarity index 100% rename from docs_src/settings/app03/config_pv1.py rename to docs_src/settings/app03_py39/config_pv1.py diff --git a/docs_src/settings/app03/main.py b/docs_src/settings/app03_py39/main.py similarity index 100% rename from docs_src/settings/app03/main.py rename to docs_src/settings/app03_py39/main.py diff --git a/docs_src/settings/tutorial001_pv1.py b/docs_src/settings/tutorial001_pv1_py39.py similarity index 100% rename from docs_src/settings/tutorial001_pv1.py rename to docs_src/settings/tutorial001_pv1_py39.py diff --git a/docs_src/settings/tutorial001.py b/docs_src/settings/tutorial001_py39.py similarity index 100% rename from docs_src/settings/tutorial001.py rename to docs_src/settings/tutorial001_py39.py diff --git a/docs_src/sql_databases/tutorial001.py b/docs_src/sql_databases/tutorial001.py deleted file mode 100644 index be86ec0eeb..0000000000 --- a/docs_src/sql_databases/tutorial001.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import List, Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select - - -class Hero(SQLModel, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - secret_name: str - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/") -def create_hero(hero: Hero, session: Session = Depends(get_session)) -> Hero: - session.add(hero) - session.commit() - session.refresh(hero) - return hero - - -@app.get("/heroes/") -def read_heroes( - session: Session = Depends(get_session), - offset: int = 0, - limit: int = Query(default=100, le=100), -) -> List[Hero]: - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}") -def read_hero(hero_id: int, session: Session = Depends(get_session)) -> Hero: - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: Session = Depends(get_session)): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/sql_databases/tutorial001_an.py b/docs_src/sql_databases/tutorial001_an.py deleted file mode 100644 index 8c000d31c7..0000000000 --- a/docs_src/sql_databases/tutorial001_an.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import List, Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select -from typing_extensions import Annotated - - -class Hero(SQLModel, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - secret_name: str - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -SessionDep = Annotated[Session, Depends(get_session)] - -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/") -def create_hero(hero: Hero, session: SessionDep) -> Hero: - session.add(hero) - session.commit() - session.refresh(hero) - return hero - - -@app.get("/heroes/") -def read_heroes( - session: SessionDep, - offset: int = 0, - limit: Annotated[int, Query(le=100)] = 100, -) -> List[Hero]: - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}") -def read_hero(hero_id: int, session: SessionDep) -> Hero: - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: SessionDep): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002.py b/docs_src/sql_databases/tutorial002.py deleted file mode 100644 index 4350d19c61..0000000000 --- a/docs_src/sql_databases/tutorial002.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import List, Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select - - -class HeroBase(SQLModel): - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - - -class Hero(HeroBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - secret_name: str - - -class HeroPublic(HeroBase): - id: int - - -class HeroCreate(HeroBase): - secret_name: str - - -class HeroUpdate(HeroBase): - name: Union[str, None] = None - age: Union[int, None] = None - secret_name: Union[str, None] = None - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/", response_model=HeroPublic) -def create_hero(hero: HeroCreate, session: Session = Depends(get_session)): - db_hero = Hero.model_validate(hero) - session.add(db_hero) - session.commit() - session.refresh(db_hero) - return db_hero - - -@app.get("/heroes/", response_model=List[HeroPublic]) -def read_heroes( - session: Session = Depends(get_session), - offset: int = 0, - limit: int = Query(default=100, le=100), -): - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(hero_id: int, session: Session = Depends(get_session)): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.patch("/heroes/{hero_id}", response_model=HeroPublic) -def update_hero( - hero_id: int, hero: HeroUpdate, session: Session = Depends(get_session) -): - hero_db = session.get(Hero, hero_id) - if not hero_db: - raise HTTPException(status_code=404, detail="Hero not found") - hero_data = hero.model_dump(exclude_unset=True) - hero_db.sqlmodel_update(hero_data) - session.add(hero_db) - session.commit() - session.refresh(hero_db) - return hero_db - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: Session = Depends(get_session)): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/sql_databases/tutorial002_an.py b/docs_src/sql_databases/tutorial002_an.py deleted file mode 100644 index 15e3d7c3a5..0000000000 --- a/docs_src/sql_databases/tutorial002_an.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import List, Union - -from fastapi import Depends, FastAPI, HTTPException, Query -from sqlmodel import Field, Session, SQLModel, create_engine, select -from typing_extensions import Annotated - - -class HeroBase(SQLModel): - name: str = Field(index=True) - age: Union[int, None] = Field(default=None, index=True) - - -class Hero(HeroBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) - secret_name: str - - -class HeroPublic(HeroBase): - id: int - - -class HeroCreate(HeroBase): - secret_name: str - - -class HeroUpdate(HeroBase): - name: Union[str, None] = None - age: Union[int, None] = None - secret_name: Union[str, None] = None - - -sqlite_file_name = "database.db" -sqlite_url = f"sqlite:///{sqlite_file_name}" - -connect_args = {"check_same_thread": False} -engine = create_engine(sqlite_url, connect_args=connect_args) - - -def create_db_and_tables(): - SQLModel.metadata.create_all(engine) - - -def get_session(): - with Session(engine) as session: - yield session - - -SessionDep = Annotated[Session, Depends(get_session)] -app = FastAPI() - - -@app.on_event("startup") -def on_startup(): - create_db_and_tables() - - -@app.post("/heroes/", response_model=HeroPublic) -def create_hero(hero: HeroCreate, session: SessionDep): - db_hero = Hero.model_validate(hero) - session.add(db_hero) - session.commit() - session.refresh(db_hero) - return db_hero - - -@app.get("/heroes/", response_model=List[HeroPublic]) -def read_heroes( - session: SessionDep, - offset: int = 0, - limit: Annotated[int, Query(le=100)] = 100, -): - heroes = session.exec(select(Hero).offset(offset).limit(limit)).all() - return heroes - - -@app.get("/heroes/{hero_id}", response_model=HeroPublic) -def read_hero(hero_id: int, session: SessionDep): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - return hero - - -@app.patch("/heroes/{hero_id}", response_model=HeroPublic) -def update_hero(hero_id: int, hero: HeroUpdate, session: SessionDep): - hero_db = session.get(Hero, hero_id) - if not hero_db: - raise HTTPException(status_code=404, detail="Hero not found") - hero_data = hero.model_dump(exclude_unset=True) - hero_db.sqlmodel_update(hero_data) - session.add(hero_db) - session.commit() - session.refresh(hero_db) - return hero_db - - -@app.delete("/heroes/{hero_id}") -def delete_hero(hero_id: int, session: SessionDep): - hero = session.get(Hero, hero_id) - if not hero: - raise HTTPException(status_code=404, detail="Hero not found") - session.delete(hero) - session.commit() - return {"ok": True} diff --git a/docs_src/static_files/tutorial001.py b/docs_src/static_files/tutorial001_py39.py similarity index 100% rename from docs_src/static_files/tutorial001.py rename to docs_src/static_files/tutorial001_py39.py diff --git a/docs_src/sub_applications/tutorial001.py b/docs_src/sub_applications/tutorial001_py39.py similarity index 100% rename from docs_src/sub_applications/tutorial001.py rename to docs_src/sub_applications/tutorial001_py39.py diff --git a/docs_src/templates/tutorial001.py b/docs_src/templates/tutorial001_py39.py similarity index 100% rename from docs_src/templates/tutorial001.py rename to docs_src/templates/tutorial001_py39.py diff --git a/docs_src/using_request_directly/tutorial001.py b/docs_src/using_request_directly/tutorial001_py39.py similarity index 100% rename from docs_src/using_request_directly/tutorial001.py rename to docs_src/using_request_directly/tutorial001_py39.py diff --git a/docs_src/websockets/tutorial001.py b/docs_src/websockets/tutorial001_py39.py similarity index 100% rename from docs_src/websockets/tutorial001.py rename to docs_src/websockets/tutorial001_py39.py diff --git a/docs_src/websockets/tutorial002_an.py b/docs_src/websockets/tutorial002_an.py deleted file mode 100644 index c838fbd306..0000000000 --- a/docs_src/websockets/tutorial002_an.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import Union - -from fastapi import ( - Cookie, - Depends, - FastAPI, - Query, - WebSocket, - WebSocketException, - status, -) -from fastapi.responses import HTMLResponse -from typing_extensions import Annotated - -app = FastAPI() - -html = """ - - - - Chat - - -

WebSocket Chat

-
- - - -
- - -
-
    -
- - - -""" - - -@app.get("/") -async def get(): - return HTMLResponse(html) - - -async def get_cookie_or_token( - websocket: WebSocket, - session: Annotated[Union[str, None], Cookie()] = None, - token: Annotated[Union[str, None], Query()] = None, -): - if session is None and token is None: - raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) - return session or token - - -@app.websocket("/items/{item_id}/ws") -async def websocket_endpoint( - *, - websocket: WebSocket, - item_id: str, - q: Union[int, None] = None, - cookie_or_token: Annotated[str, Depends(get_cookie_or_token)], -): - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text( - f"Session cookie or query token value is: {cookie_or_token}" - ) - if q is not None: - await websocket.send_text(f"Query parameter q is: {q}") - await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") diff --git a/docs_src/websockets/tutorial002.py b/docs_src/websockets/tutorial002_py39.py similarity index 100% rename from docs_src/websockets/tutorial002.py rename to docs_src/websockets/tutorial002_py39.py diff --git a/docs_src/websockets/tutorial003.py b/docs_src/websockets/tutorial003.py deleted file mode 100644 index d561633a8d..0000000000 --- a/docs_src/websockets/tutorial003.py +++ /dev/null @@ -1,83 +0,0 @@ -from typing import List - -from fastapi import FastAPI, WebSocket, WebSocketDisconnect -from fastapi.responses import HTMLResponse - -app = FastAPI() - -html = """ - - - - Chat - - -

WebSocket Chat

-

Your ID:

-
- - -
-
    -
- - - -""" - - -class ConnectionManager: - def __init__(self): - self.active_connections: List[WebSocket] = [] - - async def connect(self, websocket: WebSocket): - await websocket.accept() - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, websocket: WebSocket): - await websocket.send_text(message) - - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - - -@app.get("/") -async def get(): - return HTMLResponse(html) - - -@app.websocket("/ws/{client_id}") -async def websocket_endpoint(websocket: WebSocket, client_id: int): - await manager.connect(websocket) - try: - while True: - data = await websocket.receive_text() - await manager.send_personal_message(f"You wrote: {data}", websocket) - await manager.broadcast(f"Client #{client_id} says: {data}") - except WebSocketDisconnect: - manager.disconnect(websocket) - await manager.broadcast(f"Client #{client_id} left the chat") diff --git a/docs_src/wsgi/tutorial001.py b/docs_src/wsgi/tutorial001_py39.py similarity index 100% rename from docs_src/wsgi/tutorial001.py rename to docs_src/wsgi/tutorial001_py39.py diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index cabf482283..c4dd54dec6 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -24,13 +24,7 @@ from starlette.datastructures import UploadFile from typing_extensions import Annotated, get_args, get_origin # Copy from Pydantic v2, compatible with v1 -if sys.version_info < (3, 9): - # Pydantic no longer supports Python 3.8, this might be incorrect, but the code - # this is used for is also never reached in this codebase, as it's a copy of - # Pydantic's lenient_issubclass, just for compatibility with v1 - # TODO: remove when dropping support for Python 3.8 - WithArgsTypes: Tuple[Any, ...] = () -elif sys.version_info < (3, 10): +if sys.version_info < (3, 10): WithArgsTypes: tuple[Any, ...] = (typing._GenericAlias, types.GenericAlias) # type: ignore[attr-defined] else: WithArgsTypes: tuple[Any, ...] = ( diff --git a/pyproject.toml b/pyproject.toml index ef4440b1b4..ede080c5b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -200,7 +200,7 @@ relative_files = true context = '${CONTEXT}' dynamic_context = "test_function" omit = [ - "docs_src/response_model/tutorial003_04.py", + "docs_src/response_model/tutorial003_04_py39.py", "docs_src/response_model/tutorial003_04_py310.py", ] @@ -230,41 +230,30 @@ ignore = [ [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -"docs_src/dependencies/tutorial007.py" = ["F821"] -"docs_src/dependencies/tutorial008.py" = ["F821"] -"docs_src/dependencies/tutorial009.py" = ["F821"] -"docs_src/dependencies/tutorial010.py" = ["F821"] -"docs_src/custom_response/tutorial007.py" = ["B007"] -"docs_src/dataclasses/tutorial003.py" = ["I001"] -"docs_src/path_operation_advanced_configuration/tutorial007.py" = ["B904"] +"docs_src/dependencies/tutorial007_py39.py" = ["F821"] +"docs_src/dependencies/tutorial008_py39.py" = ["F821"] +"docs_src/dependencies/tutorial009_py39.py" = ["F821"] +"docs_src/dependencies/tutorial010_py39.py" = ["F821"] +"docs_src/custom_response/tutorial007_py39.py" = ["B007"] +"docs_src/dataclasses/tutorial003_py39.py" = ["I001"] "docs_src/path_operation_advanced_configuration/tutorial007_py39.py" = ["B904"] -"docs_src/path_operation_advanced_configuration/tutorial007_pv1.py" = ["B904"] "docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002.py" = ["B904"] "docs_src/custom_request_and_route/tutorial002_py39.py" = ["B904"] "docs_src/custom_request_and_route/tutorial002_py310.py" = ["B904"] -"docs_src/custom_request_and_route/tutorial002_an.py" = ["B904"] "docs_src/custom_request_and_route/tutorial002_an_py39.py" = ["B904"] "docs_src/custom_request_and_route/tutorial002_an_py310.py" = ["B904"] -"docs_src/dependencies/tutorial008_an.py" = ["F821"] "docs_src/dependencies/tutorial008_an_py39.py" = ["F821"] -"docs_src/query_params_str_validations/tutorial012_an.py" = ["B006"] "docs_src/query_params_str_validations/tutorial012_an_py39.py" = ["B006"] -"docs_src/query_params_str_validations/tutorial013_an.py" = ["B006"] "docs_src/query_params_str_validations/tutorial013_an_py39.py" = ["B006"] -"docs_src/security/tutorial004.py" = ["B904"] -"docs_src/security/tutorial004_an.py" = ["B904"] -"docs_src/security/tutorial004_an_py310.py" = ["B904"] +"docs_src/security/tutorial004_py39.py" = ["B904"] "docs_src/security/tutorial004_an_py39.py" = ["B904"] +"docs_src/security/tutorial004_an_py310.py" = ["B904"] "docs_src/security/tutorial004_py310.py" = ["B904"] -"docs_src/security/tutorial005.py" = ["B904"] -"docs_src/security/tutorial005_an.py" = ["B904"] "docs_src/security/tutorial005_an_py310.py" = ["B904"] "docs_src/security/tutorial005_an_py39.py" = ["B904"] "docs_src/security/tutorial005_py310.py" = ["B904"] "docs_src/security/tutorial005_py39.py" = ["B904"] -"docs_src/dependencies/tutorial008b.py" = ["B904"] -"docs_src/dependencies/tutorial008b_an.py" = ["B904"] +"docs_src/dependencies/tutorial008b_py39.py" = ["B904"] "docs_src/dependencies/tutorial008b_an_py39.py" = ["B904"] diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial001.py b/tests/test_tutorial/test_additional_responses/test_tutorial001.py index 3afeaff840..1a18db75ce 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.additional_responses.tutorial001 import app +from docs_src.additional_responses.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial002.py b/tests/test_tutorial/test_additional_responses/test_tutorial002.py index 91d6ff101f..bbcad8f294 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial002.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial002.py @@ -12,7 +12,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002"), + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial003.py b/tests/test_tutorial/test_additional_responses/test_tutorial003.py index bd34d2938c..90dc4e371e 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial003.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.additional_responses.tutorial003 import app +from docs_src.additional_responses.tutorial003_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_additional_responses/test_tutorial004.py b/tests/test_tutorial/test_additional_responses/test_tutorial004.py index 2d9491467e..cbd4fff7d6 100644 --- a/tests/test_tutorial/test_additional_responses/test_tutorial004.py +++ b/tests/test_tutorial/test_additional_responses/test_tutorial004.py @@ -12,7 +12,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004"), + pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py index b304f70153..bced1f6dfe 100644 --- a/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py +++ b/tests/test_tutorial/test_additional_status_codes/test_tutorial001.py @@ -3,16 +3,15 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py index 157fa5caf1..f17391956e 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.advanced_middleware.tutorial001 import app +from docs_src.advanced_middleware.tutorial001_py39 import app def test_middleware(): diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py index 79be52f4db..bae915406e 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.advanced_middleware.tutorial002 import app +from docs_src.advanced_middleware.tutorial002_py39 import app def test_middleware(): diff --git a/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py b/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py index 04a922ff7b..66697997c8 100644 --- a/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py +++ b/tests/test_tutorial/test_advanced_middleware/test_tutorial003.py @@ -1,7 +1,7 @@ from fastapi.responses import PlainTextResponse from fastapi.testclient import TestClient -from docs_src.advanced_middleware.tutorial003 import app +from docs_src.advanced_middleware.tutorial003_py39 import app @app.get("/large") diff --git a/tests/test_tutorial/test_async_tests/test_main.py b/tests/test_tutorial/test_async_tests/test_main_a.py similarity index 58% rename from tests/test_tutorial/test_async_tests/test_main.py rename to tests/test_tutorial/test_async_tests/test_main_a.py index 1f5d7186cc..f29acaa9a0 100644 --- a/tests/test_tutorial/test_async_tests/test_main.py +++ b/tests/test_tutorial/test_async_tests/test_main_a.py @@ -1,6 +1,6 @@ import pytest -from docs_src.async_tests.test_main import test_root +from docs_src.async_tests.app_a_py39.test_main import test_root @pytest.mark.anyio diff --git a/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py b/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py index bbd7bff30f..6f58116313 100644 --- a/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py +++ b/tests/test_tutorial/test_authentication_error_status_code/test_tutorial001.py @@ -4,14 +4,11 @@ import pytest from fastapi.testclient import TestClient from inline_snapshot import snapshot -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial001.py b/tests/test_tutorial/test_background_tasks/test_tutorial001.py index 0602cd8aa4..c0ad27a6f5 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial001.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial001.py @@ -3,7 +3,7 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.background_tasks.tutorial001 import app +from docs_src.background_tasks.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_background_tasks/test_tutorial002.py b/tests/test_tutorial/test_background_tasks/test_tutorial002.py index d5ef51ee2b..288a1c244e 100644 --- a/tests/test_tutorial/test_background_tasks/test_tutorial002.py +++ b/tests/test_tutorial/test_background_tasks/test_tutorial002.py @@ -5,16 +5,15 @@ from pathlib import Path import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial002", + "tutorial002_py39", pytest.param("tutorial002_py310", marks=needs_py310), - "tutorial002_an", - pytest.param("tutorial002_an_py39", marks=needs_py39), + "tutorial002_an_py39", pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py index a070f850f7..00574b5b0f 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.behind_a_proxy.tutorial001 import app +from docs_src.behind_a_proxy.tutorial001_py39 import app client = TestClient(app, root_path="/api/v1") diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py index f13046e018..da4acb28cc 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial001_01.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.behind_a_proxy.tutorial001_01 import app +from docs_src.behind_a_proxy.tutorial001_01_py39 import app client = TestClient( app, diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py index ce791e2157..1a49c0dfeb 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.behind_a_proxy.tutorial002 import app +from docs_src.behind_a_proxy.tutorial002_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py index ec17b41790..2d1d1b03c8 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial003.py @@ -1,7 +1,7 @@ from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from docs_src.behind_a_proxy.tutorial003 import app +from docs_src.behind_a_proxy.tutorial003_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py index 2f8eb46995..e8a03e8112 100644 --- a/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py +++ b/tests/test_tutorial/test_behind_a_proxy/test_tutorial004.py @@ -1,7 +1,7 @@ from dirty_equals import IsOneOf from fastapi.testclient import TestClient -from docs_src.behind_a_proxy.tutorial004 import app +from docs_src.behind_a_proxy.tutorial004_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_bigger_applications/test_main.py b/tests/test_tutorial/test_bigger_applications/test_main.py index fe40fad7d0..7493a9e661 100644 --- a/tests/test_tutorial/test_bigger_applications/test_main.py +++ b/tests/test_tutorial/test_bigger_applications/test_main.py @@ -4,15 +4,12 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "app_an.main", - pytest.param("app_an_py39.main", marks=needs_py39), - "app.main", + "app_py39.main", + "app_an_py39.main", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_body/test_tutorial001.py b/tests/test_tutorial/test_body/test_tutorial001.py index f8b5aee8d2..6aa9f2593e 100644 --- a/tests/test_tutorial/test_body/test_tutorial001.py +++ b/tests/test_tutorial/test_body/test_tutorial001.py @@ -11,7 +11,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_fields/test_tutorial001.py b/tests/test_tutorial/test_body_fields/test_tutorial001.py index fb68f28689..d54ec7191a 100644 --- a/tests/test_tutorial/test_body_fields/test_tutorial001.py +++ b/tests/test_tutorial/test_body_fields/test_tutorial001.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py index 1424055952..2035cf9448 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial001.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py index d18ceae486..d3e6401af2 100644 --- a/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py +++ b/tests/test_tutorial/test_body_multiple_params/test_tutorial003.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003", + "tutorial003_py39", pytest.param("tutorial003_py310", marks=needs_py310), - "tutorial003_an", - pytest.param("tutorial003_an_py39", marks=needs_py39), + "tutorial003_an_py39", pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py index 38ba3c8875..db9f04546e 100644 --- a/tests/test_tutorial/test_body_nested_models/test_tutorial009.py +++ b/tests/test_tutorial/test_body_nested_models/test_tutorial009.py @@ -4,14 +4,11 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial009", - pytest.param("tutorial009_py39", marks=needs_py39), + "tutorial009_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index f874dc9bd6..8bbc4d6992 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -3,15 +3,14 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_py39", marks=needs_py39), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py index b098f259c2..a425e893d8 100644 --- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py @@ -6,11 +6,11 @@ from ...utils import needs_pydanticv2 def get_client() -> TestClient: - from docs_src.conditional_openapi import tutorial001 + from docs_src.conditional_openapi import tutorial001_py39 - importlib.reload(tutorial001) + importlib.reload(tutorial001_py39) - client = TestClient(tutorial001.app) + client = TestClient(tutorial001_py39.app) return client diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py index a04dba2197..1fa9419a76 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.configure_swagger_ui.tutorial001 import app +from docs_src.configure_swagger_ui.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py index ea56b6f21c..c218cc858c 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.configure_swagger_ui.tutorial002 import app +from docs_src.configure_swagger_ui.tutorial002_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py index 926bbb14f0..8b73685499 100644 --- a/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py +++ b/tests/test_tutorial/test_configure_swagger_ui/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.configure_swagger_ui.tutorial003 import app +from docs_src.configure_swagger_ui.tutorial003_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py index 60643185a4..265dee944e 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial001.py @@ -5,16 +5,15 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py index cef6f66309..4a826a5374 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py @@ -6,7 +6,6 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot from tests.utils import ( - needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2, @@ -17,15 +16,13 @@ from tests.utils import ( @pytest.fixture( name="client", params=[ - pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py39", marks=needs_pydanticv2), pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_an", marks=needs_pydanticv2), - pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py39", marks=needs_pydanticv2), pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1), pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), - pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), - pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1), pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), ], ) diff --git a/tests/test_tutorial/test_cookie_params/test_tutorial001.py b/tests/test_tutorial/test_cookie_params/test_tutorial001.py index 90e8dfd37c..a65249d657 100644 --- a/tests/test_tutorial/test_cookie_params/test_tutorial001.py +++ b/tests/test_tutorial/test_cookie_params/test_tutorial001.py @@ -5,16 +5,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - "tutorial001", + "tutorial001_py39", pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_cors/test_tutorial001.py b/tests/test_tutorial/test_cors/test_tutorial001.py index f62c9df4f9..6a733693ae 100644 --- a/tests/test_tutorial/test_cors/test_tutorial001.py +++ b/tests/test_tutorial/test_cors/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.cors.tutorial001 import app +from docs_src.cors.tutorial001_py39 import app def test_cors(): diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py index cb8e8c2248..1816e5d975 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial001.py @@ -10,7 +10,7 @@ def client(): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) - from docs_src.custom_docs_ui.tutorial001 import app + from docs_src.custom_docs_ui.tutorial001_py39 import app with TestClient(app) as client: yield client diff --git a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py index 712618807c..e8b7eb7aa3 100644 --- a/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_docs_ui/test_tutorial002.py @@ -10,7 +10,7 @@ def client(): static_dir: Path = Path(os.getcwd()) / "static" print(static_dir) static_dir.mkdir(exist_ok=True) - from docs_src.custom_docs_ui.tutorial002 import app + from docs_src.custom_docs_ui.tutorial002_py39 import app with TestClient(app) as client: yield client diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py index f9fd0d1af8..b4ea537846 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial001.py @@ -6,17 +6,15 @@ import pytest from fastapi import Request from fastapi.testclient import TestClient -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001"), - pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_an"), - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py index c35752ed13..643011637e 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial002.py @@ -4,17 +4,15 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002"), - pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_an"), - pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py index 9e895b2daf..6cad7bd3f0 100644 --- a/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py +++ b/tests/test_tutorial/test_custom_request_and_route/test_tutorial003.py @@ -9,7 +9,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003"), + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001.py b/tests/test_tutorial/test_custom_response/test_tutorial001.py index fc83624673..c81e991ebf 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial001 import app +from docs_src.custom_response.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial001b.py b/tests/test_tutorial/test_custom_response/test_tutorial001b.py index 91e5c501e3..3337f47d5f 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial001b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial001b.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial001b import app +from docs_src.custom_response.tutorial001b_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial004.py b/tests/test_tutorial/test_custom_response/test_tutorial004.py index de60574f5e..0e7d69791b 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial004.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial004.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial004 import app +from docs_src.custom_response.tutorial004_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial005.py b/tests/test_tutorial/test_custom_response/test_tutorial005.py index 889bf3e929..fea105c28e 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial005.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial005.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial005 import app +from docs_src.custom_response.tutorial005_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006.py b/tests/test_tutorial/test_custom_response/test_tutorial006.py index 2d0a2cd3f6..e9e6ca1086 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial006 import app +from docs_src.custom_response.tutorial006_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006b.py b/tests/test_tutorial/test_custom_response/test_tutorial006b.py index 1739fd4570..7ca727d2cd 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006b.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial006b import app +from docs_src.custom_response.tutorial006b_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial006c.py b/tests/test_tutorial/test_custom_response/test_tutorial006c.py index 2675f2a93c..e3f76c8403 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial006c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial006c.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial006c import app +from docs_src.custom_response.tutorial006c_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial007.py b/tests/test_tutorial/test_custom_response/test_tutorial007.py index 4ede820b94..a62476ec14 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial007.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial007.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial007 import app +from docs_src.custom_response.tutorial007_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_custom_response/test_tutorial008.py b/tests/test_tutorial/test_custom_response/test_tutorial008.py index 10d88a5948..d9fe61f539 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial008.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial008.py @@ -2,15 +2,15 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.custom_response import tutorial008 -from docs_src.custom_response.tutorial008 import app +from docs_src.custom_response import tutorial008_py39 +from docs_src.custom_response.tutorial008_py39 import app client = TestClient(app) def test_get(tmp_path: Path): file_path: Path = tmp_path / "large-video-file.mp4" - tutorial008.some_file_path = str(file_path) + tutorial008_py39.some_file_path = str(file_path) test_content = b"Fake video bytes" file_path.write_bytes(test_content) response = client.get("/") diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009.py b/tests/test_tutorial/test_custom_response/test_tutorial009.py index ac20f89e67..cb6a514be6 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial009.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial009.py @@ -2,15 +2,15 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.custom_response import tutorial009 -from docs_src.custom_response.tutorial009 import app +from docs_src.custom_response import tutorial009_py39 +from docs_src.custom_response.tutorial009_py39 import app client = TestClient(app) def test_get(tmp_path: Path): file_path: Path = tmp_path / "large-video-file.mp4" - tutorial009.some_file_path = str(file_path) + tutorial009_py39.some_file_path = str(file_path) test_content = b"Fake video bytes" file_path.write_bytes(test_content) response = client.get("/") diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009b.py b/tests/test_tutorial/test_custom_response/test_tutorial009b.py index 4f56e2f3fe..9918bdb1ac 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial009b.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial009b.py @@ -2,15 +2,15 @@ from pathlib import Path from fastapi.testclient import TestClient -from docs_src.custom_response import tutorial009b -from docs_src.custom_response.tutorial009b import app +from docs_src.custom_response import tutorial009b_py39 +from docs_src.custom_response.tutorial009b_py39 import app client = TestClient(app) def test_get(tmp_path: Path): file_path: Path = tmp_path / "large-video-file.mp4" - tutorial009b.some_file_path = str(file_path) + tutorial009b_py39.some_file_path = str(file_path) test_content = b"Fake video bytes" file_path.write_bytes(test_content) response = client.get("/") diff --git a/tests/test_tutorial/test_custom_response/test_tutorial009c.py b/tests/test_tutorial/test_custom_response/test_tutorial009c.py index 23c711abe9..efc3a6b4a0 100644 --- a/tests/test_tutorial/test_custom_response/test_tutorial009c.py +++ b/tests/test_tutorial/test_custom_response/test_tutorial009c.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.custom_response.tutorial009c import app +from docs_src.custom_response.tutorial009c_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial001.py b/tests/test_tutorial/test_dataclasses/test_tutorial001.py index b36dee7684..d5f230bc42 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial001.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial001.py @@ -10,7 +10,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001"), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial002.py b/tests/test_tutorial/test_dataclasses/test_tutorial002.py index baaea45d8f..4cf8933805 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial002.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial002.py @@ -4,14 +4,13 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002"), - pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index 5728d2b6b3..d8cc45dd69 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -3,14 +3,13 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial003"), - pytest.param("tutorial003_py39", marks=needs_py39), + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial001.py b/tests/test_tutorial/test_dependencies/test_tutorial001.py index ed9944912e..8dac99cf30 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial001.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py index 8221c83d44..8a1346d0d2 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial004.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial004.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial004", + pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - "tutorial004_an", - pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_dependencies/test_tutorial006.py b/tests/test_tutorial/test_dependencies/test_tutorial006.py index 4530762f76..46f0066f9f 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial006.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial006.py @@ -4,15 +4,12 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial006", - "tutorial006_an", - pytest.param("tutorial006_an_py39", marks=needs_py39), + pytest.param("tutorial006_py39"), + pytest.param("tutorial006_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008b.py b/tests/test_tutorial/test_dependencies/test_tutorial008b.py index 4d70922657..91e00b3705 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008b.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008b.py @@ -3,15 +3,12 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial008b", - "tutorial008b_an", - pytest.param("tutorial008b_an_py39", marks=needs_py39), + pytest.param("tutorial008b_py39"), + pytest.param("tutorial008b_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008c.py b/tests/test_tutorial/test_dependencies/test_tutorial008c.py index 369b0a221d..aede6f8d25 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008c.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008c.py @@ -5,15 +5,12 @@ import pytest from fastapi.exceptions import FastAPIError from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="mod", params=[ - "tutorial008c", - "tutorial008c_an", - pytest.param("tutorial008c_an_py39", marks=needs_py39), + pytest.param("tutorial008c_py39"), + pytest.param("tutorial008c_an_py39"), ], ) def get_mod(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008d.py b/tests/test_tutorial/test_dependencies/test_tutorial008d.py index bc99bb3835..5477f8b953 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008d.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008d.py @@ -4,15 +4,12 @@ from types import ModuleType import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="mod", params=[ - "tutorial008d", - "tutorial008d_an", - pytest.param("tutorial008d_an_py39", marks=needs_py39), + pytest.param("tutorial008d_py39"), + pytest.param("tutorial008d_an_py39"), ], ) def get_mod(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial008e.py b/tests/test_tutorial/test_dependencies/test_tutorial008e.py index 1ae9ab2cd1..c433157ea1 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial008e.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial008e.py @@ -3,15 +3,12 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial008e", - "tutorial008e_an", - pytest.param("tutorial008e_an_py39", marks=needs_py39), + pytest.param("tutorial008e_py39"), + pytest.param("tutorial008e_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_dependencies/test_tutorial012.py b/tests/test_tutorial/test_dependencies/test_tutorial012.py index 0af17e9bc5..b791ee0aa1 100644 --- a/tests/test_tutorial/test_dependencies/test_tutorial012.py +++ b/tests/test_tutorial/test_dependencies/test_tutorial012.py @@ -4,15 +4,12 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial012", - "tutorial012_an", - pytest.param("tutorial012_an_py39", marks=needs_py39), + pytest.param("tutorial012_py39"), + pytest.param("tutorial012_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_events/test_tutorial001.py b/tests/test_tutorial/test_events/test_tutorial001.py index f65b92d127..5fe99d50df 100644 --- a/tests/test_tutorial/test_events/test_tutorial001.py +++ b/tests/test_tutorial/test_events/test_tutorial001.py @@ -6,7 +6,7 @@ from fastapi.testclient import TestClient @pytest.fixture(name="app", scope="module") def get_app(): with pytest.warns(DeprecationWarning): - from docs_src.events.tutorial001 import app + from docs_src.events.tutorial001_py39 import app yield app diff --git a/tests/test_tutorial/test_events/test_tutorial002.py b/tests/test_tutorial/test_events/test_tutorial002.py index 137294d737..0612899b55 100644 --- a/tests/test_tutorial/test_events/test_tutorial002.py +++ b/tests/test_tutorial/test_events/test_tutorial002.py @@ -6,7 +6,7 @@ from fastapi.testclient import TestClient @pytest.fixture(name="app", scope="module") def get_app(): with pytest.warns(DeprecationWarning): - from docs_src.events.tutorial002 import app + from docs_src.events.tutorial002_py39 import app yield app diff --git a/tests/test_tutorial/test_events/test_tutorial003.py b/tests/test_tutorial/test_events/test_tutorial003.py index 0ad1a1f8b2..38710edfea 100644 --- a/tests/test_tutorial/test_events/test_tutorial003.py +++ b/tests/test_tutorial/test_events/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.events.tutorial003 import ( +from docs_src.events.tutorial003_py39 import ( app, fake_answer_to_everything_ml_model, ml_models, diff --git a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py index a85a313501..83ecb300ee 100644 --- a/tests/test_tutorial/test_extending_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_extending_openapi/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.extending_openapi.tutorial001 import app +from docs_src.extending_openapi.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py index b816c9cab5..e11f73fe35 100644 --- a/tests/test_tutorial/test_extra_data_types/test_tutorial001.py +++ b/tests/test_tutorial/test_extra_data_types/test_tutorial001.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial003.py b/tests/test_tutorial/test_extra_models/test_tutorial003.py index 73aa299039..3aa83c0c40 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial003.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial003.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003", + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_extra_models/test_tutorial004.py b/tests/test_tutorial/test_extra_models/test_tutorial004.py index 7628db30c7..073375ccc9 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial004.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial004.py @@ -3,14 +3,11 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial004", - pytest.param("tutorial004_py39", marks=needs_py39), + pytest.param("tutorial004_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_extra_models/test_tutorial005.py b/tests/test_tutorial/test_extra_models/test_tutorial005.py index 553e442385..8e52d8d4ba 100644 --- a/tests/test_tutorial/test_extra_models/test_tutorial005.py +++ b/tests/test_tutorial/test_extra_models/test_tutorial005.py @@ -3,14 +3,11 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial005", - pytest.param("tutorial005_py39", marks=needs_py39), + pytest.param("tutorial005_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_first_steps/test_tutorial001.py b/tests/test_tutorial/test_first_steps/test_tutorial001.py index 6cc9fc2282..c102bb9999 100644 --- a/tests/test_tutorial/test_first_steps/test_tutorial001.py +++ b/tests/test_tutorial/test_first_steps/test_tutorial001.py @@ -1,7 +1,7 @@ import pytest from fastapi.testclient import TestClient -from docs_src.first_steps.tutorial001 import app +from docs_src.first_steps.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_generate_clients/test_tutorial003.py b/tests/test_tutorial/test_generate_clients/test_tutorial003.py index 1cd9678a1c..bac52e4fd6 100644 --- a/tests/test_tutorial/test_generate_clients/test_tutorial003.py +++ b/tests/test_tutorial/test_generate_clients/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.generate_clients.tutorial003 import app +from docs_src.generate_clients.tutorial003_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial001.py b/tests/test_tutorial/test_handling_errors/test_tutorial001.py index 8809c135bd..c01850fae6 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial001.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.handling_errors.tutorial001 import app +from docs_src.handling_errors.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial002.py b/tests/test_tutorial/test_handling_errors/test_tutorial002.py index efd86ebdec..09366a86fa 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial002.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.handling_errors.tutorial002 import app +from docs_src.handling_errors.tutorial002_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial003.py b/tests/test_tutorial/test_handling_errors/test_tutorial003.py index 4763f68f3f..51ac3e7b28 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial003.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.handling_errors.tutorial003 import app +from docs_src.handling_errors.tutorial003_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial004.py b/tests/test_tutorial/test_handling_errors/test_tutorial004.py index c04bf37245..376bc8266f 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial004.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial004.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.handling_errors.tutorial004 import app +from docs_src.handling_errors.tutorial004_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial005.py b/tests/test_tutorial/test_handling_errors/test_tutorial005.py index 581b2e4c75..d713c5d876 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial005.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial005.py @@ -1,7 +1,7 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.handling_errors.tutorial005 import app +from docs_src.handling_errors.tutorial005_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_handling_errors/test_tutorial006.py b/tests/test_tutorial/test_handling_errors/test_tutorial006.py index 7d2f553aac..491e461b3d 100644 --- a/tests/test_tutorial/test_handling_errors/test_tutorial006.py +++ b/tests/test_tutorial/test_handling_errors/test_tutorial006.py @@ -1,7 +1,7 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.handling_errors.tutorial006 import app +from docs_src.handling_errors.tutorial006_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial001.py b/tests/test_tutorial/test_header_param_models/test_tutorial001.py index bc876897b2..f59d50762c 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial001.py @@ -5,17 +5,15 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", - pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial002.py b/tests/test_tutorial/test_header_param_models/test_tutorial002.py index 0615521c43..a7a271ba41 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py @@ -5,21 +5,19 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 +from tests.utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002", marks=needs_pydanticv2), + pytest.param("tutorial002_py39", marks=needs_pydanticv2), pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_an", marks=needs_pydanticv2), - pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py39", marks=needs_pydanticv2), pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1), pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), - pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), - pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1), pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), ], ) diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial003.py b/tests/test_tutorial/test_header_param_models/test_tutorial003.py index 554a48d2e8..947587504f 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial003.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial003.py @@ -5,17 +5,15 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003", - pytest.param("tutorial003_py39", marks=needs_py39), + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - "tutorial003_an", - pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial001.py b/tests/test_tutorial/test_header_params/test_tutorial001.py index d6f7fe6189..beaf917f92 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial001.py +++ b/tests/test_tutorial/test_header_params/test_tutorial001.py @@ -10,9 +10,9 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial002.py b/tests/test_tutorial/test_header_params/test_tutorial002.py index 7158f8651b..b892ff905f 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial002.py +++ b/tests/test_tutorial/test_header_params/test_tutorial002.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial002", + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - "tutorial002_an", - pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_header_params/test_tutorial003.py b/tests/test_tutorial/test_header_params/test_tutorial003.py index 473b961236..ef76244159 100644 --- a/tests/test_tutorial/test_header_params/test_tutorial003.py +++ b/tests/test_tutorial/test_header_params/test_tutorial003.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003", + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - "tutorial003_an", - pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_metadata/test_tutorial001.py b/tests/test_tutorial/test_metadata/test_tutorial001.py index 04e8ff82b0..ead48577d0 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.metadata.tutorial001 import app +from docs_src.metadata.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_metadata/test_tutorial001_1.py b/tests/test_tutorial/test_metadata/test_tutorial001_1.py index 3efb1c4329..40878ccfd4 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial001_1.py +++ b/tests/test_tutorial/test_metadata/test_tutorial001_1.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.metadata.tutorial001_1 import app +from docs_src.metadata.tutorial001_1_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_metadata/test_tutorial004.py b/tests/test_tutorial/test_metadata/test_tutorial004.py index 5072203712..4ef93fd5e3 100644 --- a/tests/test_tutorial/test_metadata/test_tutorial004.py +++ b/tests/test_tutorial/test_metadata/test_tutorial004.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.metadata.tutorial004 import app +from docs_src.metadata.tutorial004_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py index 2df2b98893..975e07cbdd 100644 --- a/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_callbacks/test_tutorial001.py @@ -11,7 +11,7 @@ from tests.utils import needs_py310 @pytest.fixture( name="mod", params=[ - pytest.param("tutorial001"), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py index dc67ec401d..27619489fa 100644 --- a/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py +++ b/tests/test_tutorial/test_openapi_webhooks/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.openapi_webhooks.tutorial001 import app +from docs_src.openapi_webhooks.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py index 95542398e4..ee0b707108 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_operation_advanced_configuration.tutorial001 import app +from docs_src.path_operation_advanced_configuration.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py index d1388c3670..f6580d72e3 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_operation_advanced_configuration.tutorial002 import app +from docs_src.path_operation_advanced_configuration.tutorial002_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py index 313bb2a04a..104554fce3 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial003.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_operation_advanced_configuration.tutorial003 import app +from docs_src.path_operation_advanced_configuration.tutorial003_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index da5782d189..3805536e76 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -3,14 +3,13 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial004"), - pytest.param("tutorial004_py39", marks=needs_py39), + pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py index 07e2d7d202..e2a71236ff 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial005.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_operation_advanced_configuration.tutorial005 import app +from docs_src.path_operation_advanced_configuration.tutorial005_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py index f92c59015e..9484f7f573 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial006.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_operation_advanced_configuration.tutorial006 import app +from docs_src.path_operation_advanced_configuration.tutorial006_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index a90337a63d..204cca09ed 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -3,14 +3,13 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_pydanticv2 +from ...utils import needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial007"), - pytest.param("tutorial007_py39", marks=needs_py39), + pytest.param("tutorial007_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py index b38e4947cc..62b67a98c1 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007_pv1.py @@ -3,14 +3,13 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_pydanticv1 +from ...utils import needs_pydanticv1 @pytest.fixture( name="client", params=[ - pytest.param("tutorial007_pv1"), - pytest.param("tutorial007_pv1_py39", marks=needs_py39), + pytest.param("tutorial007_pv1_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py index 58dec5769d..5a0193adfa 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial002b.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_operation_configuration.tutorial002b import app +from docs_src.path_operation_configuration.tutorial002b_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index 0742f5d0e8..2a7a2b78b2 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -3,14 +3,13 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - "tutorial005", - pytest.param("tutorial005_py39", marks=needs_py39), + pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py index 91180d1097..5d9c55675f 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py @@ -1,7 +1,7 @@ import pytest from fastapi.testclient import TestClient -from docs_src.path_operation_configuration.tutorial006 import app +from docs_src.path_operation_configuration.tutorial006_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial004.py b/tests/test_tutorial/test_path_params/test_tutorial004.py index acbeaca769..f7f233ccff 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial004.py +++ b/tests/test_tutorial/test_path_params/test_tutorial004.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.path_params.tutorial004 import app +from docs_src.path_params.tutorial004_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_path_params/test_tutorial005.py b/tests/test_tutorial/test_path_params/test_tutorial005.py index 2e4b0146be..b3be70471a 100644 --- a/tests/test_tutorial/test_path_params/test_tutorial005.py +++ b/tests/test_tutorial/test_path_params/test_tutorial005.py @@ -1,7 +1,7 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.path_params.tutorial005 import app +from docs_src.path_params.tutorial005_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py index 3075a05f51..ce53a36eb8 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py @@ -23,7 +23,7 @@ from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - "tutorial001_an", + "tutorial001_an_py39", pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py index a402c663d1..57720bf489 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -24,7 +24,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial002_an", + "tutorial002_an_py39", pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py index 03155c9249..f020d4a975 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -23,7 +23,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003_an", + "tutorial003_an_py39", pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py index d2e204ddaf..daa6f0b050 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -17,14 +17,13 @@ import importlib from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial004_an", - pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial001.py b/tests/test_tutorial/test_query_param_models/test_tutorial001.py index 5b7bc7b424..86830b9341 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial001.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial001.py @@ -5,17 +5,15 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001", - pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial002.py b/tests/test_tutorial/test_query_param_models/test_tutorial002.py index 4432c9d8a3..e7de73f804 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py @@ -5,23 +5,19 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py39, needs_py310, needs_pydanticv1, needs_pydanticv2 +from tests.utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002", marks=needs_pydanticv2), - pytest.param("tutorial002_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_py39", marks=needs_pydanticv2), pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_an", marks=needs_pydanticv2), - pytest.param("tutorial002_an_py39", marks=[needs_py39, needs_pydanticv2]), + pytest.param("tutorial002_an_py39", marks=needs_pydanticv2), pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_pv1", marks=[needs_pydanticv1, needs_pydanticv1]), - pytest.param("tutorial002_pv1_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1), pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), - pytest.param("tutorial002_pv1_an", marks=[needs_pydanticv1]), - pytest.param("tutorial002_pv1_an_py39", marks=[needs_py39, needs_pydanticv1]), + pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1), pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), ], ) diff --git a/tests/test_tutorial/test_query_params/test_tutorial005.py b/tests/test_tutorial/test_query_params/test_tutorial005.py index 05ae85b451..ad4e4efa6b 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial005.py +++ b/tests/test_tutorial/test_query_params/test_tutorial005.py @@ -1,7 +1,7 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient -from docs_src.query_params.tutorial005 import app +from docs_src.query_params.tutorial005_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_query_params/test_tutorial006.py b/tests/test_tutorial/test_query_params/test_tutorial006.py index a0b5ef4943..349f8dd223 100644 --- a/tests/test_tutorial/test_query_params/test_tutorial006.py +++ b/tests/test_tutorial/test_query_params/test_tutorial006.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial006", + pytest.param("tutorial006_py39"), pytest.param("tutorial006_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py index e08e169633..de5dbbb2ee 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial010.py @@ -5,16 +5,15 @@ from dirty_equals import IsDict from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial010", + pytest.param("tutorial010_py39"), pytest.param("tutorial010_py310", marks=needs_py310), - "tutorial010_an", - pytest.param("tutorial010_an_py39", marks=needs_py39), + pytest.param("tutorial010_an_py39"), pytest.param("tutorial010_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py index f4da25752b..50b3c5683c 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial011.py @@ -4,17 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial011", - pytest.param("tutorial011_py39", marks=needs_py310), + pytest.param("tutorial011_py39"), pytest.param("tutorial011_py310", marks=needs_py310), - "tutorial011_an", - pytest.param("tutorial011_an_py39", marks=needs_py39), + pytest.param("tutorial011_an_py39"), pytest.param("tutorial011_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py index 549a90519e..1826928611 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial012.py @@ -3,16 +3,12 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial012", - pytest.param("tutorial012_py39", marks=needs_py39), - "tutorial012_an", - pytest.param("tutorial012_an_py39", marks=needs_py39), + pytest.param("tutorial012_py39"), + pytest.param("tutorial012_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py index f2f5f7a858..46c367c86b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial013.py @@ -3,15 +3,12 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial013", - "tutorial013_an", - pytest.param("tutorial013_an_py39", marks=needs_py39), + "tutorial013_py39", + "tutorial013_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py index edd40bb1ab..0feaccfa44 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial014.py @@ -3,16 +3,15 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial014", + pytest.param("tutorial014_py39"), pytest.param("tutorial014_py310", marks=needs_py310), - "tutorial014_an", - pytest.param("tutorial014_an_py39", marks=needs_py39), + pytest.param("tutorial014_an_py39"), pytest.param("tutorial014_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py index ae1c40286d..50ebf711f5 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py @@ -5,15 +5,14 @@ from dirty_equals import IsStr from fastapi.testclient import TestClient from inline_snapshot import snapshot -from ...utils import needs_py39, needs_py310, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial015_an", marks=needs_pydanticv2), + pytest.param("tutorial015_an_py39", marks=needs_pydanticv2), pytest.param("tutorial015_an_py310", marks=(needs_py310, needs_pydanticv2)), - pytest.param("tutorial015_an_py39", marks=(needs_py39, needs_pydanticv2)), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial001.py b/tests/test_tutorial/test_request_files/test_tutorial001.py index b069199612..a16d951dc6 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001.py @@ -4,15 +4,12 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial001", - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_py39", + "tutorial001_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_02.py b/tests/test_tutorial/test_request_files/test_tutorial001_02.py index 9075a17561..caea0d2e8f 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_02.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_02.py @@ -5,16 +5,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial001_02", + pytest.param("tutorial001_02_py39"), pytest.param("tutorial001_02_py310", marks=needs_py310), - "tutorial001_02_an", - pytest.param("tutorial001_02_an_py39", marks=needs_py39), + pytest.param("tutorial001_02_an_py39"), pytest.param("tutorial001_02_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_request_files/test_tutorial001_03.py b/tests/test_tutorial/test_request_files/test_tutorial001_03.py index 9fbe2166c1..53a7a0cf85 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial001_03.py +++ b/tests/test_tutorial/test_request_files/test_tutorial001_03.py @@ -3,15 +3,12 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial001_03", - "tutorial001_03_an", - pytest.param("tutorial001_03_an_py39", marks=needs_py39), + "tutorial001_03_py39", + "tutorial001_03_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial002.py b/tests/test_tutorial/test_request_files/test_tutorial002.py index 446a876578..34dbbb985d 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial002.py +++ b/tests/test_tutorial/test_request_files/test_tutorial002.py @@ -5,16 +5,12 @@ from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="app", params=[ - "tutorial002", - "tutorial002_an", - pytest.param("tutorial002_py39", marks=needs_py39), - pytest.param("tutorial002_an_py39", marks=needs_py39), + "tutorial002_py39", + "tutorial002_an_py39", ], ) def get_app(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_files/test_tutorial003.py b/tests/test_tutorial/test_request_files/test_tutorial003.py index 8534ba3e9a..fa4bfd5695 100644 --- a/tests/test_tutorial/test_request_files/test_tutorial003.py +++ b/tests/test_tutorial/test_request_files/test_tutorial003.py @@ -4,16 +4,12 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="app", params=[ - "tutorial003", - "tutorial003_an", - pytest.param("tutorial003_py39", marks=needs_py39), - pytest.param("tutorial003_an_py39", marks=needs_py39), + "tutorial003_py39", + "tutorial003_an_py39", ], ) def get_app(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial001.py b/tests/test_tutorial/test_request_form_models/test_tutorial001.py index 1ca3c96d33..a54df8536c 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial001.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial001.py @@ -4,15 +4,12 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial001", - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_py39", + "tutorial001_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002.py b/tests/test_tutorial/test_request_form_models/test_tutorial002.py index b3f6be63ab..9bb90fa064 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py @@ -3,15 +3,14 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_pydanticv2 +from ...utils import needs_pydanticv2 @pytest.fixture( name="client", params=[ - "tutorial002", - "tutorial002_an", - pytest.param("tutorial002_an_py39", marks=needs_py39), + "tutorial002_py39", + "tutorial002_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py index b503f23a53..41c7833bef 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py @@ -3,15 +3,14 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_pydanticv1 +from ...utils import needs_pydanticv1 @pytest.fixture( name="client", params=[ - "tutorial002_pv1", - "tutorial002_pv1_an", - pytest.param("tutorial002_pv1_an_py39", marks=needs_py39), + "tutorial002_pv1_py39", + "tutorial002_pv1_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_forms/test_tutorial001.py b/tests/test_tutorial/test_request_forms/test_tutorial001.py index 321f8022b7..da20535cf8 100644 --- a/tests/test_tutorial/test_request_forms/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms/test_tutorial001.py @@ -4,15 +4,12 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial001", - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_py39", + "tutorial001_an_py39", ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py index d12219245e..f37ffad443 100644 --- a/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py +++ b/tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py @@ -5,15 +5,12 @@ from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="app", params=[ - "tutorial001", - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + "tutorial001_py39", + "tutorial001_an_py39", ], ) def get_app(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py b/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py index 8ce3dcf1ab..05d5ca619f 100644 --- a/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py +++ b/tests/test_tutorial/test_response_change_status_code/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_change_status_code.tutorial001 import app +from docs_src.response_change_status_code.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_cookies/test_tutorial001.py b/tests/test_tutorial/test_response_cookies/test_tutorial001.py index eecd1a24c9..6b931c8bda 100644 --- a/tests/test_tutorial/test_response_cookies/test_tutorial001.py +++ b/tests/test_tutorial/test_response_cookies/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_cookies.tutorial001 import app +from docs_src.response_cookies.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_cookies/test_tutorial002.py b/tests/test_tutorial/test_response_cookies/test_tutorial002.py index 3e390025fa..3ee5f500c5 100644 --- a/tests/test_tutorial/test_response_cookies/test_tutorial002.py +++ b/tests/test_tutorial/test_response_cookies/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_cookies.tutorial002 import app +from docs_src.response_cookies.tutorial002_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py index 2cc4f3b0c1..127f0e4c1d 100644 --- a/tests/test_tutorial/test_response_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py @@ -9,7 +9,7 @@ from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="client", params=[ - pytest.param("tutorial001"), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_headers/test_tutorial001.py b/tests/test_tutorial/test_response_headers/test_tutorial001.py index 1549d6b5b1..6232aad23e 100644 --- a/tests/test_tutorial/test_response_headers/test_tutorial001.py +++ b/tests/test_tutorial/test_response_headers/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_headers.tutorial001 import app +from docs_src.response_headers.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_headers/test_tutorial002.py b/tests/test_tutorial/test_response_headers/test_tutorial002.py index 2826833f84..2ac2226cb1 100644 --- a/tests/test_tutorial/test_response_headers/test_tutorial002.py +++ b/tests/test_tutorial/test_response_headers/test_tutorial002.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_headers.tutorial002 import app +from docs_src.response_headers.tutorial002_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003.py b/tests/test_tutorial/test_response_model/test_tutorial003.py index 70cfd6e4cc..0f9eac890b 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003", + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_01.py b/tests/test_tutorial/test_response_model/test_tutorial003_01.py index 3975856b6c..1a7ce4c7a1 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_01.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_01.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003_01", + pytest.param("tutorial003_01_py39"), pytest.param("tutorial003_01_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_02.py b/tests/test_tutorial/test_response_model/test_tutorial003_02.py index eabd203456..b7507b7110 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_02.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_02.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_model.tutorial003_02 import app +from docs_src.response_model.tutorial003_02_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_03.py b/tests/test_tutorial/test_response_model/test_tutorial003_03.py index 970ff58450..ea3c733b24 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_03.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_03.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.response_model.tutorial003_03 import app +from docs_src.response_model.tutorial003_03_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_04.py b/tests/test_tutorial/test_response_model/test_tutorial003_04.py index f32e93ddcb..145af126fd 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_04.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_04.py @@ -9,7 +9,7 @@ from ...utils import needs_py310 @pytest.mark.parametrize( "module_name", [ - "tutorial003_04", + pytest.param("tutorial003_04_py39"), pytest.param("tutorial003_04_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial003_05.py b/tests/test_tutorial/test_response_model/test_tutorial003_05.py index 9500852e15..19a7c601bb 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial003_05.py +++ b/tests/test_tutorial/test_response_model/test_tutorial003_05.py @@ -9,7 +9,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003_05", + pytest.param("tutorial003_05_py39"), pytest.param("tutorial003_05_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial004.py b/tests/test_tutorial/test_response_model/test_tutorial004.py index 449a52b813..19f6998f70 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial004.py +++ b/tests/test_tutorial/test_response_model/test_tutorial004.py @@ -4,14 +4,13 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial004", - pytest.param("tutorial004_py39", marks=needs_py39), + pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial005.py b/tests/test_tutorial/test_response_model/test_tutorial005.py index a633a3fddd..47d77dc498 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial005.py +++ b/tests/test_tutorial/test_response_model/test_tutorial005.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial005", + pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_response_model/test_tutorial006.py b/tests/test_tutorial/test_response_model/test_tutorial006.py index 863522d1b4..a03aa41e8c 100644 --- a/tests/test_tutorial/test_response_model/test_tutorial006.py +++ b/tests/test_tutorial/test_response_model/test_tutorial006.py @@ -10,7 +10,7 @@ from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial006", + pytest.param("tutorial006_py39"), pytest.param("tutorial006_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py index c21cbb4bc2..4d1808cf6a 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py @@ -9,7 +9,7 @@ from ...utils import needs_py310, needs_pydanticv2 @pytest.fixture( name="client", params=[ - "tutorial001", + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py index b79f42e642..552bb66970 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py @@ -9,7 +9,7 @@ from ...utils import needs_py310, needs_pydanticv1 @pytest.fixture( name="client", params=[ - "tutorial001_pv1", + pytest.param("tutorial001_pv1_py39"), pytest.param("tutorial001_pv1_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py index 61aefd12a8..47ecb9ba73 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial004.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial004", + pytest.param("tutorial004_py39"), pytest.param("tutorial004_py310", marks=needs_py310), - "tutorial004_an", - pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py39"), pytest.param("tutorial004_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py index 12859227b1..1c964f3d15 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial005.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial005", + pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), - "tutorial005_an", - pytest.param("tutorial005_an_py39", marks=needs_py39), + pytest.param("tutorial005_an_py39"), pytest.param("tutorial005_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial001.py b/tests/test_tutorial/test_security/test_tutorial001.py index f572d6e3e1..cdaa50b191 100644 --- a/tests/test_tutorial/test_security/test_tutorial001.py +++ b/tests/test_tutorial/test_security/test_tutorial001.py @@ -3,15 +3,12 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial001", - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_py39"), + pytest.param("tutorial001_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py index 6b87351139..000c8b2ac4 100644 --- a/tests/test_tutorial/test_security/test_tutorial003.py +++ b/tests/test_tutorial/test_security/test_tutorial003.py @@ -4,16 +4,15 @@ import pytest from dirty_equals import IsDict from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - "tutorial003", + pytest.param("tutorial003_py39"), pytest.param("tutorial003_py310", marks=needs_py310), - "tutorial003_an", - pytest.param("tutorial003_an_py39", marks=needs_py39), + pytest.param("tutorial003_an_py39"), pytest.param("tutorial003_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial005.py b/tests/test_tutorial/test_security/test_tutorial005.py index ad644d61bb..7953e8e3f6 100644 --- a/tests/test_tutorial/test_security/test_tutorial005.py +++ b/tests/test_tutorial/test_security/test_tutorial005.py @@ -5,17 +5,15 @@ import pytest from dirty_equals import IsDict, IsOneOf from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="mod", params=[ - "tutorial005", + pytest.param("tutorial005_py39"), pytest.param("tutorial005_py310", marks=needs_py310), - "tutorial005_an", - pytest.param("tutorial005_py39", marks=needs_py39), - pytest.param("tutorial005_an_py39", marks=needs_py39), + pytest.param("tutorial005_an_py39"), pytest.param("tutorial005_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_security/test_tutorial006.py b/tests/test_tutorial/test_security/test_tutorial006.py index 9587159dc2..a4b3104bbc 100644 --- a/tests/test_tutorial/test_security/test_tutorial006.py +++ b/tests/test_tutorial/test_security/test_tutorial006.py @@ -4,15 +4,12 @@ from base64 import b64encode import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="client", params=[ - "tutorial006", - "tutorial006_an", - pytest.param("tutorial006_an_py39", marks=needs_py39), + pytest.param("tutorial006_py39"), + pytest.param("tutorial006_an_py39"), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py index 059fb889b3..b59d799a31 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py @@ -3,15 +3,14 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv2 @pytest.fixture( name="client", params=[ - "tutorial001", + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - pytest.param("tutorial001_py39", marks=needs_py39), ], ) def get_client(request: pytest.FixtureRequest) -> TestClient: diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py index cc9afeab75..61fbacfc34 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py @@ -3,15 +3,14 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39, needs_py310, needs_pydanticv2 +from ...utils import needs_py310, needs_pydanticv2 @pytest.fixture( name="client", params=[ - "tutorial002", + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - pytest.param("tutorial002_py39", marks=needs_py39), ], ) def get_client(request: pytest.FixtureRequest) -> TestClient: diff --git a/tests/test_tutorial/test_settings/test_app02.py b/tests/test_tutorial/test_settings/test_app02.py index 5e1232ea01..9cedc5a526 100644 --- a/tests/test_tutorial/test_settings/test_app02.py +++ b/tests/test_tutorial/test_settings/test_app02.py @@ -4,15 +4,14 @@ from types import ModuleType import pytest from pytest import MonkeyPatch -from ...utils import needs_py39, needs_pydanticv2 +from ...utils import needs_pydanticv2 @pytest.fixture( name="mod_path", params=[ - pytest.param("app02"), - pytest.param("app02_an"), - pytest.param("app02_an_py39", marks=needs_py39), + pytest.param("app02_py39"), + pytest.param("app02_an_py39"), ], ) def get_mod_path(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_settings/test_app03.py b/tests/test_tutorial/test_settings/test_app03.py index d9872c15f2..dbaf8f3f90 100644 --- a/tests/test_tutorial/test_settings/test_app03.py +++ b/tests/test_tutorial/test_settings/test_app03.py @@ -5,15 +5,14 @@ import pytest from fastapi.testclient import TestClient from pytest import MonkeyPatch -from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="mod_path", params=[ - pytest.param("app03"), - pytest.param("app03_an"), - pytest.param("app03_an_py39", marks=needs_py39), + pytest.param("app03_py39"), + pytest.param("app03_an_py39"), ], ) def get_mod_path(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_settings/test_tutorial001.py b/tests/test_tutorial/test_settings/test_tutorial001.py index 92a5782d4d..2c6dce261f 100644 --- a/tests/test_tutorial/test_settings/test_tutorial001.py +++ b/tests/test_tutorial/test_settings/test_tutorial001.py @@ -10,8 +10,8 @@ from ...utils import needs_pydanticv1, needs_pydanticv2 @pytest.fixture( name="app", params=[ - pytest.param("tutorial001", marks=needs_pydanticv2), - pytest.param("tutorial001_pv1", marks=needs_pydanticv1), + pytest.param("tutorial001_py39", marks=needs_pydanticv2), + pytest.param("tutorial001_pv1_py39", marks=needs_pydanticv1), ], ) def get_app(request: pytest.FixtureRequest, monkeypatch: MonkeyPatch): diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial001.py b/tests/test_tutorial/test_sql_databases/test_tutorial001.py index b45be4884d..e3e6bac128 100644 --- a/tests/test_tutorial/test_sql_databases/test_tutorial001.py +++ b/tests/test_tutorial/test_sql_databases/test_tutorial001.py @@ -9,7 +9,7 @@ from sqlalchemy import StaticPool from sqlmodel import SQLModel, create_engine from sqlmodel.main import default_registry -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 def clear_sqlmodel(): @@ -22,11 +22,9 @@ def clear_sqlmodel(): @pytest.fixture( name="client", params=[ - "tutorial001", - pytest.param("tutorial001_py39", marks=needs_py39), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_sql_databases/test_tutorial002.py b/tests/test_tutorial/test_sql_databases/test_tutorial002.py index da0b8b7ce7..e3b8c7f9e4 100644 --- a/tests/test_tutorial/test_sql_databases/test_tutorial002.py +++ b/tests/test_tutorial/test_sql_databases/test_tutorial002.py @@ -9,7 +9,7 @@ from sqlalchemy import StaticPool from sqlmodel import SQLModel, create_engine from sqlmodel.main import default_registry -from tests.utils import needs_py39, needs_py310 +from tests.utils import needs_py310 def clear_sqlmodel(): @@ -22,11 +22,9 @@ def clear_sqlmodel(): @pytest.fixture( name="client", params=[ - "tutorial002", - pytest.param("tutorial002_py39", marks=needs_py39), + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - "tutorial002_an", - pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_sub_applications/test_tutorial001.py b/tests/test_tutorial/test_sub_applications/test_tutorial001.py index 0790d207be..ef1f80164b 100644 --- a/tests/test_tutorial/test_sub_applications/test_tutorial001.py +++ b/tests/test_tutorial/test_sub_applications/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.sub_applications.tutorial001 import app +from docs_src.sub_applications.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_templates/test_tutorial001.py b/tests/test_tutorial/test_templates/test_tutorial001.py index 4d4729425e..818508037d 100644 --- a/tests/test_tutorial/test_templates/test_tutorial001.py +++ b/tests/test_tutorial/test_templates/test_tutorial001.py @@ -11,7 +11,7 @@ def test_main(): shutil.rmtree("./templates") shutil.copytree("./docs_src/templates/templates/", "./templates") shutil.copytree("./docs_src/templates/static/", "./static") - from docs_src.templates.tutorial001 import app + from docs_src.templates.tutorial001_py39 import app client = TestClient(app) response = client.get("/items/foo") diff --git a/tests/test_tutorial/test_testing/test_main.py b/tests/test_tutorial/test_testing/test_main_a.py similarity index 90% rename from tests/test_tutorial/test_testing/test_main.py rename to tests/test_tutorial/test_testing/test_main_a.py index fe34980813..9b3c796bdc 100644 --- a/tests/test_tutorial/test_testing/test_main.py +++ b/tests/test_tutorial/test_testing/test_main_a.py @@ -1,4 +1,4 @@ -from docs_src.app_testing.test_main import client, test_read_main +from docs_src.app_testing.app_a_py39.test_main import client, test_read_main def test_main(): diff --git a/tests/test_tutorial/test_testing/test_main_b.py b/tests/test_tutorial/test_testing/test_main_b.py index aa7f969c6d..3d679cd5a6 100644 --- a/tests/test_tutorial/test_testing/test_main_b.py +++ b/tests/test_tutorial/test_testing/test_main_b.py @@ -3,16 +3,15 @@ from types import ModuleType import pytest -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="test_module", params=[ - "app_b.test_main", + "app_b_py39.test_main", pytest.param("app_b_py310.test_main", marks=needs_py310), - "app_b_an.test_main", - pytest.param("app_b_an_py39.test_main", marks=needs_py39), + "app_b_an_py39.test_main", pytest.param("app_b_an_py310.test_main", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_testing/test_tutorial001.py b/tests/test_tutorial/test_testing/test_tutorial001.py index 471e896c93..5f6533306e 100644 --- a/tests/test_tutorial/test_testing/test_tutorial001.py +++ b/tests/test_tutorial/test_testing/test_tutorial001.py @@ -1,4 +1,4 @@ -from docs_src.app_testing.tutorial001 import client, test_read_main +from docs_src.app_testing.tutorial001_py39 import client, test_read_main def test_main(): diff --git a/tests/test_tutorial/test_testing/test_tutorial002.py b/tests/test_tutorial/test_testing/test_tutorial002.py index ec4f91ee7f..cc9b5ba27c 100644 --- a/tests/test_tutorial/test_testing/test_tutorial002.py +++ b/tests/test_tutorial/test_testing/test_tutorial002.py @@ -1,4 +1,4 @@ -from docs_src.app_testing.tutorial002 import test_read_main, test_websocket +from docs_src.app_testing.tutorial002_py39 import test_read_main, test_websocket def test_main(): diff --git a/tests/test_tutorial/test_testing/test_tutorial003.py b/tests/test_tutorial/test_testing/test_tutorial003.py index 2a5d670712..4faa820e98 100644 --- a/tests/test_tutorial/test_testing/test_tutorial003.py +++ b/tests/test_tutorial/test_testing/test_tutorial003.py @@ -3,5 +3,5 @@ import pytest def test_main(): with pytest.warns(DeprecationWarning): - from docs_src.app_testing.tutorial003 import test_read_items + from docs_src.app_testing.tutorial003_py39 import test_read_items test_read_items() diff --git a/tests/test_tutorial/test_testing/test_tutorial004.py b/tests/test_tutorial/test_testing/test_tutorial004.py index 812ee44c1f..c95214ffe3 100644 --- a/tests/test_tutorial/test_testing/test_tutorial004.py +++ b/tests/test_tutorial/test_testing/test_tutorial004.py @@ -1,4 +1,4 @@ -from docs_src.app_testing.tutorial004 import test_read_items +from docs_src.app_testing.tutorial004_py39 import test_read_items def test_main(): diff --git a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py index 00ee6ab1ea..6e9656bf55 100644 --- a/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py +++ b/tests/test_tutorial/test_testing_dependencies/test_tutorial001.py @@ -3,16 +3,15 @@ from types import ModuleType import pytest -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="test_module", params=[ - "tutorial001", + pytest.param("tutorial001_py39"), pytest.param("tutorial001_py310", marks=needs_py310), - "tutorial001_an", - pytest.param("tutorial001_an_py39", marks=needs_py39), + pytest.param("tutorial001_an_py39"), pytest.param("tutorial001_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py index 54c53ae1e3..33e661b164 100644 --- a/tests/test_tutorial/test_using_request_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_using_request_directly/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.using_request_directly.tutorial001 import app +from docs_src.using_request_directly.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_websockets/test_tutorial001.py b/tests/test_tutorial/test_websockets/test_tutorial001.py index 7dbecb2650..4f8368db28 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial001.py +++ b/tests/test_tutorial/test_websockets/test_tutorial001.py @@ -2,7 +2,7 @@ import pytest from fastapi.testclient import TestClient from fastapi.websockets import WebSocketDisconnect -from docs_src.websockets.tutorial001 import app +from docs_src.websockets.tutorial001_py39 import app client = TestClient(app) diff --git a/tests/test_tutorial/test_websockets/test_tutorial002.py b/tests/test_tutorial/test_websockets/test_tutorial002.py index 51aa5752a6..ebf1fc8e81 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial002.py +++ b/tests/test_tutorial/test_websockets/test_tutorial002.py @@ -5,16 +5,15 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from fastapi.websockets import WebSocketDisconnect -from ...utils import needs_py39, needs_py310 +from ...utils import needs_py310 @pytest.fixture( name="app", params=[ - "tutorial002", + pytest.param("tutorial002_py39"), pytest.param("tutorial002_py310", marks=needs_py310), - "tutorial002_an", - pytest.param("tutorial002_an_py39", marks=needs_py39), + pytest.param("tutorial002_an_py39"), pytest.param("tutorial002_an_py310", marks=needs_py310), ], ) diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py index 85efc18590..f303990f03 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial003.py +++ b/tests/test_tutorial/test_websockets/test_tutorial003.py @@ -4,14 +4,11 @@ from types import ModuleType import pytest from fastapi.testclient import TestClient -from ...utils import needs_py39 - @pytest.fixture( name="mod", params=[ - pytest.param("tutorial003"), - pytest.param("tutorial003_py39", marks=needs_py39), + pytest.param("tutorial003_py39"), ], ) def get_mod(request: pytest.FixtureRequest): @@ -32,13 +29,11 @@ def get_client(mod: ModuleType): return client -@needs_py39 def test_get(client: TestClient, html: str): response = client.get("/") assert response.text == html -@needs_py39 def test_websocket_handle_disconnection(client: TestClient): with client.websocket_connect("/ws/1234") as connection, client.websocket_connect( "/ws/5678" diff --git a/tests/test_tutorial/test_wsgi/test_tutorial001.py b/tests/test_tutorial/test_wsgi/test_tutorial001.py index 4f82252739..9fe8c2a4b8 100644 --- a/tests/test_tutorial/test_wsgi/test_tutorial001.py +++ b/tests/test_tutorial/test_wsgi/test_tutorial001.py @@ -1,6 +1,6 @@ from fastapi.testclient import TestClient -from docs_src.wsgi.tutorial001 import app +from docs_src.wsgi.tutorial001_py39 import app client = TestClient(app) From e0fd79139e40e873672544a31e0c3727602deeb1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 20:42:17 +0000 Subject: [PATCH 107/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 53e28fce1f..d3a9dc04e1 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* ⚰️ Remove Python 3.8 from CI and remove Python 3.8 examples from source docs. PR [#14559](https://github.com/fastapi/fastapi/pull/14559) by [@tiangolo](https://github.com/tiangolo). + ### Translations * 🌐 Update translations for pt (add-missing). PR [#14539](https://github.com/fastapi/fastapi/pull/14539) by [@tiangolo](https://github.com/tiangolo). From 84668c2acc3bd3443556d5ba98af674505e1d84b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 12:47:16 -0800 Subject: [PATCH 108/140] =?UTF-8?q?=F0=9F=94=A7=20Drop=20support=20for=20P?= =?UTF-8?q?ython=203.8=20(#14563)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ede080c5b3..dd16e1f8fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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.8" +requires-python = ">=3.9" authors = [ { name = "Sebastián Ramírez", email = "tiangolo@gmail.com" }, ] @@ -34,7 +34,6 @@ classifiers = [ "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From 7f9709d75ebb1aa1316553f8273a10bc2c8b23ab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 20:47:44 +0000 Subject: [PATCH 109/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d3a9dc04e1..5db95c2964 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Breaking Changes + +* 🔧 Drop support for Python 3.8. PR [#14563](https://github.com/fastapi/fastapi/pull/14563) by [@tiangolo](https://github.com/tiangolo). + ### Docs * ⚰️ Remove Python 3.8 from CI and remove Python 3.8 examples from source docs. PR [#14559](https://github.com/fastapi/fastapi/pull/14559) by [@tiangolo](https://github.com/tiangolo). From 1c4fc96c917a1097149b6e1ff8e36a62f8d994b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 13:25:59 -0800 Subject: [PATCH 110/140] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Upgrade=20internal?= =?UTF-8?q?=20syntax=20to=20Python=203.9+=20=F0=9F=8E=89=20(#14564)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs_src/header_params/tutorial003_an_py39.py | 4 +- fastapi/_compat/main.py | 30 ++- fastapi/_compat/may_v1.py | 25 +-- fastapi/_compat/model_field.py | 9 +- fastapi/_compat/shared.py | 35 ++-- fastapi/_compat/v1.py | 41 ++-- fastapi/_compat/v2.py | 78 ++++---- fastapi/applications.py | 156 ++++++++------- fastapi/background.py | 4 +- fastapi/concurrency.py | 6 +- fastapi/datastructures.py | 14 +- fastapi/dependencies/models.py | 22 +-- fastapi/dependencies/utils.py | 89 ++++----- fastapi/encoders.py | 17 +- fastapi/exceptions.py | 10 +- fastapi/openapi/docs.py | 9 +- fastapi/openapi/models.py | 109 ++++++----- fastapi/openapi/utils.py | 83 ++++---- fastapi/param_functions.py | 47 ++--- fastapi/params.py | 53 ++--- fastapi/routing.py | 184 +++++++++--------- fastapi/security/api_key.py | 3 +- fastapi/security/http.py | 7 +- fastapi/security/oauth2.py | 15 +- fastapi/security/open_id_connect_url.py | 3 +- fastapi/security/utils.py | 4 +- fastapi/temp_pydantic_v1_params.py | 52 ++--- fastapi/types.py | 8 +- fastapi/utils.py | 19 +- pdm_build.py | 6 +- scripts/docs.py | 14 +- scripts/mkdocs_hooks.py | 12 +- scripts/notify_translations.py | 20 +- scripts/people.py | 3 +- tests/main.py | 8 +- tests/test_additional_properties.py | 4 +- ...tional_responses_custom_validationerror.py | 4 +- ...est_additional_responses_response_class.py | 4 +- tests/test_allow_inf_nan_in_enforcing.py | 3 +- tests/test_ambiguous_params.py | 3 +- tests/test_annotated.py | 3 +- tests/test_arbitrary_types.py | 7 +- tests/test_compat.py | 16 +- tests/test_compat_params_v1.py | 7 +- tests/test_custom_schema_fields.py | 3 +- tests/test_datastructures.py | 3 +- tests/test_dependency_after_yield_raise.py | 3 +- .../test_dependency_after_yield_streaming.py | 4 +- .../test_dependency_after_yield_websockets.py | 4 +- tests/test_dependency_class.py | 2 +- tests/test_dependency_contextmanager.py | 9 +- tests/test_dependency_contextvars.py | 5 +- tests/test_dependency_duplicates.py | 4 +- tests/test_dependency_paramless.py | 3 +- tests/test_dependency_partial.py | 4 +- tests/test_dependency_security_overrides.py | 6 +- tests/test_dependency_wrapped.py | 2 +- tests/test_dependency_yield_scope.py | 11 +- .../test_dependency_yield_scope_websockets.py | 13 +- tests/test_file_and_form_order_issue_9116.py | 7 +- tests/test_form_default.py | 3 +- tests/test_forms_single_model.py | 5 +- tests/test_forms_single_param.py | 3 +- tests/test_generate_unique_id_function.py | 47 +++-- tests/test_generic_parameterless_depends.py | 3 +- ...t_get_model_definitions_formfeed_escape.py | 7 +- tests/test_invalid_path_param.py | 8 +- tests/test_invalid_sequence_param.py | 8 +- tests/test_multi_body_errors.py | 3 +- tests/test_multi_query_errors.py | 4 +- tests/test_no_schema_split.py | 3 +- tests/test_openapi_schema_type.py | 4 +- ...t_openapi_separate_input_output_schemas.py | 8 +- tests/test_optional_file_list.py | 4 +- tests/test_params_repr.py | 4 +- tests/test_pydantic_v1_v2_01.py | 4 +- tests/test_pydantic_v1_v2_list.py | 18 +- tests/test_pydantic_v1_v2_mixed.py | 26 +-- tests/test_pydantic_v1_v2_multifile/main.py | 18 +- .../test_pydantic_v1_v2_multifile/modelsv1.py | 4 +- .../test_pydantic_v1_v2_multifile/modelsv2.py | 4 +- .../modelsv2b.py | 4 +- tests/test_pydantic_v1_v2_noneable.py | 6 +- ...ataclasses_uuid_stringified_annotations.py | 4 +- tests/test_regex_deprecated_body.py | 3 +- tests/test_regex_deprecated_params.py | 3 +- ...test_request_body_parameters_media_type.py | 4 +- .../test_body/test_list.py | 19 +- .../test_body/test_optional_list.py | 19 +- .../test_body/test_optional_str.py | 3 +- .../test_body/test_required_str.py | 11 +- tests/test_request_params/test_body/utils.py | 4 +- .../test_cookie/test_optional_str.py | 3 +- .../test_cookie/test_required_str.py | 3 +- .../test_file/test_list.py | 19 +- .../test_file/test_optional.py | 3 +- .../test_file/test_optional_list.py | 19 +- .../test_file/test_required.py | 3 +- tests/test_request_params/test_file/utils.py | 4 +- .../test_form/test_list.py | 19 +- .../test_form/test_optional_list.py | 19 +- .../test_form/test_optional_str.py | 3 +- .../test_form/test_required_str.py | 3 +- tests/test_request_params/test_form/utils.py | 4 +- .../test_header/test_list.py | 19 +- .../test_header/test_optional_list.py | 19 +- .../test_header/test_optional_str.py | 3 +- .../test_header/test_required_str.py | 3 +- .../test_path/test_required_str.py | 3 +- .../test_query/test_list.py | 19 +- .../test_query/test_optional_list.py | 19 +- .../test_query/test_optional_str.py | 3 +- .../test_query/test_required_str.py | 3 +- tests/test_response_by_alias.py | 8 +- tests/test_response_class_no_mediatype.py | 4 +- tests/test_response_code_no_body.py | 4 +- ...est_response_model_as_return_annotation.py | 8 +- tests/test_response_model_data_filter.py | 4 +- ...sponse_model_data_filter_no_inheritance.py | 4 +- tests/test_response_model_invalid.py | 6 +- tests/test_response_model_sub_types.py | 6 +- tests/test_router_events.py | 21 +- ...uthorization_code_bearer_scopes_openapi.py | 3 +- ...ation_code_bearer_scopes_openapi_simple.py | 3 +- tests/test_security_scopes.py | 7 +- tests/test_security_scopes_dont_propagate.py | 9 +- tests/test_security_scopes_sub_dependency.py | 8 +- tests/test_serialize_response.py | 6 +- tests/test_serialize_response_dataclass.py | 8 +- tests/test_serialize_response_model.py | 12 +- .../test_stringified_annotation_dependency.py | 3 +- tests/test_stringified_annotations_simple.py | 3 +- tests/test_tuples.py | 8 +- .../test_websockets/test_tutorial003.py | 7 +- tests/test_union_body_discriminator.py | 6 +- ...test_union_body_discriminator_annotated.py | 3 +- tests/test_union_forms.py | 3 +- tests/test_validate_response.py | 6 +- tests/test_validate_response_dataclass.py | 6 +- tests/test_validate_response_recursive/app.py | 8 +- tests/test_webhooks_security.py | 2 +- tests/test_ws_dependencies.py | 7 +- 142 files changed, 939 insertions(+), 1038 deletions(-) diff --git a/docs_src/header_params/tutorial003_an_py39.py b/docs_src/header_params/tutorial003_an_py39.py index c1dd499611..5aad89407e 100644 --- a/docs_src/header_params/tutorial003_an_py39.py +++ b/docs_src/header_params/tutorial003_an_py39.py @@ -1,4 +1,4 @@ -from typing import Annotated, List, Union +from typing import Annotated, Union from fastapi import FastAPI, Header @@ -6,5 +6,5 @@ app = FastAPI() @app.get("/items/") -async def read_items(x_token: Annotated[Union[List[str], None], Header()] = None): +async def read_items(x_token: Annotated[Union[list[str], None], Header()] = None): return {"X-Token values": x_token} diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py index e5275950e8..2043a66781 100644 --- a/fastapi/_compat/main.py +++ b/fastapi/_compat/main.py @@ -1,12 +1,8 @@ import sys +from collections.abc import Sequence from functools import lru_cache from typing import ( Any, - Dict, - List, - Sequence, - Tuple, - Type, ) from fastapi._compat import may_v1 @@ -50,7 +46,7 @@ else: @lru_cache -def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]: +def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: if lenient_issubclass(model, may_v1.BaseModel): from fastapi._compat import v1 @@ -119,7 +115,7 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: def create_body_model( *, fields: Sequence[ModelField], model_name: str -) -> Type[BaseModel]: +) -> type[BaseModel]: if fields and isinstance(fields[0], may_v1.ModelField): from fastapi._compat import v1 @@ -221,7 +217,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type] -def _model_rebuild(model: Type[BaseModel]) -> None: +def _model_rebuild(model: type[BaseModel]) -> None: if lenient_issubclass(model, may_v1.BaseModel): from fastapi._compat import v1 @@ -232,7 +228,7 @@ def _model_rebuild(model: Type[BaseModel]) -> None: v2._model_rebuild(model) -def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: +def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: v1_model_fields = [ field for field in fields if isinstance(field, may_v1.ModelField) ] @@ -266,15 +262,15 @@ def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap: def get_definitions( *, - fields: List[ModelField], + fields: list[ModelField], model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, -) -> Tuple[ - Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], +) -> tuple[ + dict[ + tuple[ModelField, Literal["validation", "serialization"]], may_v1.JsonSchemaValue, ], - Dict[str, Dict[str, Any]], + dict[str, dict[str, Any]], ]: if sys.version_info < (3, 14): v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)] @@ -315,12 +311,12 @@ def get_schema_from_model_field( *, field: ModelField, model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], may_v1.JsonSchemaValue, ], separate_input_output_schemas: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: if isinstance(field, may_v1.ModelField): from fastapi._compat import v1 diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py index beea4d167f..c772162283 100644 --- a/fastapi/_compat/may_v1.py +++ b/fastapi/_compat/may_v1.py @@ -1,5 +1,6 @@ import sys -from typing import Any, Dict, List, Literal, Sequence, Tuple, Type, Union +from collections.abc import Sequence +from typing import Any, Literal, Union from fastapi.types import ModelNameMap @@ -60,14 +61,14 @@ if sys.version_info >= (3, 14): def get_definitions( *, - fields: List[ModelField], + fields: list[ModelField], model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, - ) -> Tuple[ - Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + ) -> tuple[ + dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], - Dict[str, Dict[str, Any]], + dict[str, dict[str, Any]], ]: return {}, {} # pragma: no cover @@ -94,11 +95,11 @@ else: from .v1 import get_definitions as get_definitions -RequestErrorModel: Type[BaseModel] = create_model("Request") +RequestErrorModel: type[BaseModel] = create_model("Request") -def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: - use_errors: List[Any] = [] +def _normalize_errors(errors: Sequence[Any]) -> list[dict[str, Any]]: + use_errors: list[Any] = [] for error in errors: if isinstance(error, ErrorWrapper): new_errors = ValidationError( # type: ignore[call-arg] @@ -113,9 +114,9 @@ def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]: def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...] -) -> List[Dict[str, Any]]: - updated_loc_errors: List[Any] = [ + *, errors: Sequence[Any], loc_prefix: tuple[Union[str, int], ...] +) -> list[dict[str, Any]]: + updated_loc_errors: list[Any] = [ {**err, "loc": loc_prefix + err.get("loc", ())} for err in _normalize_errors(errors) ] diff --git a/fastapi/_compat/model_field.py b/fastapi/_compat/model_field.py index fa2008c5e0..47d05cb946 100644 --- a/fastapi/_compat/model_field.py +++ b/fastapi/_compat/model_field.py @@ -1,8 +1,5 @@ from typing import ( Any, - Dict, - List, - Tuple, Union, ) @@ -34,10 +31,10 @@ class ModelField(Protocol): def validate( self, value: Any, - values: Dict[str, Any] = {}, # noqa: B006 + values: dict[str, Any] = {}, # noqa: B006 *, - loc: Tuple[Union[int, str], ...] = (), - ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: ... + loc: tuple[Union[int, str], ...] = (), + ) -> tuple[Any, Union[list[dict[str, Any]], None]]: ... def serialize( self, diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py index c4dd54dec6..3a11e88ac9 100644 --- a/fastapi/_compat/shared.py +++ b/fastapi/_compat/shared.py @@ -2,17 +2,11 @@ import sys import types import typing from collections import deque +from collections.abc import Mapping, Sequence from dataclasses import is_dataclass from typing import ( + Annotated, Any, - Deque, - FrozenSet, - List, - Mapping, - Sequence, - Set, - Tuple, - Type, Union, ) @@ -21,7 +15,7 @@ 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 Annotated, get_args, get_origin +from typing_extensions import get_args, get_origin # Copy from Pydantic v2, compatible with v1 if sys.version_info < (3, 10): @@ -39,26 +33,21 @@ PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 sequence_annotation_to_type = { Sequence: list, - List: list, list: list, - Tuple: tuple, tuple: tuple, - Set: set, set: set, - FrozenSet: frozenset, frozenset: frozenset, - Deque: deque, deque: deque, } sequence_types = tuple(sequence_annotation_to_type.keys()) -Url: Type[Any] +Url: type[Any] # Copy of Pydantic v2, compatible with v1 def lenient_issubclass( - cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...], None] + cls: Any, class_or_tuple: Union[type[Any], tuple[type[Any], ...], None] ) -> bool: try: return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] @@ -68,13 +57,13 @@ def lenient_issubclass( raise # pragma: no cover -def _annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: +def _annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: if lenient_issubclass(annotation, (str, bytes)): return False - return lenient_issubclass(annotation, sequence_types) # type: ignore[arg-type] + return lenient_issubclass(annotation, sequence_types) -def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: +def field_annotation_is_sequence(annotation: Union[type[Any], None]) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: for arg in get_args(annotation): @@ -87,10 +76,10 @@ def field_annotation_is_sequence(annotation: Union[Type[Any], None]) -> bool: def value_is_sequence(value: Any) -> bool: - return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) # type: ignore[arg-type] + 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: Union[type[Any], None]) -> bool: return ( lenient_issubclass( annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile) @@ -100,7 +89,7 @@ def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: ) -def field_annotation_is_complex(annotation: Union[Type[Any], None]) -> bool: +def field_annotation_is_complex(annotation: Union[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)) @@ -121,7 +110,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: Union[type[Any], None]) -> bool: origin = get_origin(annotation) if origin is Union or origin is UnionType: at_least_one_scalar_sequence = False diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py index e17ce8beaf..b29a61f734 100644 --- a/fastapi/_compat/v1.py +++ b/fastapi/_compat/v1.py @@ -1,15 +1,10 @@ +from collections.abc import Sequence from copy import copy from dataclasses import dataclass, is_dataclass from enum import Enum from typing import ( Any, Callable, - Dict, - List, - Sequence, - Set, - Tuple, - Type, Union, ) @@ -124,7 +119,7 @@ else: GetJsonSchemaHandler = Any -JsonSchemaValue = Dict[str, Any] +JsonSchemaValue = dict[str, Any] CoreSchema = Any Url = AnyUrl @@ -154,7 +149,7 @@ class PydanticSchemaGenerationError(Exception): pass -RequestErrorModel: Type[BaseModel] = create_model("Request") +RequestErrorModel: type[BaseModel] = create_model("Request") def with_info_plain_validator_function( @@ -169,10 +164,10 @@ def with_info_plain_validator_function( def get_model_definitions( *, - flat_models: Set[Union[Type[BaseModel], Type[Enum]]], - model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str], -) -> Dict[str, Any]: - definitions: Dict[str, Dict[str, Any]] = {} + flat_models: set[Union[type[BaseModel], type[Enum]]], + model_name_map: dict[Union[type[BaseModel], type[Enum]], str], +) -> dict[str, Any]: + definitions: dict[str, dict[str, Any]] = {} for model in flat_models: m_schema, m_definitions, m_nested_models = model_process_schema( model, model_name_map=model_name_map, ref_prefix=REF_PREFIX @@ -219,7 +214,7 @@ def is_pv1_scalar_sequence_field(field: ModelField) -> bool: return False -def _model_rebuild(model: Type[BaseModel]) -> None: +def _model_rebuild(model: type[BaseModel]) -> None: model.update_forward_refs() @@ -237,11 +232,11 @@ def get_schema_from_model_field( *, field: ModelField, model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], separate_input_output_schemas: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: return field_schema( # type: ignore[no-any-return] field, model_name_map=model_name_map, ref_prefix=REF_PREFIX )[0] @@ -254,12 +249,12 @@ def get_schema_from_model_field( def get_definitions( *, - fields: List[ModelField], + fields: list[ModelField], model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, -) -> Tuple[ - Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], - Dict[str, Dict[str, Any]], +) -> tuple[ + dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + dict[str, dict[str, Any]], ]: models = get_flat_models_from_fields(fields, known_models=set()) return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) @@ -293,7 +288,7 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return] -def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: +def get_missing_field_error(loc: tuple[str, ...]) -> dict[str, Any]: missing_field_error = ErrorWrapper(MissingError(), loc=loc) new_error = ValidationError([missing_field_error], RequestErrorModel) return new_error.errors()[0] # type: ignore[return-value] @@ -301,12 +296,12 @@ def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: def create_body_model( *, fields: Sequence[ModelField], model_name: str -) -> Type[BaseModel]: +) -> type[BaseModel]: BodyModel = create_model(model_name) for f in fields: BodyModel.__fields__[f.name] = f # type: ignore[index] return BodyModel -def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: +def get_model_fields(model: type[BaseModel]) -> list[ModelField]: return list(model.__fields__.values()) # type: ignore[attr-defined] diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index a17d625568..c4fa49e403 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -1,16 +1,12 @@ import re import warnings +from collections.abc import Sequence from copy import copy, deepcopy from dataclasses import dataclass, is_dataclass from enum import Enum from typing import ( + Annotated, Any, - Dict, - List, - Sequence, - Set, - Tuple, - Type, Union, cast, ) @@ -33,7 +29,7 @@ from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue from pydantic_core import CoreSchema as CoreSchema from pydantic_core import PydanticUndefined, PydanticUndefinedType from pydantic_core import Url as Url -from typing_extensions import Annotated, Literal, get_args, get_origin +from typing_extensions import Literal, get_args, get_origin try: from pydantic_core.core_schema import ( @@ -77,7 +73,7 @@ _Attrs = { # TODO: remove when dropping support for Pydantic < v2.12.3 -def asdict(field_info: FieldInfo) -> Dict[str, Any]: +def asdict(field_info: FieldInfo) -> dict[str, Any]: attributes = {} for attr in _Attrs: value = getattr(field_info, attr, Undefined) @@ -169,10 +165,10 @@ class ModelField: def validate( self, value: Any, - values: Dict[str, Any] = {}, # noqa: B006 + values: dict[str, Any] = {}, # noqa: B006 *, - loc: Tuple[Union[int, str], ...] = (), - ) -> Tuple[Any, Union[List[Dict[str, Any]], None]]: + loc: tuple[Union[int, str], ...] = (), + ) -> tuple[Any, Union[list[dict[str, Any]], None]]: try: return ( self._type_adapter.validate_python(value, from_attributes=True), @@ -220,7 +216,7 @@ def get_annotation_from_field_info( return annotation -def _model_rebuild(model: Type[BaseModel]) -> None: +def _model_rebuild(model: type[BaseModel]) -> None: model.model_rebuild() @@ -245,11 +241,11 @@ def get_schema_from_model_field( *, field: ModelField, model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], separate_input_output_schemas: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: override_mode: Union[Literal["validation"], None] = ( None if (separate_input_output_schemas or _has_computed_fields(field)) @@ -277,9 +273,9 @@ def get_definitions( fields: Sequence[ModelField], model_name_map: ModelNameMap, separate_input_output_schemas: bool = True, -) -> Tuple[ - Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], - Dict[str, Dict[str, Any]], +) -> tuple[ + dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + dict[str, dict[str, Any]], ]: schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) validation_fields = [field for field in fields if field.mode == "validation"] @@ -324,7 +320,7 @@ def get_definitions( for field in list(fields) + list(unique_flat_model_fields) ] field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs) - for item_def in cast(Dict[str, Dict[str, Any]], definitions).values(): + for item_def in cast(dict[str, dict[str, Any]], definitions).values(): if "description" in item_def: item_description = cast(str, item_def["description"]).split("\f")[0] item_def["description"] = item_description @@ -338,9 +334,9 @@ def get_definitions( def _replace_refs( *, - schema: Dict[str, Any], - old_name_to_new_name_map: Dict[str, str], -) -> Dict[str, Any]: + schema: dict[str, Any], + old_name_to_new_name_map: dict[str, str], +) -> dict[str, Any]: new_schema = deepcopy(schema) for key, value in new_schema.items(): if key == "$ref": @@ -375,13 +371,13 @@ def _replace_refs( def _remap_definitions_and_field_mappings( *, model_name_map: ModelNameMap, - definitions: Dict[str, Any], - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + definitions: dict[str, Any], + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], -) -> Tuple[ - Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], - Dict[str, Any], +) -> tuple[ + dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], + dict[str, Any], ]: old_name_to_new_name_map = {} for field_key, schema in field_mapping.items(): @@ -394,8 +390,8 @@ def _remap_definitions_and_field_mappings( continue old_name_to_new_name_map[old_name] = new_name - new_field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + new_field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ] = {} for field_key, schema in field_mapping.items(): new_schema = _replace_refs( @@ -461,10 +457,10 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: origin_type = get_origin(union_arg) or union_arg break assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type] - return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return] + return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] -def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: +def get_missing_field_error(loc: tuple[str, ...]) -> dict[str, Any]: error = ValidationError.from_exception_data( "Field required", [{"type": "missing", "loc": loc, "input": {}}] ).errors(include_url=False)[0] @@ -474,14 +470,14 @@ def get_missing_field_error(loc: Tuple[str, ...]) -> Dict[str, Any]: def create_body_model( *, fields: Sequence[ModelField], model_name: str -) -> Type[BaseModel]: +) -> type[BaseModel]: field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields} - BodyModel: Type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] + BodyModel: type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] return BodyModel -def get_model_fields(model: Type[BaseModel]) -> List[ModelField]: - model_fields: List[ModelField] = [] +def get_model_fields(model: type[BaseModel]) -> list[ModelField]: + model_fields: list[ModelField] = [] for name, field_info in model.model_fields.items(): type_ = field_info.annotation if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_): @@ -501,17 +497,17 @@ def get_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]] -TypeModelSet = Set[TypeModelOrEnum] +TypeModelOrEnum = Union[type["BaseModel"], type[Enum]] +TypeModelSet = set[TypeModelOrEnum] def normalize_name(name: str) -> str: return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name) -def get_model_name_map(unique_models: TypeModelSet) -> Dict[TypeModelOrEnum, str]: +def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]: name_model_map = {} - conflicting_names: Set[str] = set() + conflicting_names: set[str] = set() for model in unique_models: model_name = normalize_name(model.__name__) if model_name in conflicting_names: @@ -528,7 +524,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: Union[TypeModelSet, None] = None ) -> TypeModelSet: known_models = known_models or set() fields = get_model_fields(model) diff --git a/fastapi/applications.py b/fastapi/applications.py index 02193312b9..54175cb3b0 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -1,14 +1,10 @@ +from collections.abc import Awaitable, Coroutine, Sequence from enum import Enum from typing import ( + Annotated, Any, - Awaitable, Callable, - Coroutine, - Dict, - List, Optional, - Sequence, - Type, TypeVar, Union, ) @@ -44,7 +40,7 @@ from starlette.requests import Request from starlette.responses import HTMLResponse, JSONResponse, Response from starlette.routing import BaseRoute from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send -from typing_extensions import Annotated, deprecated +from typing_extensions import deprecated AppType = TypeVar("AppType", bound="FastAPI") @@ -81,7 +77,7 @@ class FastAPI(Starlette): ), ] = False, routes: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -230,7 +226,7 @@ class FastAPI(Starlette): ), ] = "/openapi.json", openapi_tags: Annotated[ - Optional[List[Dict[str, Any]]], + Optional[list[dict[str, Any]]], Doc( """ A list of tags used by OpenAPI, these are the same `tags` you can set @@ -290,7 +286,7 @@ class FastAPI(Starlette): ), ] = None, servers: Annotated[ - Optional[List[Dict[str, Union[str, Any]]]], + Optional[list[dict[str, Union[str, Any]]]], Doc( """ A `list` of `dict`s with connectivity information to a target server. @@ -361,7 +357,7 @@ class FastAPI(Starlette): ), ] = None, default_response_class: Annotated[ - Type[Response], + type[Response], Doc( """ The default response class to be used. @@ -467,7 +463,7 @@ class FastAPI(Starlette): ), ] = "/docs/oauth2-redirect", swagger_ui_init_oauth: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ OAuth2 configuration for the Swagger UI, by default shown at `/docs`. @@ -493,8 +489,8 @@ class FastAPI(Starlette): ] = None, exception_handlers: Annotated[ Optional[ - Dict[ - Union[int, Type[Exception]], + dict[ + Union[int, type[Exception]], Callable[[Request, Any], Coroutine[Any, Any, Response]], ] ], @@ -567,7 +563,7 @@ class FastAPI(Starlette): ), ] = None, contact: Annotated[ - Optional[Dict[str, Union[str, Any]]], + Optional[dict[str, Union[str, Any]]], Doc( """ A dictionary with the contact information for the exposed API. @@ -600,7 +596,7 @@ class FastAPI(Starlette): ), ] = None, license_info: Annotated[ - Optional[Dict[str, Union[str, Any]]], + Optional[dict[str, Union[str, Any]]], Doc( """ A dictionary with the license information for the exposed API. @@ -689,7 +685,7 @@ class FastAPI(Starlette): ), ] = True, responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses to be shown in OpenAPI. @@ -705,7 +701,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ OpenAPI callbacks that should apply to all *path operations*. @@ -762,7 +758,7 @@ class FastAPI(Starlette): ), ] = True, swagger_ui_parameters: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Parameters to configure Swagger UI, the autogenerated interactive API @@ -820,7 +816,7 @@ class FastAPI(Starlette): ), ] = True, openapi_external_docs: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ This field allows you to provide additional external documentation links. @@ -906,7 +902,7 @@ class FastAPI(Starlette): """ ), ] = "3.1.0" - self.openapi_schema: Optional[Dict[str, Any]] = None + self.openapi_schema: Optional[dict[str, Any]] = None if self.openapi_url: assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'" @@ -949,7 +945,7 @@ class FastAPI(Starlette): ), ] = State() self.dependency_overrides: Annotated[ - Dict[Callable[..., Any], Callable[..., Any]], + dict[Callable[..., Any], Callable[..., Any]], Doc( """ A dictionary with overrides for the dependencies. @@ -980,7 +976,7 @@ class FastAPI(Starlette): responses=responses, generate_unique_id_function=generate_unique_id_function, ) - self.exception_handlers: Dict[ + self.exception_handlers: dict[ Any, Callable[[Request, Any], Union[Response, Awaitable[Response]]] ] = {} if exception_handlers is None else dict(exception_handlers) self.exception_handlers.setdefault(HTTPException, http_exception_handler) @@ -993,7 +989,7 @@ class FastAPI(Starlette): websocket_request_validation_exception_handler, # type: ignore ) - self.user_middleware: List[Middleware] = ( + self.user_middleware: list[Middleware] = ( [] if middleware is None else list(middleware) ) self.middleware_stack: Union[ASGIApp, None] = None @@ -1047,7 +1043,7 @@ class FastAPI(Starlette): app = cls(app, *args, **kwargs) return app - def openapi(self) -> Dict[str, Any]: + def openapi(self) -> dict[str, Any]: """ Generate the OpenAPI schema of the application. This is called by FastAPI internally. @@ -1145,14 +1141,14 @@ class FastAPI(Starlette): *, response_model: Any = Default(None), status_code: Optional[int] = None, - tags: Optional[List[Union[str, Enum]]] = None, + tags: Optional[list[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", - responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, deprecated: Optional[bool] = None, - methods: Optional[List[str]] = None, + methods: Optional[list[str]] = None, operation_id: Optional[str] = None, response_model_include: Optional[IncEx] = None, response_model_exclude: Optional[IncEx] = None, @@ -1161,11 +1157,11 @@ class FastAPI(Starlette): response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[Type[Response], DefaultPlaceholder] = Default( + response_class: Union[type[Response], DefaultPlaceholder] = Default( JSONResponse ), name: Optional[str] = None, - openapi_extra: Optional[Dict[str, Any]] = None, + openapi_extra: Optional[dict[str, Any]] = None, generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), @@ -1203,14 +1199,14 @@ class FastAPI(Starlette): *, response_model: Any = Default(None), status_code: Optional[int] = None, - tags: Optional[List[Union[str, Enum]]] = None, + tags: Optional[list[Union[str, Enum]]] = None, dependencies: Optional[Sequence[Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", - responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, deprecated: Optional[bool] = None, - methods: Optional[List[str]] = None, + methods: Optional[list[str]] = None, operation_id: Optional[str] = None, response_model_include: Optional[IncEx] = None, response_model_exclude: Optional[IncEx] = None, @@ -1219,9 +1215,9 @@ class FastAPI(Starlette): response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Type[Response] = Default(JSONResponse), + response_class: type[Response] = Default(JSONResponse), name: Optional[str] = None, - openapi_extra: Optional[Dict[str, Any]] = None, + openapi_extra: Optional[dict[str, Any]] = None, generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( generate_unique_id ), @@ -1343,7 +1339,7 @@ class FastAPI(Starlette): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to all the *path operations* in this @@ -1385,7 +1381,7 @@ class FastAPI(Starlette): ), ] = None, responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses to be shown in OpenAPI. @@ -1452,7 +1448,7 @@ class FastAPI(Starlette): ), ] = True, default_response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Default response class to be used for the *path operations* in this @@ -1480,7 +1476,7 @@ class FastAPI(Starlette): ), ] = Default(JSONResponse), callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1603,7 +1599,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -1669,7 +1665,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -1810,7 +1806,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -1831,7 +1827,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1847,7 +1843,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -1976,7 +1972,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -2042,7 +2038,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -2183,7 +2179,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -2204,7 +2200,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2220,7 +2216,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2354,7 +2350,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -2420,7 +2416,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -2561,7 +2557,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -2582,7 +2578,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2598,7 +2594,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2732,7 +2728,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -2798,7 +2794,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -2939,7 +2935,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -2960,7 +2956,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2976,7 +2972,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3105,7 +3101,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -3171,7 +3167,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -3312,7 +3308,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -3333,7 +3329,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3349,7 +3345,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3478,7 +3474,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -3544,7 +3540,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -3685,7 +3681,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -3706,7 +3702,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3722,7 +3718,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3851,7 +3847,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -3917,7 +3913,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -4058,7 +4054,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -4079,7 +4075,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4095,7 +4091,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4229,7 +4225,7 @@ class FastAPI(Starlette): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -4295,7 +4291,7 @@ class FastAPI(Starlette): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -4436,7 +4432,7 @@ class FastAPI(Starlette): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -4457,7 +4453,7 @@ class FastAPI(Starlette): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4473,7 +4469,7 @@ class FastAPI(Starlette): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4628,7 +4624,7 @@ class FastAPI(Starlette): def exception_handler( self, exc_class_or_status_code: Annotated[ - Union[int, Type[Exception]], + Union[int, type[Exception]], Doc( """ The Exception class this would handle, or a status code. diff --git a/fastapi/background.py b/fastapi/background.py index 6d4a30d442..20803ba670 100644 --- a/fastapi/background.py +++ b/fastapi/background.py @@ -1,8 +1,8 @@ -from typing import Any, Callable +from typing import Annotated, Any, Callable from annotated_doc import Doc from starlette.background import BackgroundTasks as StarletteBackgroundTasks -from typing_extensions import Annotated, ParamSpec +from typing_extensions import ParamSpec P = ParamSpec("P") diff --git a/fastapi/concurrency.py b/fastapi/concurrency.py index 3202c70789..76a5a2eb12 100644 --- a/fastapi/concurrency.py +++ b/fastapi/concurrency.py @@ -1,5 +1,7 @@ +from collections.abc import AsyncGenerator +from contextlib import AbstractContextManager from contextlib import asynccontextmanager as asynccontextmanager -from typing import AsyncGenerator, ContextManager, TypeVar +from typing import TypeVar import anyio.to_thread from anyio import CapacityLimiter @@ -14,7 +16,7 @@ _T = TypeVar("_T") @asynccontextmanager async def contextmanager_in_threadpool( - cm: ContextManager[_T], + cm: AbstractContextManager[_T], ) -> AsyncGenerator[_T, None]: # blocking __exit__ from running waiting on a free thread # can create race conditions/deadlocks if the context manager itself diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index 8ad9aa11a6..b38a326def 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -1,11 +1,10 @@ +from collections.abc import Iterable from typing import ( + Annotated, Any, BinaryIO, Callable, - Dict, - Iterable, Optional, - Type, TypeVar, cast, ) @@ -23,7 +22,6 @@ from starlette.datastructures import Headers as Headers # noqa: F401 from starlette.datastructures import QueryParams as QueryParams # noqa: F401 from starlette.datastructures import State as State # noqa: F401 from starlette.datastructures import UploadFile as StarletteUploadFile -from typing_extensions import Annotated class UploadFile(StarletteUploadFile): @@ -138,11 +136,11 @@ class UploadFile(StarletteUploadFile): return await super().close() @classmethod - def __get_validators__(cls: Type["UploadFile"]) -> Iterable[Callable[..., Any]]: + def __get_validators__(cls: type["UploadFile"]) -> Iterable[Callable[..., Any]]: yield cls.validate @classmethod - def validate(cls: Type["UploadFile"], v: Any) -> Any: + def validate(cls: type["UploadFile"], v: Any) -> Any: if not isinstance(v, StarletteUploadFile): raise ValueError(f"Expected UploadFile, received: {type(v)}") return v @@ -155,7 +153,7 @@ class UploadFile(StarletteUploadFile): # TODO: remove when deprecating Pydantic v1 @classmethod - def __modify_schema__(cls, field_schema: Dict[str, Any]) -> None: + def __modify_schema__(cls, field_schema: dict[str, Any]) -> None: field_schema.update({"type": "string", "format": "binary"}) @classmethod @@ -166,7 +164,7 @@ class UploadFile(StarletteUploadFile): @classmethod def __get_pydantic_core_schema__( - cls, source: Type[Any], handler: Callable[[Any], CoreSchema] + cls, source: type[Any], handler: Callable[[Any], CoreSchema] ) -> CoreSchema: from ._compat.v2 import with_info_plain_validator_function diff --git a/fastapi/dependencies/models.py b/fastapi/dependencies/models.py index 6c4bf18b37..58392326d6 100644 --- a/fastapi/dependencies/models.py +++ b/fastapi/dependencies/models.py @@ -2,7 +2,7 @@ import inspect import sys from dataclasses import dataclass, field from functools import cached_property, partial -from typing import Any, Callable, List, Optional, Union +from typing import Any, Callable, Optional, Union from fastapi._compat import ModelField from fastapi.security.base import SecurityBase @@ -30,12 +30,12 @@ def _impartial(func: Callable[..., Any]) -> Callable[..., Any]: @dataclass class Dependant: - path_params: List[ModelField] = field(default_factory=list) - query_params: List[ModelField] = field(default_factory=list) - header_params: List[ModelField] = field(default_factory=list) - cookie_params: List[ModelField] = field(default_factory=list) - body_params: List[ModelField] = field(default_factory=list) - dependencies: List["Dependant"] = field(default_factory=list) + path_params: list[ModelField] = field(default_factory=list) + query_params: list[ModelField] = field(default_factory=list) + header_params: list[ModelField] = field(default_factory=list) + 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 @@ -44,14 +44,14 @@ class Dependant: 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 + own_oauth_scopes: Optional[list[str]] = None + parent_oauth_scopes: Optional[list[str]] = None use_cache: bool = True path: Optional[str] = None scope: Union[Literal["function", "request"], None] = None @cached_property - def oauth_scopes(self) -> List[str]: + def oauth_scopes(self) -> list[str]: scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else [] # This doesn't use a set to preserve order, just in case for scope in self.own_oauth_scopes or []: @@ -98,7 +98,7 @@ class Dependant: return unwrapped @cached_property - def _security_dependencies(self) -> List["Dependant"]: + def _security_dependencies(self) -> list["Dependant"]: security_deps = [dep for dep in self.dependencies if dep._is_security_scheme] return security_deps diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index cc7e55b4b0..3db006a918 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,21 +1,16 @@ import dataclasses import inspect import sys +from collections.abc import Coroutine, Mapping, Sequence from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy from dataclasses import dataclass from typing import ( + Annotated, Any, Callable, - Coroutine, - Dict, ForwardRef, - List, - Mapping, Optional, - Sequence, - Tuple, - Type, Union, cast, ) @@ -75,7 +70,7 @@ from starlette.datastructures import ( from starlette.requests import HTTPConnection, Request from starlette.responses import Response from starlette.websockets import WebSocket -from typing_extensions import Annotated, Literal, get_args, get_origin +from typing_extensions import Literal, get_args, get_origin from .. import temp_pydantic_v1_params @@ -125,7 +120,7 @@ def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> De assert callable(depends.dependency), ( "A parameter-less dependency must have a callable dependency" ) - own_oauth_scopes: List[str] = [] + own_oauth_scopes: list[str] = [] if isinstance(depends, params.Security) and depends.scopes: own_oauth_scopes.extend(depends.scopes) return get_dependant( @@ -140,8 +135,8 @@ def get_flat_dependant( dependant: Dependant, *, skip_repeats: bool = False, - visited: Optional[List[DependencyCacheKey]] = None, - parent_oauth_scopes: Optional[List[str]] = None, + visited: Optional[list[DependencyCacheKey]] = None, + parent_oauth_scopes: Optional[list[str]] = None, ) -> Dependant: if visited is None: visited = [] @@ -190,7 +185,7 @@ def get_flat_dependant( return flat_dependant -def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]: +def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]: if not fields: return fields first_field = fields[0] @@ -200,7 +195,7 @@ def _get_flat_fields_from_params(fields: List[ModelField]) -> List[ModelField]: return fields -def get_flat_params(dependant: Dependant) -> List[ModelField]: +def get_flat_params(dependant: Dependant) -> list[ModelField]: flat_dependant = get_flat_dependant(dependant, skip_repeats=True) path_params = _get_flat_fields_from_params(flat_dependant.path_params) query_params = _get_flat_fields_from_params(flat_dependant.query_params) @@ -239,7 +234,7 @@ def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: return typed_signature -def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any: +def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any: if isinstance(annotation, str): annotation = ForwardRef(annotation) annotation = evaluate_forwardref(annotation, globalns, globalns) @@ -265,8 +260,8 @@ 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, + own_oauth_scopes: Optional[list[str]] = None, + parent_oauth_scopes: Optional[list[str]] = None, use_cache: bool = True, scope: Union[Literal["function", "request"], None] = None, ) -> Dependant: @@ -303,7 +298,7 @@ def get_dependant( f'The dependency "{dependant.call.__name__}" has a scope of ' '"request", it cannot depend on dependencies with scope "function".' ) - sub_own_oauth_scopes: List[str] = [] + sub_own_oauth_scopes: list[str] = [] if isinstance(param_details.depends, params.Security): if param_details.depends.scopes: sub_own_oauth_scopes = list(param_details.depends.scopes) @@ -572,7 +567,7 @@ def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: async def _solve_generator( - *, dependant: Dependant, stack: AsyncExitStack, sub_values: Dict[str, Any] + *, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any] ) -> Any: assert dependant.call if dependant.is_async_gen_callable: @@ -584,22 +579,22 @@ async def _solve_generator( @dataclass class SolvedDependency: - values: Dict[str, Any] - errors: List[Any] + values: dict[str, Any] + errors: list[Any] background_tasks: Optional[StarletteBackgroundTasks] response: Response - dependency_cache: Dict[DependencyCacheKey, Any] + dependency_cache: dict[DependencyCacheKey, Any] async def solve_dependencies( *, request: Union[Request, WebSocket], dependant: Dependant, - body: Optional[Union[Dict[str, Any], FormData]] = None, + 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, + dependency_cache: Optional[dict[DependencyCacheKey, Any]] = 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, @@ -613,8 +608,8 @@ async def solve_dependencies( assert isinstance(function_astack, AsyncExitStack), ( "fastapi_function_astack not found in request scope" ) - values: Dict[str, Any] = {} - errors: List[Any] = [] + values: dict[str, Any] = {} + errors: list[Any] = [] if response is None: response = Response() del response.headers["content-length"] @@ -732,8 +727,8 @@ async def solve_dependencies( def _validate_value_with_model_field( - *, field: ModelField, value: Any, values: Dict[str, Any], loc: Tuple[str, ...] -) -> Tuple[Any, List[Any]]: + *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...] +) -> tuple[Any, list[Any]]: if value is None: if field.required: return None, [get_missing_field_error(loc=loc)] @@ -776,9 +771,9 @@ def _get_multidict_value( def request_params_to_args( fields: Sequence[ModelField], received_params: Union[Mapping[str, Any], QueryParams, Headers], -) -> Tuple[Dict[str, Any], List[Any]]: - values: Dict[str, Any] = {} - errors: List[Dict[str, Any]] = [] +) -> tuple[dict[str, Any], list[Any]]: + values: dict[str, Any] = {} + errors: list[dict[str, Any]] = [] if not fields: return values, errors @@ -796,7 +791,7 @@ def request_params_to_args( first_field.field_info, "convert_underscores", True ) - params_to_process: Dict[str, Any] = {} + params_to_process: dict[str, Any] = {} processed_keys = set() @@ -833,7 +828,7 @@ def request_params_to_args( assert isinstance(field_info, (params.Param, temp_pydantic_v1_params.Param)), ( "Params must be subclasses of Param" ) - loc: Tuple[str, ...] = (field_info.in_.value,) + loc: tuple[str, ...] = (field_info.in_.value,) v_, errors_ = _validate_value_with_model_field( field=first_field, value=params_to_process, values=values, loc=loc ) @@ -875,7 +870,7 @@ def is_union_of_base_models(field_type: Any) -> bool: return True -def _should_embed_body_fields(fields: List[ModelField]) -> bool: +def _should_embed_body_fields(fields: list[ModelField]) -> bool: if not fields: return False # More than one dependency could have the same field, it would show up as multiple @@ -900,9 +895,9 @@ def _should_embed_body_fields(fields: List[ModelField]) -> bool: async def _extract_form_body( - body_fields: List[ModelField], + body_fields: list[ModelField], received_body: FormData, -) -> Dict[str, Any]: +) -> dict[str, Any]: values = {} for field in body_fields: @@ -920,8 +915,8 @@ async def _extract_form_body( and value_is_sequence(value) ): # For types - assert isinstance(value, sequence_types) # type: ignore[arg-type] - results: List[Union[bytes, str]] = [] + assert isinstance(value, sequence_types) + results: list[Union[bytes, str]] = [] async def process_fn( fn: Callable[[], Coroutine[Any, Any, Any]], @@ -947,18 +942,18 @@ async def _extract_form_body( async def request_body_to_args( - body_fields: List[ModelField], - received_body: Optional[Union[Dict[str, Any], FormData]], + body_fields: list[ModelField], + received_body: Optional[Union[dict[str, Any], FormData]], embed_body_fields: bool, -) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: - values: Dict[str, Any] = {} - errors: List[Dict[str, Any]] = [] +) -> tuple[dict[str, Any], list[dict[str, Any]]]: + values: dict[str, Any] = {} + errors: list[dict[str, Any]] = [] assert body_fields, "request_body_to_args() should be called with fields" single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields first_field = body_fields[0] body_to_process = received_body - fields_to_extract: List[ModelField] = body_fields + fields_to_extract: list[ModelField] = body_fields if ( single_not_embedded_field @@ -971,7 +966,7 @@ async def request_body_to_args( body_to_process = await _extract_form_body(fields_to_extract, received_body) if single_not_embedded_field: - loc: Tuple[str, ...] = ("body",) + loc: tuple[str, ...] = ("body",) v_, errors_ = _validate_value_with_model_field( field=first_field, value=body_to_process, values=values, loc=loc ) @@ -1019,19 +1014,19 @@ def get_body_field( fields=flat_dependant.body_params, model_name=model_name ) required = any(True for f in flat_dependant.body_params if f.required) - BodyFieldInfo_kwargs: Dict[str, Any] = { + BodyFieldInfo_kwargs: dict[str, Any] = { "annotation": BodyModel, "alias": "body", } if not required: BodyFieldInfo_kwargs["default"] = None if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params): - BodyFieldInfo: Type[params.Body] = params.File + BodyFieldInfo: type[params.Body] = params.File elif any( isinstance(f.field_info, temp_pydantic_v1_params.File) for f in flat_dependant.body_params ): - BodyFieldInfo: Type[temp_pydantic_v1_params.Body] = temp_pydantic_v1_params.File # type: ignore[no-redef] + BodyFieldInfo: type[temp_pydantic_v1_params.Body] = temp_pydantic_v1_params.File # type: ignore[no-redef] elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): BodyFieldInfo = params.Form elif any( diff --git a/fastapi/encoders.py b/fastapi/encoders.py index 7939510895..cbeeee4559 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -14,7 +14,7 @@ from ipaddress import ( from pathlib import Path, PurePath from re import Pattern from types import GeneratorType -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Annotated, Any, Callable, Optional, Union from uuid import UUID from annotated_doc import Doc @@ -24,7 +24,6 @@ from pydantic import BaseModel from pydantic.color import Color from pydantic.networks import AnyUrl, NameEmail from pydantic.types import SecretBytes, SecretStr -from typing_extensions import Annotated from ._compat import Url, _is_undefined, _model_dump @@ -61,7 +60,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: return float(dec_value) -ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { +ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { bytes: lambda o: o.decode(), Color: str, may_v1.Color: str, @@ -98,9 +97,9 @@ ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = { def generate_encoders_by_class_tuples( - type_encoder_map: Dict[Any, Callable[[Any], Any]], -) -> Dict[Callable[[Any], Any], Tuple[Any, ...]]: - encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict( + type_encoder_map: dict[Any, Callable[[Any], Any]], +) -> dict[Callable[[Any], Any], tuple[Any, ...]]: + encoders_by_class_tuples: dict[Callable[[Any], Any], tuple[Any, ...]] = defaultdict( tuple ) for type_, encoder in type_encoder_map.items(): @@ -180,7 +179,7 @@ def jsonable_encoder( ), ] = False, custom_encoder: Annotated[ - Optional[Dict[Any, Callable[[Any], Any]]], + Optional[dict[Any, Callable[[Any], Any]]], Doc( """ Pydantic's `custom_encoder` parameter, passed to Pydantic models to define @@ -227,7 +226,7 @@ def jsonable_encoder( exclude = set(exclude) if isinstance(obj, (BaseModel, may_v1.BaseModel)): # TODO: remove when deprecating Pydantic v1 - encoders: Dict[Any, Any] = {} + encoders: dict[Any, Any] = {} if isinstance(obj, may_v1.BaseModel): encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined] if custom_encoder: @@ -336,7 +335,7 @@ def jsonable_encoder( try: data = dict(obj) except Exception as e: - errors: List[Exception] = [] + errors: list[Exception] = [] errors.append(e) try: data = vars(obj) diff --git a/fastapi/exceptions.py b/fastapi/exceptions.py index a46e823506..8e0c559023 100644 --- a/fastapi/exceptions.py +++ b/fastapi/exceptions.py @@ -1,10 +1,10 @@ -from typing import Any, Dict, Optional, Sequence, Type, TypedDict, Union +from collections.abc import Sequence +from typing import Annotated, Any, Optional, TypedDict, Union from annotated_doc import Doc from pydantic import BaseModel, create_model from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import WebSocketException as StarletteWebSocketException -from typing_extensions import Annotated class EndpointContext(TypedDict, total=False): @@ -62,7 +62,7 @@ class HTTPException(StarletteHTTPException): ), ] = None, headers: Annotated[ - Optional[Dict[str, str]], + Optional[dict[str, str]], Doc( """ Any headers to send to the client in the response. @@ -144,8 +144,8 @@ class WebSocketException(StarletteWebSocketException): super().__init__(code=code, reason=reason) -RequestErrorModel: Type[BaseModel] = create_model("Request") -WebSocketErrorModel: Type[BaseModel] = create_model("WebSocket") +RequestErrorModel: type[BaseModel] = create_model("Request") +WebSocketErrorModel: type[BaseModel] = create_model("WebSocket") class FastAPIError(RuntimeError): diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index 74b23a3706..82380f85d9 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -1,13 +1,12 @@ import json -from typing import Any, Dict, Optional +from typing import Annotated, Any, Optional from annotated_doc import Doc from fastapi.encoders import jsonable_encoder from starlette.responses import HTMLResponse -from typing_extensions import Annotated swagger_ui_default_parameters: Annotated[ - Dict[str, Any], + dict[str, Any], Doc( """ Default configurations for Swagger UI. @@ -82,7 +81,7 @@ def get_swagger_ui_html( ), ] = None, init_oauth: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ A dictionary with Swagger UI OAuth2 initialization configurations. @@ -90,7 +89,7 @@ def get_swagger_ui_html( ), ] = None, swagger_ui_parameters: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Configuration parameters for Swagger UI. diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 81d276aed6..7aa80f5cb0 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -1,5 +1,6 @@ +from collections.abc import Iterable from enum import Enum -from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Type, Union +from typing import Annotated, Any, Callable, Optional, Union from fastapi._compat import ( PYDANTIC_V2, @@ -11,7 +12,7 @@ from fastapi._compat import ( ) from fastapi.logger import logger from pydantic import AnyUrl, BaseModel, Field -from typing_extensions import Annotated, Literal, TypedDict +from typing_extensions import Literal, TypedDict from typing_extensions import deprecated as typing_deprecated try: @@ -50,7 +51,7 @@ except ImportError: # pragma: no cover @classmethod def __get_pydantic_core_schema__( - cls, source: Type[Any], handler: Callable[[Any], CoreSchema] + cls, source: type[Any], handler: Callable[[Any], CoreSchema] ) -> CoreSchema: return with_info_plain_validator_function(cls._validate) @@ -88,7 +89,7 @@ class Info(BaseModelWithConfig): class ServerVariable(BaseModelWithConfig): - enum: Annotated[Optional[List[str]], Field(min_length=1)] = None + enum: Annotated[Optional[list[str]], Field(min_length=1)] = None default: str description: Optional[str] = None @@ -96,7 +97,7 @@ class ServerVariable(BaseModelWithConfig): class Server(BaseModelWithConfig): url: Union[AnyUrl, str] description: Optional[str] = None - variables: Optional[Dict[str, ServerVariable]] = None + variables: Optional[dict[str, ServerVariable]] = None class Reference(BaseModel): @@ -105,7 +106,7 @@ class Reference(BaseModel): class Discriminator(BaseModel): propertyName: str - mapping: Optional[Dict[str, str]] = None + mapping: Optional[dict[str, str]] = None class XML(BaseModelWithConfig): @@ -137,34 +138,34 @@ class Schema(BaseModelWithConfig): 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") + defs: Optional[dict[str, "SchemaOrBool"]] = Field(default=None, alias="$defs") comment: Optional[str] = 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: Optional[list["SchemaOrBool"]] = None + anyOf: Optional[list["SchemaOrBool"]] = None + oneOf: Optional[list["SchemaOrBool"]] = 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: Optional[dict[str, "SchemaOrBool"]] = None + prefixItems: Optional[list["SchemaOrBool"]] = None # TODO: uncomment and remove below when deprecating Pydantic v1 # It generates a list of schemas for tuples, before prefixItems was available # items: Optional["SchemaOrBool"] = None - items: Optional[Union["SchemaOrBool", List["SchemaOrBool"]]] = None + items: Optional[Union["SchemaOrBool", list["SchemaOrBool"]]] = None contains: Optional["SchemaOrBool"] = None - properties: Optional[Dict[str, "SchemaOrBool"]] = None - patternProperties: Optional[Dict[str, "SchemaOrBool"]] = None + properties: Optional[dict[str, "SchemaOrBool"]] = None + patternProperties: Optional[dict[str, "SchemaOrBool"]] = 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 + 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 @@ -181,8 +182,8 @@ class Schema(BaseModelWithConfig): 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 + required: Optional[list[str]] = None + dependentRequired: Optional[dict[str, set[str]]] = 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 @@ -199,7 +200,7 @@ class Schema(BaseModelWithConfig): deprecated: Optional[bool] = None readOnly: Optional[bool] = None writeOnly: Optional[bool] = None - examples: Optional[List[Any]] = None + examples: Optional[list[Any]] = 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 @@ -243,7 +244,7 @@ class ParameterInType(Enum): class Encoding(BaseModelWithConfig): contentType: Optional[str] = None - headers: Optional[Dict[str, Union["Header", Reference]]] = None + headers: Optional[dict[str, Union["Header", Reference]]] = None style: Optional[str] = None explode: Optional[bool] = None allowReserved: Optional[bool] = None @@ -252,8 +253,8 @@ class Encoding(BaseModelWithConfig): 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 + examples: Optional[dict[str, Union[Example, Reference]]] = None + encoding: Optional[dict[str, Encoding]] = None class ParameterBase(BaseModelWithConfig): @@ -266,9 +267,9 @@ class ParameterBase(BaseModelWithConfig): 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 + examples: Optional[dict[str, Union[Example, Reference]]] = None # Serialization rules for more complex scenarios - content: Optional[Dict[str, MediaType]] = None + content: Optional[dict[str, MediaType]] = None class Parameter(ParameterBase): @@ -282,14 +283,14 @@ class Header(ParameterBase): class RequestBody(BaseModelWithConfig): description: Optional[str] = None - content: Dict[str, MediaType] + content: dict[str, MediaType] required: Optional[bool] = None class Link(BaseModelWithConfig): operationRef: Optional[str] = None operationId: Optional[str] = None - parameters: Optional[Dict[str, Union[Any, str]]] = None + parameters: Optional[dict[str, Union[Any, str]]] = None requestBody: Optional[Union[Any, str]] = None description: Optional[str] = None server: Optional[Server] = None @@ -297,25 +298,25 @@ class Link(BaseModelWithConfig): 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: Optional[dict[str, Union[Header, Reference]]] = None + content: Optional[dict[str, MediaType]] = None + links: Optional[dict[str, Union[Link, Reference]]] = None class Operation(BaseModelWithConfig): - tags: Optional[List[str]] = None + 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 + parameters: Optional[list[Union[Parameter, Reference]]] = None requestBody: Optional[Union[RequestBody, Reference]] = None # Using Any for Specification Extensions - responses: Optional[Dict[str, Union[Response, Any]]] = None - callbacks: Optional[Dict[str, Union[Dict[str, "PathItem"], Reference]]] = None + 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 + security: Optional[list[dict[str, list[str]]]] = None + servers: Optional[list[Server]] = None class PathItem(BaseModelWithConfig): @@ -330,8 +331,8 @@ class PathItem(BaseModelWithConfig): head: Optional[Operation] = None patch: Optional[Operation] = None trace: Optional[Operation] = None - servers: Optional[List[Server]] = None - parameters: Optional[List[Union[Parameter, Reference]]] = None + servers: Optional[list[Server]] = None + parameters: Optional[list[Union[Parameter, Reference]]] = None class SecuritySchemeType(Enum): @@ -370,7 +371,7 @@ class HTTPBearer(HTTPBase): class OAuthFlow(BaseModelWithConfig): refreshUrl: Optional[str] = None - scopes: Dict[str, str] = {} + scopes: dict[str, str] = {} class OAuthFlowImplicit(OAuthFlow): @@ -413,17 +414,17 @@ SecurityScheme = Union[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: 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 # 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: Optional[dict[str, Union[dict[str, PathItem], Reference, Any]]] = None + pathItems: Optional[dict[str, Union[PathItem, Reference]]] = None class Tag(BaseModelWithConfig): @@ -436,13 +437,13 @@ class OpenAPI(BaseModelWithConfig): openapi: str info: Info jsonSchemaDialect: Optional[str] = None - servers: Optional[List[Server]] = None + servers: Optional[list[Server]] = None # Using Any for Specification Extensions - paths: Optional[Dict[str, Union[PathItem, Any]]] = None - webhooks: Optional[Dict[str, Union[PathItem, Reference]]] = None + 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 + security: Optional[list[dict[str, list[str]]]] = None + tags: Optional[list[Tag]] = None externalDocs: Optional[ExternalDocumentation] = None diff --git a/fastapi/openapi/utils.py b/fastapi/openapi/utils.py index 9fe2044f26..a99d4188e7 100644 --- a/fastapi/openapi/utils.py +++ b/fastapi/openapi/utils.py @@ -1,7 +1,8 @@ import http.client import inspect import warnings -from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast +from collections.abc import Sequence +from typing import Any, Optional, Union, cast from fastapi import routing from fastapi._compat import ( @@ -66,7 +67,7 @@ validation_error_response_definition = { }, } -status_code_ranges: Dict[str, str] = { +status_code_ranges: dict[str, str] = { "1XX": "Information", "2XX": "Success", "3XX": "Redirection", @@ -78,10 +79,10 @@ status_code_ranges: Dict[str, str] = { def get_openapi_security_definitions( flat_dependant: Dependant, -) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: +) -> tuple[dict[str, Any], list[dict[str, Any]]]: security_definitions = {} # Use a dict to merge scopes for same security scheme - operation_security_dict: Dict[str, List[str]] = {} + operation_security_dict: dict[str, list[str]] = {} for security_dependency in flat_dependant._security_dependencies: security_definition = jsonable_encoder( security_dependency._security_scheme.model, @@ -106,11 +107,11 @@ def _get_openapi_operation_parameters( *, dependant: Dependant, model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], separate_input_output_schemas: bool = True, -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: parameters = [] flat_dependant = get_flat_dependant(dependant, skip_repeats=True) path_params = _get_flat_fields_from_params(flat_dependant.path_params) @@ -179,11 +180,11 @@ def get_openapi_operation_request_body( *, body_field: Optional[ModelField], model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], separate_input_output_schemas: bool = True, -) -> Optional[Dict[str, Any]]: +) -> Optional[dict[str, Any]]: if not body_field: return None assert _is_model_field(body_field) @@ -196,10 +197,10 @@ def get_openapi_operation_request_body( field_info = cast(Body, body_field.field_info) request_media_type = field_info.media_type required = body_field.required - request_body_oai: Dict[str, Any] = {} + request_body_oai: dict[str, Any] = {} if required: request_body_oai["required"] = required - request_media_content: Dict[str, Any] = {"schema": body_schema} + request_media_content: dict[str, Any] = {"schema": body_schema} if field_info.openapi_examples: request_media_content["examples"] = jsonable_encoder( field_info.openapi_examples @@ -232,9 +233,9 @@ def generate_operation_summary(*, route: routing.APIRoute, method: str) -> str: def get_openapi_operation_metadata( - *, route: routing.APIRoute, method: str, operation_ids: Set[str] -) -> Dict[str, Any]: - operation: Dict[str, Any] = {} + *, route: routing.APIRoute, method: str, operation_ids: set[str] +) -> dict[str, Any]: + operation: dict[str, Any] = {} if route.tags: operation["tags"] = route.tags operation["summary"] = generate_operation_summary(route=route, method=method) @@ -260,19 +261,19 @@ def get_openapi_operation_metadata( def get_openapi_path( *, route: routing.APIRoute, - operation_ids: Set[str], + operation_ids: set[str], model_name_map: ModelNameMap, - field_mapping: Dict[ - Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue + field_mapping: dict[ + tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue ], separate_input_output_schemas: bool = True, -) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]: +) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: path = {} - security_schemes: Dict[str, Any] = {} - definitions: Dict[str, Any] = {} + security_schemes: dict[str, Any] = {} + definitions: dict[str, Any] = {} assert route.methods is not None, "Methods must be a list" if isinstance(route.response_class, DefaultPlaceholder): - current_response_class: Type[Response] = route.response_class.value + current_response_class: type[Response] = route.response_class.value else: current_response_class = route.response_class assert current_response_class, "A response class is needed to generate OpenAPI" @@ -282,7 +283,7 @@ def get_openapi_path( operation = get_openapi_operation_metadata( route=route, method=method, operation_ids=operation_ids ) - parameters: List[Dict[str, Any]] = [] + parameters: list[dict[str, Any]] = [] flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True) security_definitions, operation_security = get_openapi_security_definitions( flat_dependant=flat_dependant @@ -390,7 +391,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: Optional[dict[str, Any]] = None if field: additional_field_schema = get_schema_from_model_field( field=field, @@ -445,11 +446,11 @@ def get_openapi_path( def get_fields_from_routes( routes: Sequence[BaseRoute], -) -> List[ModelField]: - body_fields_from_routes: List[ModelField] = [] - responses_from_routes: List[ModelField] = [] - request_fields_from_routes: List[ModelField] = [] - callback_flat_models: List[ModelField] = [] +) -> list[ModelField]: + body_fields_from_routes: list[ModelField] = [] + responses_from_routes: list[ModelField] = [] + request_fields_from_routes: list[ModelField] = [] + callback_flat_models: list[ModelField] = [] for route in routes: if getattr(route, "include_in_schema", None) and isinstance( route, routing.APIRoute @@ -483,15 +484,15 @@ def get_openapi( description: Optional[str] = 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, + 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, + contact: Optional[dict[str, Union[str, Any]]] = None, + license_info: Optional[dict[str, Union[str, Any]]] = None, separate_input_output_schemas: bool = True, - external_docs: Optional[Dict[str, Any]] = None, -) -> Dict[str, Any]: - info: Dict[str, Any] = {"title": title, "version": version} + external_docs: Optional[dict[str, Any]] = None, +) -> dict[str, Any]: + info: dict[str, Any] = {"title": title, "version": version} if summary: info["summary"] = summary if description: @@ -502,13 +503,13 @@ def get_openapi( info["contact"] = contact if license_info: info["license"] = license_info - output: Dict[str, Any] = {"openapi": openapi_version, "info": info} + output: dict[str, Any] = {"openapi": openapi_version, "info": info} if servers: output["servers"] = servers - components: Dict[str, Dict[str, Any]] = {} - paths: Dict[str, Dict[str, Any]] = {} - webhook_paths: Dict[str, Dict[str, Any]] = {} - operation_ids: Set[str] = set() + components: dict[str, dict[str, Any]] = {} + paths: dict[str, dict[str, Any]] = {} + webhook_paths: dict[str, dict[str, Any]] = {} + operation_ids: set[str] = set() all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or [])) model_name_map = get_compat_model_name_map(all_fields) field_mapping, definitions = get_definitions( diff --git a/fastapi/param_functions.py b/fastapi/param_functions.py index e32f755933..844542594b 100644 --- a/fastapi/param_functions.py +++ b/fastapi/param_functions.py @@ -1,10 +1,11 @@ -from typing import Any, Callable, Dict, List, Optional, Sequence, Union +from collections.abc import Sequence +from typing import Annotated, Any, Callable, Optional, Union from annotated_doc import Doc from fastapi import params from fastapi._compat import Undefined from fastapi.openapi.models import Example -from typing_extensions import Annotated, Literal, deprecated +from typing_extensions import Literal, deprecated _Unset: Any = Undefined @@ -209,7 +210,7 @@ def Path( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -224,7 +225,7 @@ def Path( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -262,7 +263,7 @@ def Path( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. @@ -534,7 +535,7 @@ def Query( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -549,7 +550,7 @@ def Query( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -587,7 +588,7 @@ def Query( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. @@ -849,7 +850,7 @@ def Header( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -864,7 +865,7 @@ def Header( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -902,7 +903,7 @@ def Header( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. @@ -1154,7 +1155,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -1169,7 +1170,7 @@ def Cookie( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -1207,7 +1208,7 @@ def Cookie( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. @@ -1481,7 +1482,7 @@ def Body( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -1496,7 +1497,7 @@ def Body( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -1534,7 +1535,7 @@ def Body( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. @@ -1796,7 +1797,7 @@ def Form( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -1811,7 +1812,7 @@ def Form( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -1849,7 +1850,7 @@ def Form( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. @@ -2110,7 +2111,7 @@ def File( # noqa: N802 ), ] = _Unset, examples: Annotated[ - Optional[List[Any]], + Optional[list[Any]], Doc( """ Example values for this field. @@ -2125,7 +2126,7 @@ def File( # noqa: N802 ), ] = _Unset, openapi_examples: Annotated[ - Optional[Dict[str, Example]], + Optional[dict[str, Example]], Doc( """ OpenAPI-specific examples. @@ -2163,7 +2164,7 @@ def File( # noqa: N802 ), ] = True, json_schema_extra: Annotated[ - Union[Dict[str, Any], None], + Union[dict[str, Any], None], Doc( """ Any additional JSON schema data. diff --git a/fastapi/params.py b/fastapi/params.py index b6d0f08e31..4990d0e70e 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -1,11 +1,12 @@ import warnings +from collections.abc import Sequence from dataclasses import dataclass from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Sequence, Union +from typing import Annotated, Any, Callable, Optional, Union from fastapi.openapi.models import Example from pydantic.fields import FieldInfo -from typing_extensions import Annotated, Literal, deprecated +from typing_extensions import Literal, deprecated from ._compat import ( PYDANTIC_V2, @@ -59,7 +60,7 @@ class Param(FieldInfo): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -67,10 +68,10 @@ class Param(FieldInfo): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): if example is not _Unset: @@ -177,7 +178,7 @@ class Path(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -185,10 +186,10 @@ class Path(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): assert default is ..., "Path parameters cannot have a default value" @@ -263,7 +264,7 @@ class Query(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -271,10 +272,10 @@ class Query(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( @@ -348,7 +349,7 @@ class Header(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -356,10 +357,10 @@ class Header(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): self.convert_underscores = convert_underscores @@ -433,7 +434,7 @@ class Cookie(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -441,10 +442,10 @@ class Cookie(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( @@ -517,7 +518,7 @@ class Body(FieldInfo): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -525,10 +526,10 @@ class Body(FieldInfo): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): self.embed = embed @@ -637,7 +638,7 @@ class Form(Body): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -645,10 +646,10 @@ class Form(Body): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( @@ -721,7 +722,7 @@ class File(Form): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -729,10 +730,10 @@ class File(Form): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( diff --git a/fastapi/routing.py b/fastapi/routing.py index 9be2b44bc1..fa6904a6b6 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -3,23 +3,21 @@ import email.message import functools import inspect import json +from collections.abc import ( + AsyncIterator, + Awaitable, + Collection, + Coroutine, + Mapping, + Sequence, +) from contextlib import AsyncExitStack, asynccontextmanager from enum import Enum, IntEnum from typing import ( + Annotated, Any, - AsyncIterator, - Awaitable, Callable, - Collection, - Coroutine, - Dict, - List, - Mapping, Optional, - Sequence, - Set, - Tuple, - Type, Union, ) @@ -77,7 +75,7 @@ from starlette.routing import ( from starlette.routing import Mount as Mount # noqa from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send from starlette.websockets import WebSocket -from typing_extensions import Annotated, deprecated +from typing_extensions import deprecated # Copy of starlette.routing.request_response modified to include the @@ -214,7 +212,7 @@ def _merge_lifespan_context( # Cache for endpoint context to avoid re-extracting on every request -_endpoint_context_cache: Dict[int, EndpointContext] = {} +_endpoint_context_cache: dict[int, EndpointContext] = {} def _extract_endpoint_context(func: Any) -> EndpointContext: @@ -306,7 +304,7 @@ async def serialize_response( async def run_endpoint_function( - *, dependant: Dependant, values: Dict[str, Any], is_coroutine: bool + *, dependant: Dependant, values: dict[str, Any], is_coroutine: bool ) -> Any: # Only called by get_request_handler. Has been split into its own function to # facilitate profiling endpoints, since inner functions are harder to profile. @@ -322,7 +320,7 @@ def get_request_handler( dependant: Dependant, body_field: Optional[ModelField] = None, status_code: Optional[int] = None, - response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse), + response_class: Union[type[Response], DefaultPlaceholder] = Default(JSONResponse), response_field: Optional[ModelField] = None, response_model_include: Optional[IncEx] = None, response_model_exclude: Optional[IncEx] = None, @@ -339,7 +337,7 @@ def get_request_handler( body_field.field_info, (params.Form, temp_pydantic_v1_params.Form) ) if isinstance(response_class, DefaultPlaceholder): - actual_response_class: Type[Response] = response_class.value + actual_response_class: type[Response] = response_class.value else: actual_response_class = response_class @@ -412,7 +410,7 @@ def get_request_handler( raise http_error from e # Solve dependencies and run path operation function, auto-closing dependencies - errors: List[Any] = [] + errors: list[Any] = [] async_exit_stack = request.scope.get("fastapi_inner_astack") assert isinstance(async_exit_stack, AsyncExitStack), ( "fastapi_inner_astack not found in request scope" @@ -437,7 +435,7 @@ def get_request_handler( raw_response.background = solved_result.background_tasks response = raw_response else: - response_args: Dict[str, Any] = { + response_args: dict[str, Any] = { "background": solved_result.background_tasks } # If status_code was set, use it, otherwise use the default from the @@ -550,7 +548,7 @@ class APIWebSocketRoute(routing.WebSocketRoute): ) ) - def matches(self, scope: Scope) -> Tuple[Match, Scope]: + def matches(self, scope: Scope) -> tuple[Match, Scope]: match, child_scope = super().matches(scope) if match != Match.NONE: child_scope["route"] = self @@ -565,15 +563,15 @@ class APIRoute(routing.Route): *, response_model: Any = Default(None), status_code: Optional[int] = None, - tags: Optional[List[Union[str, Enum]]] = None, + tags: Optional[list[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", - responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, deprecated: Optional[bool] = None, name: Optional[str] = None, - methods: Optional[Union[Set[str], List[str]]] = None, + methods: Optional[Union[set[str], list[str]]] = None, operation_id: Optional[str] = None, response_model_include: Optional[IncEx] = None, response_model_exclude: Optional[IncEx] = None, @@ -582,12 +580,12 @@ class APIRoute(routing.Route): response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[Type[Response], DefaultPlaceholder] = Default( + response_class: Union[type[Response], DefaultPlaceholder] = Default( JSONResponse ), dependency_overrides_provider: Optional[Any] = None, - callbacks: Optional[List[BaseRoute]] = None, - openapi_extra: Optional[Dict[str, Any]] = None, + callbacks: Optional[list[BaseRoute]] = None, + openapi_extra: Optional[dict[str, Any]] = None, generate_unique_id_function: Union[ Callable[["APIRoute"], str], DefaultPlaceholder ] = Default(generate_unique_id), @@ -623,7 +621,7 @@ class APIRoute(routing.Route): self.path_regex, self.path_format, self.param_convertors = compile_path(path) if methods is None: methods = ["GET"] - self.methods: Set[str] = {method.upper() for method in methods} + self.methods: set[str] = {method.upper() for method in methods} if isinstance(generate_unique_id_function, DefaultPlaceholder): current_generate_unique_id: Callable[[APIRoute], str] = ( generate_unique_id_function.value @@ -678,7 +676,7 @@ class APIRoute(routing.Route): ) response_fields[additional_status_code] = response_field if response_fields: - self.response_fields: Dict[Union[int, str], ModelField] = response_fields + self.response_fields: dict[Union[int, str], ModelField] = response_fields else: self.response_fields = {} @@ -719,7 +717,7 @@ class APIRoute(routing.Route): embed_body_fields=self._embed_body_fields, ) - def matches(self, scope: Scope) -> Tuple[Match, Scope]: + def matches(self, scope: Scope) -> tuple[Match, Scope]: match, child_scope = super().matches(scope) if match != Match.NONE: child_scope["route"] = self @@ -758,7 +756,7 @@ class APIRouter(routing.Router): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to all the *path operations* in this @@ -784,7 +782,7 @@ class APIRouter(routing.Router): ), ] = None, default_response_class: Annotated[ - Type[Response], + type[Response], Doc( """ The default response class to be used. @@ -795,7 +793,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses to be shown in OpenAPI. @@ -811,7 +809,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ OpenAPI callbacks that should apply to all *path operations* in this @@ -825,7 +823,7 @@ class APIRouter(routing.Router): ), ] = None, routes: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ **Note**: you probably shouldn't use this parameter, it is inherited @@ -876,7 +874,7 @@ class APIRouter(routing.Router): ), ] = None, route_class: Annotated[ - Type[APIRoute], + type[APIRoute], Doc( """ Custom route (*path operation*) class to be used by this router. @@ -982,7 +980,7 @@ class APIRouter(routing.Router): "A path prefix must not end with '/', as the routes will start with '/'" ) self.prefix = prefix - self.tags: List[Union[str, Enum]] = tags or [] + self.tags: list[Union[str, Enum]] = tags or [] self.dependencies = list(dependencies or []) self.deprecated = deprecated self.include_in_schema = include_in_schema @@ -1019,14 +1017,14 @@ class APIRouter(routing.Router): *, response_model: Any = Default(None), status_code: Optional[int] = None, - tags: Optional[List[Union[str, Enum]]] = None, + tags: Optional[list[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", - responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, deprecated: Optional[bool] = None, - methods: Optional[Union[Set[str], List[str]]] = None, + methods: Optional[Union[set[str], list[str]]] = None, operation_id: Optional[str] = None, response_model_include: Optional[IncEx] = None, response_model_exclude: Optional[IncEx] = None, @@ -1035,13 +1033,13 @@ class APIRouter(routing.Router): response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Union[Type[Response], DefaultPlaceholder] = Default( + response_class: Union[type[Response], DefaultPlaceholder] = Default( JSONResponse ), name: Optional[str] = None, - route_class_override: Optional[Type[APIRoute]] = None, - callbacks: Optional[List[BaseRoute]] = None, - openapi_extra: Optional[Dict[str, Any]] = None, + route_class_override: Optional[type[APIRoute]] = None, + callbacks: Optional[list[BaseRoute]] = None, + openapi_extra: Optional[dict[str, Any]] = None, generate_unique_id_function: Union[ Callable[[APIRoute], str], DefaultPlaceholder ] = Default(generate_unique_id), @@ -1100,14 +1098,14 @@ class APIRouter(routing.Router): *, response_model: Any = Default(None), status_code: Optional[int] = None, - tags: Optional[List[Union[str, Enum]]] = None, + tags: Optional[list[Union[str, Enum]]] = None, dependencies: Optional[Sequence[params.Depends]] = None, summary: Optional[str] = None, description: Optional[str] = None, response_description: str = "Successful Response", - responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None, + responses: Optional[dict[Union[int, str], dict[str, Any]]] = None, deprecated: Optional[bool] = None, - methods: Optional[List[str]] = None, + methods: Optional[list[str]] = None, operation_id: Optional[str] = None, response_model_include: Optional[IncEx] = None, response_model_exclude: Optional[IncEx] = None, @@ -1116,10 +1114,10 @@ class APIRouter(routing.Router): response_model_exclude_defaults: bool = False, response_model_exclude_none: bool = False, include_in_schema: bool = True, - response_class: Type[Response] = Default(JSONResponse), + response_class: type[Response] = Default(JSONResponse), name: Optional[str] = None, - callbacks: Optional[List[BaseRoute]] = None, - openapi_extra: Optional[Dict[str, Any]] = None, + callbacks: Optional[list[BaseRoute]] = None, + openapi_extra: Optional[dict[str, Any]] = None, generate_unique_id_function: Callable[[APIRoute], str] = Default( generate_unique_id ), @@ -1259,7 +1257,7 @@ class APIRouter(routing.Router): *, prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to all the *path operations* in this @@ -1285,7 +1283,7 @@ class APIRouter(routing.Router): ), ] = None, default_response_class: Annotated[ - Type[Response], + type[Response], Doc( """ The default response class to be used. @@ -1296,7 +1294,7 @@ class APIRouter(routing.Router): ), ] = Default(JSONResponse), responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses to be shown in OpenAPI. @@ -1312,7 +1310,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ OpenAPI callbacks that should apply to all *path operations* in this @@ -1417,7 +1415,7 @@ class APIRouter(routing.Router): current_tags.extend(tags) if route.tags: current_tags.extend(route.tags) - current_dependencies: List[params.Depends] = [] + current_dependencies: list[params.Depends] = [] if dependencies: current_dependencies.extend(dependencies) if route.dependencies: @@ -1558,7 +1556,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -1624,7 +1622,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -1765,7 +1763,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -1786,7 +1784,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -1802,7 +1800,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -1935,7 +1933,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -2001,7 +1999,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -2142,7 +2140,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -2163,7 +2161,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2179,7 +2177,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2317,7 +2315,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -2383,7 +2381,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -2524,7 +2522,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -2545,7 +2543,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2561,7 +2559,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -2699,7 +2697,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -2765,7 +2763,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -2906,7 +2904,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -2927,7 +2925,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -2943,7 +2941,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3076,7 +3074,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -3142,7 +3140,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -3283,7 +3281,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -3304,7 +3302,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3320,7 +3318,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3453,7 +3451,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -3519,7 +3517,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -3660,7 +3658,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -3681,7 +3679,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -3697,7 +3695,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -3835,7 +3833,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -3901,7 +3899,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -4042,7 +4040,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -4063,7 +4061,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4079,7 +4077,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path @@ -4217,7 +4215,7 @@ class APIRouter(routing.Router): ), ] = None, tags: Annotated[ - Optional[List[Union[str, Enum]]], + Optional[list[Union[str, Enum]]], Doc( """ A list of tags to be applied to the *path operation*. @@ -4283,7 +4281,7 @@ class APIRouter(routing.Router): ), ] = "Successful Response", responses: Annotated[ - Optional[Dict[Union[int, str], Dict[str, Any]]], + Optional[dict[Union[int, str], dict[str, Any]]], Doc( """ Additional responses that could be returned by this *path operation*. @@ -4424,7 +4422,7 @@ class APIRouter(routing.Router): ), ] = True, response_class: Annotated[ - Type[Response], + type[Response], Doc( """ Response class to be used for this *path operation*. @@ -4445,7 +4443,7 @@ class APIRouter(routing.Router): ), ] = None, callbacks: Annotated[ - Optional[List[BaseRoute]], + Optional[list[BaseRoute]], Doc( """ List of *path operations* that will be used as OpenAPI callbacks. @@ -4461,7 +4459,7 @@ class APIRouter(routing.Router): ), ] = None, openapi_extra: Annotated[ - Optional[Dict[str, Any]], + Optional[dict[str, Any]], Doc( """ Extra metadata to be included in the OpenAPI schema for this *path diff --git a/fastapi/security/api_key.py b/fastapi/security/api_key.py index 81c7be10d6..18dfb8e618 100644 --- a/fastapi/security/api_key.py +++ b/fastapi/security/api_key.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import Annotated, Optional, Union from annotated_doc import Doc from fastapi.openapi.models import APIKey, APIKeyIn @@ -6,7 +6,6 @@ from fastapi.security.base import SecurityBase from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.status import HTTP_401_UNAUTHORIZED -from typing_extensions import Annotated class APIKeyBase(SecurityBase): diff --git a/fastapi/security/http.py b/fastapi/security/http.py index 0d1bbba3a0..b4c3bc6d85 100644 --- a/fastapi/security/http.py +++ b/fastapi/security/http.py @@ -1,6 +1,6 @@ import binascii from base64 import b64decode -from typing import Dict, Optional +from typing import Annotated, Optional from annotated_doc import Doc from fastapi.exceptions import HTTPException @@ -11,7 +11,6 @@ from fastapi.security.utils import get_authorization_scheme_param from pydantic import BaseModel from starlette.requests import Request from starlette.status import HTTP_401_UNAUTHORIZED -from typing_extensions import Annotated class HTTPBasicCredentials(BaseModel): @@ -82,7 +81,7 @@ class HTTPBase(SecurityBase): self.scheme_name = scheme_name or self.__class__.__name__ self.auto_error = auto_error - def make_authenticate_headers(self) -> Dict[str, str]: + def make_authenticate_headers(self) -> dict[str, str]: return {"WWW-Authenticate": f"{self.model.scheme.title()}"} def make_not_authenticated_error(self) -> HTTPException: @@ -197,7 +196,7 @@ class HTTPBasic(HTTPBase): self.realm = realm self.auto_error = auto_error - def make_authenticate_headers(self) -> Dict[str, str]: + def make_authenticate_headers(self) -> dict[str, str]: if self.realm: return {"WWW-Authenticate": f'Basic realm="{self.realm}"'} return {"WWW-Authenticate": "Basic"} diff --git a/fastapi/security/oauth2.py b/fastapi/security/oauth2.py index b41b0f8778..fc49ba1903 100644 --- a/fastapi/security/oauth2.py +++ b/fastapi/security/oauth2.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Union, cast +from typing import Annotated, Any, Optional, Union, cast from annotated_doc import Doc from fastapi.exceptions import HTTPException @@ -10,9 +10,6 @@ from fastapi.security.utils import get_authorization_scheme_param from starlette.requests import Request from starlette.status import HTTP_401_UNAUTHORIZED -# TODO: import from typing when deprecating Python 3.9 -from typing_extensions import Annotated - class OAuth2PasswordRequestForm: """ @@ -323,7 +320,7 @@ class OAuth2(SecurityBase): self, *, flows: Annotated[ - Union[OAuthFlowsModel, Dict[str, Dict[str, Any]]], + Union[OAuthFlowsModel, dict[str, dict[str, Any]]], Doc( """ The dictionary of OAuth2 flows. @@ -440,7 +437,7 @@ class OAuth2PasswordBearer(OAuth2): ), ] = None, scopes: Annotated[ - Optional[Dict[str, str]], + Optional[dict[str, str]], Doc( """ The OAuth2 scopes that would be required by the *path operations* that @@ -553,7 +550,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2): ), ] = None, scopes: Annotated[ - Optional[Dict[str, str]], + Optional[dict[str, str]], Doc( """ The OAuth2 scopes that would be required by the *path operations* that @@ -639,7 +636,7 @@ class SecurityScopes: def __init__( self, scopes: Annotated[ - Optional[List[str]], + Optional[list[str]], Doc( """ This will be filled by FastAPI. @@ -648,7 +645,7 @@ class SecurityScopes: ] = None, ): self.scopes: Annotated[ - List[str], + list[str], Doc( """ The list of all the scopes required by dependencies. diff --git a/fastapi/security/open_id_connect_url.py b/fastapi/security/open_id_connect_url.py index e574a56a82..f4d953351e 100644 --- a/fastapi/security/open_id_connect_url.py +++ b/fastapi/security/open_id_connect_url.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Annotated, Optional from annotated_doc import Doc from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel @@ -6,7 +6,6 @@ from fastapi.security.base import SecurityBase from starlette.exceptions import HTTPException from starlette.requests import Request from starlette.status import HTTP_401_UNAUTHORIZED -from typing_extensions import Annotated class OpenIdConnect(SecurityBase): diff --git a/fastapi/security/utils.py b/fastapi/security/utils.py index fa7a450b74..002e68b445 100644 --- a/fastapi/security/utils.py +++ b/fastapi/security/utils.py @@ -1,9 +1,9 @@ -from typing import Optional, Tuple +from typing import Optional def get_authorization_scheme_param( authorization_header_value: Optional[str], -) -> Tuple[str, str]: +) -> tuple[str, str]: if not authorization_header_value: return "", "" scheme, _, param = authorization_header_value.partition(" ") diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py index e41d712308..b6c804dc44 100644 --- a/fastapi/temp_pydantic_v1_params.py +++ b/fastapi/temp_pydantic_v1_params.py @@ -1,9 +1,9 @@ import warnings -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Annotated, Any, Callable, Optional, Union from fastapi.openapi.models import Example from fastapi.params import ParamTypes -from typing_extensions import Annotated, deprecated +from typing_extensions import deprecated from ._compat.may_v1 import FieldInfo, Undefined from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE @@ -47,7 +47,7 @@ class Param(FieldInfo): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -55,10 +55,10 @@ class Param(FieldInfo): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): if example is not _Unset: @@ -148,7 +148,7 @@ class Path(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -156,10 +156,10 @@ class Path(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): assert default is ..., "Path parameters cannot have a default value" @@ -234,7 +234,7 @@ class Query(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -242,10 +242,10 @@ class Query(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( @@ -319,7 +319,7 @@ class Header(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -327,10 +327,10 @@ class Header(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): self.convert_underscores = convert_underscores @@ -404,7 +404,7 @@ class Cookie(Param): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -412,10 +412,10 @@ class Cookie(Param): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( @@ -488,7 +488,7 @@ class Body(FieldInfo): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -496,10 +496,10 @@ class Body(FieldInfo): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): self.embed = embed @@ -591,7 +591,7 @@ class Form(Body): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -599,10 +599,10 @@ class Form(Body): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( @@ -675,7 +675,7 @@ class File(Form): # type: ignore[misc] allow_inf_nan: Union[bool, None] = _Unset, max_digits: Union[int, None] = _Unset, decimal_places: Union[int, None] = _Unset, - examples: Optional[List[Any]] = None, + examples: Optional[list[Any]] = None, example: Annotated[ Optional[Any], deprecated( @@ -683,10 +683,10 @@ class File(Form): # type: ignore[misc] "although still supported. Use examples instead." ), ] = _Unset, - openapi_examples: Optional[Dict[str, Example]] = None, + openapi_examples: Optional[dict[str, Example]] = None, deprecated: Union[deprecated, str, bool, None] = None, include_in_schema: bool = True, - json_schema_extra: Union[Dict[str, Any], None] = None, + json_schema_extra: Union[dict[str, Any], None] = None, **extra: Any, ): super().__init__( diff --git a/fastapi/types.py b/fastapi/types.py index 3f4e81a7cc..d3e980cb43 100644 --- a/fastapi/types.py +++ b/fastapi/types.py @@ -1,11 +1,11 @@ import types from enum import Enum -from typing import Any, Callable, Dict, Optional, Set, Tuple, Type, TypeVar, Union +from typing import Any, Callable, Optional, TypeVar, Union from pydantic import BaseModel DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) UnionType = getattr(types, "UnionType", Union) -ModelNameMap = Dict[Union[Type[BaseModel], Type[Enum]], str] -IncEx = Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any]] -DependencyCacheKey = Tuple[Optional[Callable[..., Any]], Tuple[str, ...], str] +ModelNameMap = dict[Union[type[BaseModel], type[Enum]], str] +IncEx = Union[set[int], set[str], dict[int, Any], dict[str, Any]] +DependencyCacheKey = tuple[Optional[Callable[..., Any]], tuple[str, ...], str] diff --git a/fastapi/utils.py b/fastapi/utils.py index b3b89ed2b4..5b93b9af8e 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -1,14 +1,11 @@ import re import warnings +from collections.abc import MutableMapping from dataclasses import is_dataclass from typing import ( TYPE_CHECKING, Any, - Dict, - MutableMapping, Optional, - Set, - Type, Union, cast, ) @@ -36,7 +33,7 @@ if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute # Cache for `create_cloned_field` -_CLONED_TYPES_CACHE: MutableMapping[Type[BaseModel], Type[BaseModel]] = ( +_CLONED_TYPES_CACHE: MutableMapping[type[BaseModel], type[BaseModel]] = ( WeakKeyDictionary() ) @@ -58,7 +55,7 @@ def is_body_allowed_for_status_code(status_code: Union[int, str, None]) -> bool: return not (current_status_code < 200 or current_status_code in {204, 205, 304}) -def get_path_param_names(path: str) -> Set[str]: +def get_path_param_names(path: str) -> set[str]: return set(re.findall("{(.*?)}", path)) @@ -76,10 +73,10 @@ _invalid_args_message = ( def create_model_field( name: str, type_: Any, - class_validators: Optional[Dict[str, Validator]] = None, + class_validators: Optional[dict[str, Validator]] = None, default: Optional[Any] = Undefined, required: Union[bool, UndefinedType] = Undefined, - model_config: Union[Type[BaseConfig], None] = None, + model_config: Union[type[BaseConfig], None] = None, field_info: Optional[FieldInfo] = None, alias: Optional[str] = None, mode: Literal["validation", "serialization"] = "validation", @@ -141,7 +138,7 @@ def create_model_field( def create_cloned_field( field: ModelField, *, - cloned_types: Optional[MutableMapping[Type[BaseModel], Type[BaseModel]]] = None, + cloned_types: Optional[MutableMapping[type[BaseModel], type[BaseModel]]] = None, ) -> ModelField: if PYDANTIC_V2: from ._compat import v2 @@ -161,7 +158,7 @@ def create_cloned_field( original_type = original_type.__pydantic_model__ use_type = original_type if lenient_issubclass(original_type, v1.BaseModel): - original_type = cast(Type[v1.BaseModel], original_type) + original_type = cast(type[v1.BaseModel], original_type) use_type = cloned_types.get(original_type) if use_type is None: use_type = v1.create_model(original_type.__name__, __base__=original_type) @@ -224,7 +221,7 @@ def generate_unique_id(route: "APIRoute") -> str: return operation_id -def deep_dict_update(main_dict: Dict[Any, Any], update_dict: Dict[Any, Any]) -> None: +def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> None: for key, value in update_dict.items(): if ( key in main_dict diff --git a/pdm_build.py b/pdm_build.py index c83222b336..a0eb88eebb 100644 --- a/pdm_build.py +++ b/pdm_build.py @@ -1,5 +1,5 @@ import os -from typing import Any, Dict +from typing import Any from pdm.backend.hooks import Context @@ -9,12 +9,12 @@ TIANGOLO_BUILD_PACKAGE = os.getenv("TIANGOLO_BUILD_PACKAGE", "fastapi") def pdm_build_initialize(context: Context) -> None: metadata = context.config.metadata # Get custom config for the current package, from the env var - config: Dict[str, Any] = context.config.data["tool"]["tiangolo"][ + config: dict[str, Any] = context.config.data["tool"]["tiangolo"][ "_internal-slim-build" ]["packages"].get(TIANGOLO_BUILD_PACKAGE) if not config: return - project_config: Dict[str, Any] = config["project"] + 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 diff --git a/scripts/docs.py b/scripts/docs.py index b35bb3627f..bf7d9de395 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -8,7 +8,7 @@ from html.parser import HTMLParser from http.server import HTTPServer, SimpleHTTPRequestHandler from multiprocessing import Pool from pathlib import Path -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union import mkdocs.utils import typer @@ -82,11 +82,11 @@ def slugify(text: str) -> str: ) -def get_en_config() -> Dict[str, Any]: +def get_en_config() -> dict[str, Any]: return mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8")) -def get_lang_paths() -> List[Path]: +def get_lang_paths() -> list[Path]: return sorted(docs_path.iterdir()) @@ -334,14 +334,14 @@ def live( ) -def get_updated_config_content() -> Dict[str, Any]: +def get_updated_config_content() -> dict[str, Any]: config = get_en_config() languages = [{"en": "/"}] - new_alternate: List[Dict[str, str]] = [] + new_alternate: list[dict[str, str]] = [] # Language names sourced from https://quickref.me/iso-639-1 # Contributors may wish to update or change these, e.g. to fix capitalization. language_names_path = Path(__file__).parent / "../docs/language_names.yml" - local_language_names: Dict[str, str] = mkdocs.utils.yaml_load( + local_language_names: dict[str, str] = mkdocs.utils.yaml_load( language_names_path.read_text(encoding="utf-8") ) for lang_path in get_lang_paths(): @@ -530,7 +530,7 @@ def add_permalinks_page(path: Path, update_existing: bool = False): @app.command() -def add_permalinks_pages(pages: List[Path], update_existing: bool = False) -> None: +def add_permalinks_pages(pages: list[Path], update_existing: bool = False) -> None: """ Add or update header permalinks in specific pages of En docs. """ diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index 09cfa99e38..4b781270a2 100644 --- a/scripts/mkdocs_hooks.py +++ b/scripts/mkdocs_hooks.py @@ -1,6 +1,6 @@ from functools import lru_cache from pathlib import Path -from typing import Any, List, Union +from typing import Any, Union import material from mkdocs.config.defaults import MkDocsConfig @@ -27,7 +27,7 @@ def get_missing_translation_content(docs_dir: str) -> str: @lru_cache -def get_mkdocs_material_langs() -> List[str]: +def get_mkdocs_material_langs() -> list[str]: material_path = Path(material.__file__).parent material_langs_path = material_path / "templates" / "partials" / "languages" langs = [file.stem for file in material_langs_path.glob("*.html")] @@ -65,7 +65,7 @@ def resolve_file(*, item: str, files: Files, config: MkDocsConfig) -> None: ) -def resolve_files(*, items: List[Any], files: Files, config: MkDocsConfig) -> None: +def resolve_files(*, items: list[Any], files: Files, config: MkDocsConfig) -> None: for item in items: if isinstance(item, str): resolve_file(item=item, files=files, config=config) @@ -94,9 +94,9 @@ def on_files(files: Files, *, config: MkDocsConfig) -> Files: def generate_renamed_section_items( - items: List[Union[Page, Section, Link]], *, config: MkDocsConfig -) -> List[Union[Page, Section, Link]]: - new_items: List[Union[Page, Section, Link]] = [] + items: list[Union[Page, Section, Link]], *, config: MkDocsConfig +) -> list[Union[Page, Section, Link]]: + new_items: list[Union[Page, Section, Link]] = [] for item in items: if isinstance(item, Section): new_title = item.title diff --git a/scripts/notify_translations.py b/scripts/notify_translations.py index c300624db2..2ca740a607 100644 --- a/scripts/notify_translations.py +++ b/scripts/notify_translations.py @@ -3,7 +3,7 @@ import random import sys import time from pathlib import Path -from typing import Any, Dict, List, Union, cast +from typing import Any, Union, cast import httpx from github import Github @@ -120,7 +120,7 @@ class CommentsEdge(BaseModel): class Comments(BaseModel): - edges: List[CommentsEdge] + edges: list[CommentsEdge] class CommentsDiscussion(BaseModel): @@ -149,7 +149,7 @@ class AllDiscussionsLabelsEdge(BaseModel): class AllDiscussionsDiscussionLabels(BaseModel): - edges: List[AllDiscussionsLabelsEdge] + edges: list[AllDiscussionsLabelsEdge] class AllDiscussionsDiscussionNode(BaseModel): @@ -160,7 +160,7 @@ class AllDiscussionsDiscussionNode(BaseModel): class AllDiscussionsDiscussions(BaseModel): - nodes: List[AllDiscussionsDiscussionNode] + nodes: list[AllDiscussionsDiscussionNode] class AllDiscussionsRepository(BaseModel): @@ -205,7 +205,7 @@ def get_graphql_response( discussion_id: Union[str, None] = None, comment_id: Union[str, None] = None, body: Union[str, None] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = { "after": after, @@ -233,12 +233,12 @@ def get_graphql_response( logging.error(data["errors"]) logging.error(response.text) raise RuntimeError(response.text) - return cast(Dict[str, Any], data) + return cast(dict[str, Any], data) def get_graphql_translation_discussions( *, settings: Settings -) -> List[AllDiscussionsDiscussionNode]: +) -> list[AllDiscussionsDiscussionNode]: data = get_graphql_response( settings=settings, query=all_discussions_query, @@ -250,7 +250,7 @@ def get_graphql_translation_discussions( def get_graphql_translation_discussion_comments_edges( *, settings: Settings, discussion_number: int, after: Union[str, None] = None -) -> List[CommentsEdge]: +) -> list[CommentsEdge]: data = get_graphql_response( settings=settings, query=translation_discussion_query, @@ -264,7 +264,7 @@ def get_graphql_translation_discussion_comments_edges( def get_graphql_translation_discussion_comments( *, settings: Settings, discussion_number: int ) -> list[Comment]: - comment_nodes: List[Comment] = [] + comment_nodes: list[Comment] = [] discussion_edges = get_graphql_translation_discussion_comments_edges( settings=settings, discussion_number=discussion_number ) @@ -348,7 +348,7 @@ def main() -> None: # Generate translation map, lang ID to discussion discussions = get_graphql_translation_discussions(settings=settings) - lang_to_discussion_map: Dict[str, AllDiscussionsDiscussionNode] = {} + lang_to_discussion_map: dict[str, AllDiscussionsDiscussionNode] = {} for discussion in discussions: for edge in discussion.labels.edges: label = edge.node.name diff --git a/scripts/people.py b/scripts/people.py index 65e009944c..207ab46493 100644 --- a/scripts/people.py +++ b/scripts/people.py @@ -3,9 +3,10 @@ import secrets import subprocess import time from collections import Counter +from collections.abc import Container from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Container, Union +from typing import Any, Union import httpx import yaml diff --git a/tests/main.py b/tests/main.py index 2f1d617115..7edb16c615 100644 --- a/tests/main.py +++ b/tests/main.py @@ -1,5 +1,5 @@ import http -from typing import FrozenSet, List, Optional +from typing import Optional from fastapi import FastAPI, Path, Query @@ -195,15 +195,15 @@ def get_enum_status_code(): @app.get("/query/frozenset") -def get_query_type_frozenset(query: FrozenSet[int] = Query(...)): +def get_query_type_frozenset(query: frozenset[int] = Query(...)): return ",".join(map(str, sorted(query))) @app.get("/query/list") -def get_query_list(device_ids: List[int] = Query()) -> List[int]: +def get_query_list(device_ids: list[int] = Query()) -> list[int]: return device_ids @app.get("/query/list-default") -def get_query_list_default(device_ids: List[int] = Query(default=[])) -> List[int]: +def get_query_list_default(device_ids: list[int] = Query(default=[])) -> list[int]: return device_ids diff --git a/tests/test_additional_properties.py b/tests/test_additional_properties.py index be14d10edc..2622366400 100644 --- a/tests/test_additional_properties.py +++ b/tests/test_additional_properties.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -8,7 +6,7 @@ app = FastAPI() class Items(BaseModel): - items: Dict[str, int] + items: dict[str, int] @app.post("/foo") diff --git a/tests/test_additional_responses_custom_validationerror.py b/tests/test_additional_responses_custom_validationerror.py index 9fec5c96d4..8724e5ecb8 100644 --- a/tests/test_additional_responses_custom_validationerror.py +++ b/tests/test_additional_responses_custom_validationerror.py @@ -1,5 +1,3 @@ -import typing - from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -18,7 +16,7 @@ class Error(BaseModel): class JsonApiError(BaseModel): - errors: typing.List[Error] + errors: list[Error] @app.get( diff --git a/tests/test_additional_responses_response_class.py b/tests/test_additional_responses_response_class.py index 68753561c9..fecc3ee16b 100644 --- a/tests/test_additional_responses_response_class.py +++ b/tests/test_additional_responses_response_class.py @@ -1,5 +1,3 @@ -import typing - from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -18,7 +16,7 @@ class Error(BaseModel): class JsonApiError(BaseModel): - errors: typing.List[Error] + errors: list[Error] @app.get( diff --git a/tests/test_allow_inf_nan_in_enforcing.py b/tests/test_allow_inf_nan_in_enforcing.py index 9e855fdf81..083a024af0 100644 --- a/tests/test_allow_inf_nan_in_enforcing.py +++ b/tests/test_allow_inf_nan_in_enforcing.py @@ -1,7 +1,8 @@ +from typing import Annotated + import pytest from fastapi import Body, FastAPI, Query from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_ambiguous_params.py b/tests/test_ambiguous_params.py index 8a31442eb0..5e48782f8d 100644 --- a/tests/test_ambiguous_params.py +++ b/tests/test_ambiguous_params.py @@ -1,9 +1,10 @@ +from typing import Annotated + import pytest from fastapi import Depends, FastAPI, Path from fastapi.param_functions import Query from fastapi.testclient import TestClient from fastapi.utils import PYDANTIC_V2 -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_annotated.py b/tests/test_annotated.py index 473d33e52c..a9e7c78c99 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -1,8 +1,9 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict from fastapi import APIRouter, FastAPI, Query from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_arbitrary_types.py b/tests/test_arbitrary_types.py index e5fa95ef22..8b8417bae0 100644 --- a/tests/test_arbitrary_types.py +++ b/tests/test_arbitrary_types.py @@ -1,10 +1,9 @@ -from typing import List +from typing import Annotated import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot -from typing_extensions import Annotated from .utils import needs_pydanticv2 @@ -25,7 +24,7 @@ def get_client(): FakeNumpyArrayPydantic = Annotated[ FakeNumpyArray, - WithJsonSchema(TypeAdapter(List[float]).json_schema()), + WithJsonSchema(TypeAdapter(list[float]).json_schema()), PlainSerializer(lambda v: v.data), ] @@ -66,7 +65,7 @@ def test_typeadapter(): FakeNumpyArrayPydantic = Annotated[ FakeNumpyArray, - WithJsonSchema(TypeAdapter(List[float]).json_schema()), + WithJsonSchema(TypeAdapter(list[float]).json_schema()), PlainSerializer(lambda v: v.data), ] diff --git a/tests/test_compat.py b/tests/test_compat.py index 26537c5ab7..3f9d84d894 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Union +from typing import Any, Union from fastapi import FastAPI, UploadFile from fastapi._compat import ( @@ -61,7 +61,7 @@ def test_complex(): app = FastAPI() @app.post("/") - def foo(foo: Union[str, List[int]]): + def foo(foo: Union[str, list[int]]): return foo client = TestClient(app) @@ -95,7 +95,7 @@ def test_propagates_pydantic2_model_config(): embedded_model: EmbeddedModel = EmbeddedModel() @app.post("/") - def foo(req: Model) -> Dict[str, Union[str, None]]: + def foo(req: Model) -> dict[str, Union[str, None]]: return { "value": req.value or None, "embedded_value": req.embedded_model.value or None, @@ -125,7 +125,7 @@ def test_is_bytes_sequence_annotation_union(): # TODO: in theory this would allow declaring types that could be lists of bytes # to be read from files and other types, but I'm not even sure it's a good idea # to support it as a first class "feature" - assert is_bytes_sequence_annotation(Union[List[str], List[bytes]]) + assert is_bytes_sequence_annotation(Union[list[str], list[bytes]]) def test_is_uploadfile_sequence_annotation(): @@ -133,7 +133,7 @@ def test_is_uploadfile_sequence_annotation(): # TODO: in theory this would allow declaring types that could be lists of UploadFile # and other types, but I'm not even sure it's a good idea to support it as a first # class "feature" - assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]]) + assert is_uploadfile_sequence_annotation(Union[list[str], list[UploadFile]]) @needs_pydanticv2 @@ -141,7 +141,7 @@ def test_serialize_sequence_value_with_optional_list(): """Test that serialize_sequence_value handles optional lists correctly.""" from fastapi._compat import v2 - field_info = FieldInfo(annotation=Union[List[str], None]) + field_info = FieldInfo(annotation=Union[list[str], None]) field = v2.ModelField(name="items", field_info=field_info) result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"]) assert result == ["a", "b", "c"] @@ -166,7 +166,7 @@ def test_serialize_sequence_value_with_none_first_in_union(): """Test that serialize_sequence_value handles Union[None, List[...]] correctly.""" from fastapi._compat import v2 - field_info = FieldInfo(annotation=Union[None, List[str]]) + field_info = FieldInfo(annotation=Union[None, list[str]]) field = v2.ModelField(name="items", field_info=field_info) result = v2.serialize_sequence_value(field=field, value=["x", "y"]) assert result == ["x", "y"] @@ -179,7 +179,7 @@ def test_is_pv1_scalar_field(): # For coverage class Model(v1.BaseModel): - foo: Union[str, Dict[str, Any]] + foo: Union[str, dict[str, Any]] fields = v1.get_model_fields(Model) assert not is_scalar_field(fields[0]) diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py index 7064761cb5..8829a0a611 100644 --- a/tests/test_compat_params_v1.py +++ b/tests/test_compat_params_v1.py @@ -1,5 +1,5 @@ import sys -from typing import List, Optional +from typing import Optional import pytest @@ -8,6 +8,8 @@ from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() +from typing import Annotated + from fastapi import FastAPI from fastapi._compat.v1 import BaseModel from fastapi.temp_pydantic_v1_params import ( @@ -21,7 +23,6 @@ from fastapi.temp_pydantic_v1_params import ( ) from fastapi.testclient import TestClient from inline_snapshot import snapshot -from typing_extensions import Annotated class Item(BaseModel): @@ -112,7 +113,7 @@ def upload_file( @app.post("/upload-multiple/") def upload_multiple_files( - files: Annotated[List[bytes], File()], + files: Annotated[list[bytes], File()], note: Annotated[str, Form()] = "", ): return { diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py index d890291b19..bc3e1c5ec3 100644 --- a/tests/test_custom_schema_fields.py +++ b/tests/test_custom_schema_fields.py @@ -1,10 +1,9 @@ -from typing import Optional +from typing import Annotated, Optional from fastapi import FastAPI from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel -from typing_extensions import Annotated if PYDANTIC_V2: from pydantic import WithJsonSchema diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index 7e57d525ce..c175147bc3 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -1,6 +1,5 @@ import io from pathlib import Path -from typing import List import pytest from fastapi import FastAPI, UploadFile @@ -38,7 +37,7 @@ def test_upload_file_is_closed(tmp_path: Path): path.write_bytes(b"") app = FastAPI() - testing_file_store: List[UploadFile] = [] + testing_file_store: list[UploadFile] = [] @app.post("/uploadfile/") def create_upload_file(file: UploadFile): diff --git a/tests/test_dependency_after_yield_raise.py b/tests/test_dependency_after_yield_raise.py index b560dc36f9..b561402772 100644 --- a/tests/test_dependency_after_yield_raise.py +++ b/tests/test_dependency_after_yield_raise.py @@ -1,9 +1,8 @@ -from typing import Any +from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI, HTTPException from fastapi.testclient import TestClient -from typing_extensions import Annotated class CustomError(Exception): diff --git a/tests/test_dependency_after_yield_streaming.py b/tests/test_dependency_after_yield_streaming.py index 7e1c8822b8..cbadff8f87 100644 --- a/tests/test_dependency_after_yield_streaming.py +++ b/tests/test_dependency_after_yield_streaming.py @@ -1,11 +1,11 @@ +from collections.abc import Generator from contextlib import contextmanager -from typing import Any, Generator +from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI from fastapi.responses import StreamingResponse from fastapi.testclient import TestClient -from typing_extensions import Annotated class Session: diff --git a/tests/test_dependency_after_yield_websockets.py b/tests/test_dependency_after_yield_websockets.py index 7c323c338b..0fdf697b6c 100644 --- a/tests/test_dependency_after_yield_websockets.py +++ b/tests/test_dependency_after_yield_websockets.py @@ -1,10 +1,10 @@ +from collections.abc import Generator from contextlib import contextmanager -from typing import Any, Generator +from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI, WebSocket from fastapi.testclient import TestClient -from typing_extensions import Annotated class Session: diff --git a/tests/test_dependency_class.py b/tests/test_dependency_class.py index 75241b467a..95ff3e9d95 100644 --- a/tests/test_dependency_class.py +++ b/tests/test_dependency_class.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator, Generator +from collections.abc import AsyncGenerator, Generator import pytest from fastapi import Depends, FastAPI diff --git a/tests/test_dependency_contextmanager.py b/tests/test_dependency_contextmanager.py index 02c10458cb..5a89934741 100644 --- a/tests/test_dependency_contextmanager.py +++ b/tests/test_dependency_contextmanager.py @@ -1,5 +1,4 @@ import json -from typing import Dict import pytest from fastapi import BackgroundTasks, Depends, FastAPI @@ -37,19 +36,19 @@ class OtherDependencyError(Exception): pass -async def asyncgen_state(state: Dict[str, str] = Depends(get_state)): +async def asyncgen_state(state: dict[str, str] = Depends(get_state)): state["/async"] = "asyncgen started" yield state["/async"] state["/async"] = "asyncgen completed" -def generator_state(state: Dict[str, str] = Depends(get_state)): +def generator_state(state: dict[str, str] = Depends(get_state)): state["/sync"] = "generator started" yield state["/sync"] state["/sync"] = "generator completed" -async def asyncgen_state_try(state: Dict[str, str] = Depends(get_state)): +async def asyncgen_state_try(state: dict[str, str] = Depends(get_state)): state["/async_raise"] = "asyncgen raise started" try: yield state["/async_raise"] @@ -60,7 +59,7 @@ async def asyncgen_state_try(state: Dict[str, str] = Depends(get_state)): state["/async_raise"] = "asyncgen raise finalized" -def generator_state_try(state: Dict[str, str] = Depends(get_state)): +def generator_state_try(state: dict[str, str] = Depends(get_state)): state["/sync_raise"] = "generator raise started" try: yield state["/sync_raise"] diff --git a/tests/test_dependency_contextvars.py b/tests/test_dependency_contextvars.py index 076802df84..0c2e5594b6 100644 --- a/tests/test_dependency_contextvars.py +++ b/tests/test_dependency_contextvars.py @@ -1,10 +1,11 @@ +from collections.abc import Awaitable from contextvars import ContextVar -from typing import Any, Awaitable, Callable, Dict, Optional +from typing import Any, Callable, Optional from fastapi import Depends, FastAPI, Request, Response from fastapi.testclient import TestClient -legacy_request_state_context_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar( +legacy_request_state_context_var: ContextVar[Optional[dict[str, Any]]] = ContextVar( "legacy_request_state_context_var", default=None ) diff --git a/tests/test_dependency_duplicates.py b/tests/test_dependency_duplicates.py index 8e8d07c2d6..7c6717e2aa 100644 --- a/tests/test_dependency_duplicates.py +++ b/tests/test_dependency_duplicates.py @@ -1,5 +1,3 @@ -from typing import List - from dirty_equals import IsDict from fastapi import Depends, FastAPI from fastapi.testclient import TestClient @@ -40,7 +38,7 @@ async def no_duplicates(item: Item, item2: Item = Depends(dependency)): @app.post("/with-duplicates-sub") async def no_duplicates_sub( - item: Item, sub_items: List[Item] = Depends(sub_duplicate_dependency) + item: Item, sub_items: list[Item] = Depends(sub_duplicate_dependency) ): return [item, sub_items] diff --git a/tests/test_dependency_paramless.py b/tests/test_dependency_paramless.py index 9c3cc3878b..1774196fe4 100644 --- a/tests/test_dependency_paramless.py +++ b/tests/test_dependency_paramless.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Annotated, Union from fastapi import FastAPI, HTTPException, Security from fastapi.security import ( @@ -6,7 +6,6 @@ from fastapi.security import ( SecurityScopes, ) from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_dependency_partial.py b/tests/test_dependency_partial.py index 61a76236f8..05a3cffa5a 100644 --- a/tests/test_dependency_partial.py +++ b/tests/test_dependency_partial.py @@ -1,10 +1,10 @@ +from collections.abc import AsyncGenerator, Generator from functools import partial -from typing import AsyncGenerator, Generator +from typing import Annotated import pytest from fastapi import Depends, FastAPI from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_dependency_security_overrides.py b/tests/test_dependency_security_overrides.py index b89d82db43..14b65c7770 100644 --- a/tests/test_dependency_security_overrides.py +++ b/tests/test_dependency_security_overrides.py @@ -1,5 +1,3 @@ -from typing import List, Tuple - from fastapi import Depends, FastAPI, Security from fastapi.security import SecurityScopes from fastapi.testclient import TestClient @@ -25,8 +23,8 @@ def get_data_override(): @app.get("/user") def read_user( - user_data: Tuple[str, List[str]] = Security(get_user, scopes=["foo", "bar"]), - data: List[int] = Depends(get_data), + user_data: tuple[str, list[str]] = Security(get_user, scopes=["foo", "bar"]), + data: list[int] = Depends(get_data), ): return {"user": user_data[0], "scopes": user_data[1], "data": data} diff --git a/tests/test_dependency_wrapped.py b/tests/test_dependency_wrapped.py index 08356712d6..a4044112a2 100644 --- a/tests/test_dependency_wrapped.py +++ b/tests/test_dependency_wrapped.py @@ -1,7 +1,7 @@ import inspect import sys +from collections.abc import AsyncGenerator, Generator from functools import wraps -from typing import AsyncGenerator, Generator import pytest from fastapi import Depends, FastAPI diff --git a/tests/test_dependency_yield_scope.py b/tests/test_dependency_yield_scope.py index d87164fe80..f3fc3cc94f 100644 --- a/tests/test_dependency_yield_scope.py +++ b/tests/test_dependency_yield_scope.py @@ -1,12 +1,11 @@ import json -from typing import Any, Tuple +from typing import Annotated, Any import pytest from fastapi import APIRouter, Depends, FastAPI, HTTPException from fastapi.exceptions import FastAPIError from fastapi.responses import StreamingResponse from fastapi.testclient import TestClient -from typing_extensions import Annotated class Session: @@ -43,7 +42,7 @@ def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep) named_session.open = False -NamedSessionsDep = Annotated[Tuple[NamedSession, Session], Depends(get_named_session)] +NamedSessionsDep = Annotated[tuple[NamedSession, Session], Depends(get_named_session)] def get_named_func_session(session: SessionFuncDep) -> Any: @@ -58,14 +57,14 @@ def get_named_regular_func_session(session: SessionFuncDep) -> Any: BrokenSessionsDep = Annotated[ - Tuple[NamedSession, Session], Depends(get_named_func_session) + tuple[NamedSession, Session], Depends(get_named_func_session) ] NamedSessionsFuncDep = Annotated[ - Tuple[NamedSession, Session], Depends(get_named_func_session, scope="function") + tuple[NamedSession, Session], Depends(get_named_func_session, scope="function") ] RegularSessionsDep = Annotated[ - Tuple[NamedSession, Session], Depends(get_named_regular_func_session) + tuple[NamedSession, Session], Depends(get_named_regular_func_session) ] app = FastAPI() diff --git a/tests/test_dependency_yield_scope_websockets.py b/tests/test_dependency_yield_scope_websockets.py index 52a30ae7a7..dbe35e576c 100644 --- a/tests/test_dependency_yield_scope_websockets.py +++ b/tests/test_dependency_yield_scope_websockets.py @@ -1,13 +1,12 @@ from contextvars import ContextVar -from typing import Any, Dict, Tuple +from typing import Annotated, Any import pytest from fastapi import Depends, FastAPI, WebSocket from fastapi.exceptions import FastAPIError from fastapi.testclient import TestClient -from typing_extensions import Annotated -global_context: ContextVar[Dict[str, Any]] = ContextVar("global_context", default={}) # noqa: B039 +global_context: ContextVar[dict[str, Any]] = ContextVar("global_context", default={}) # noqa: B039 class Session: @@ -43,7 +42,7 @@ def get_named_session(session: SessionRequestDep, session_b: SessionDefaultDep) global_state["named_session_closed"] = True -NamedSessionsDep = Annotated[Tuple[NamedSession, Session], Depends(get_named_session)] +NamedSessionsDep = Annotated[tuple[NamedSession, Session], Depends(get_named_session)] def get_named_func_session(session: SessionFuncDep) -> Any: @@ -60,14 +59,14 @@ def get_named_regular_func_session(session: SessionFuncDep) -> Any: BrokenSessionsDep = Annotated[ - Tuple[NamedSession, Session], Depends(get_named_func_session) + tuple[NamedSession, Session], Depends(get_named_func_session) ] NamedSessionsFuncDep = Annotated[ - Tuple[NamedSession, Session], Depends(get_named_func_session, scope="function") + tuple[NamedSession, Session], Depends(get_named_func_session, scope="function") ] RegularSessionsDep = Annotated[ - Tuple[NamedSession, Session], Depends(get_named_regular_func_session) + tuple[NamedSession, Session], Depends(get_named_regular_func_session) ] app = FastAPI() diff --git a/tests/test_file_and_form_order_issue_9116.py b/tests/test_file_and_form_order_issue_9116.py index cb9a31d314..75290b60cd 100644 --- a/tests/test_file_and_form_order_issue_9116.py +++ b/tests/test_file_and_form_order_issue_9116.py @@ -4,12 +4,11 @@ See https://github.com/tiangolo/fastapi/discussions/9116 """ from pathlib import Path -from typing import List +from typing import Annotated import pytest from fastapi import FastAPI, File, Form from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() @@ -32,7 +31,7 @@ def file_after_form( @app.post("/file_list_before_form") def file_list_before_form( - files: Annotated[List[bytes], File()], + files: Annotated[list[bytes], File()], city: Annotated[str, Form()], ): return {"file_contents": files, "city": city} @@ -41,7 +40,7 @@ def file_list_before_form( @app.post("/file_list_after_form") def file_list_after_form( city: Annotated[str, Form()], - files: Annotated[List[bytes], File()], + files: Annotated[list[bytes], File()], ): return {"file_contents": files, "city": city} diff --git a/tests/test_form_default.py b/tests/test_form_default.py index 2a12049d1a..0b3eb8f2e2 100644 --- a/tests/test_form_default.py +++ b/tests/test_form_default.py @@ -1,8 +1,7 @@ -from typing import Optional +from typing import Annotated, Optional from fastapi import FastAPI, File, Form from starlette.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py index 1db63f0214..b149b76539 100644 --- a/tests/test_forms_single_model.py +++ b/tests/test_forms_single_model.py @@ -1,11 +1,10 @@ -from typing import List, Optional +from typing import Annotated, Optional from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated app = FastAPI() @@ -14,7 +13,7 @@ class FormModel(BaseModel): username: str lastname: str age: Optional[int] = None - tags: List[str] = ["foo", "bar"] + tags: list[str] = ["foo", "bar"] alias_with: str = Field(alias="with", default="nothing") diff --git a/tests/test_forms_single_param.py b/tests/test_forms_single_param.py index 3bb951441f..67f054b34e 100644 --- a/tests/test_forms_single_param.py +++ b/tests/test_forms_single_param.py @@ -1,6 +1,7 @@ +from typing import Annotated + from fastapi import FastAPI, Form from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_generate_unique_id_function.py b/tests/test_generate_unique_id_function.py index 5aeec66367..62ebfbc964 100644 --- a/tests/test_generate_unique_id_function.py +++ b/tests/test_generate_unique_id_function.py @@ -1,5 +1,4 @@ import warnings -from typing import List from fastapi import APIRouter, FastAPI from fastapi.routing import APIRoute @@ -33,12 +32,12 @@ def test_top_level_generate_unique_id(): app = FastAPI(generate_unique_id_function=custom_generate_unique_id) router = APIRouter() - @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}}) + @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}}) def post_root(item1: Item, item2: Item): return item1, item2 # pragma: nocover @router.post( - "/router", response_model=List[Item], responses={404: {"model": List[Message]}} + "/router", response_model=list[Item], responses={404: {"model": list[Message]}} ) def post_router(item1: Item, item2: Item): return item1, item2 # pragma: nocover @@ -234,12 +233,12 @@ def test_router_overrides_generate_unique_id(): app = FastAPI(generate_unique_id_function=custom_generate_unique_id) router = APIRouter(generate_unique_id_function=custom_generate_unique_id2) - @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}}) + @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}}) def post_root(item1: Item, item2: Item): return item1, item2 # pragma: nocover @router.post( - "/router", response_model=List[Item], responses={404: {"model": List[Message]}} + "/router", response_model=list[Item], responses={404: {"model": list[Message]}} ) def post_router(item1: Item, item2: Item): return item1, item2 # pragma: nocover @@ -435,12 +434,12 @@ def test_router_include_overrides_generate_unique_id(): app = FastAPI(generate_unique_id_function=custom_generate_unique_id) router = APIRouter(generate_unique_id_function=custom_generate_unique_id2) - @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}}) + @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}}) def post_root(item1: Item, item2: Item): return item1, item2 # pragma: nocover @router.post( - "/router", response_model=List[Item], responses={404: {"model": List[Message]}} + "/router", response_model=list[Item], responses={404: {"model": list[Message]}} ) def post_router(item1: Item, item2: Item): return item1, item2 # pragma: nocover @@ -637,20 +636,20 @@ def test_subrouter_top_level_include_overrides_generate_unique_id(): router = APIRouter() sub_router = APIRouter(generate_unique_id_function=custom_generate_unique_id2) - @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}}) + @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}}) def post_root(item1: Item, item2: Item): return item1, item2 # pragma: nocover @router.post( - "/router", response_model=List[Item], responses={404: {"model": List[Message]}} + "/router", response_model=list[Item], responses={404: {"model": list[Message]}} ) def post_router(item1: Item, item2: Item): return item1, item2 # pragma: nocover @sub_router.post( "/subrouter", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, ) def post_subrouter(item1: Item, item2: Item): return item1, item2 # pragma: nocover @@ -910,14 +909,14 @@ def test_router_path_operation_overrides_generate_unique_id(): app = FastAPI(generate_unique_id_function=custom_generate_unique_id) router = APIRouter(generate_unique_id_function=custom_generate_unique_id2) - @app.post("/", response_model=List[Item], responses={404: {"model": List[Message]}}) + @app.post("/", response_model=list[Item], responses={404: {"model": list[Message]}}) def post_root(item1: Item, item2: Item): return item1, item2 # pragma: nocover @router.post( "/router", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, generate_unique_id_function=custom_generate_unique_id3, ) def post_router(item1: Item, item2: Item): @@ -1116,8 +1115,8 @@ def test_app_path_operation_overrides_generate_unique_id(): @app.post( "/", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, generate_unique_id_function=custom_generate_unique_id3, ) def post_root(item1: Item, item2: Item): @@ -1125,8 +1124,8 @@ def test_app_path_operation_overrides_generate_unique_id(): @router.post( "/router", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, ) def post_router(item1: Item, item2: Item): return item1, item2 # pragma: nocover @@ -1324,8 +1323,8 @@ def test_callback_override_generate_unique_id(): @callback_router.post( "/post-callback", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, generate_unique_id_function=custom_generate_unique_id3, ) def post_callback(item1: Item, item2: Item): @@ -1333,8 +1332,8 @@ def test_callback_override_generate_unique_id(): @app.post( "/", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, generate_unique_id_function=custom_generate_unique_id3, callbacks=callback_router.routes, ) @@ -1343,8 +1342,8 @@ def test_callback_override_generate_unique_id(): @app.post( "/tocallback", - response_model=List[Item], - responses={404: {"model": List[Message]}}, + response_model=list[Item], + responses={404: {"model": list[Message]}}, ) def post_with_callback(item1: Item, item2: Item): return item1, item2 # pragma: nocover diff --git a/tests/test_generic_parameterless_depends.py b/tests/test_generic_parameterless_depends.py index 5aa35320c9..93b72ad243 100644 --- a/tests/test_generic_parameterless_depends.py +++ b/tests/test_generic_parameterless_depends.py @@ -1,8 +1,7 @@ -from typing import TypeVar +from typing import Annotated, TypeVar from fastapi import Depends, FastAPI from fastapi.testclient import TestClient -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py index 6601585ef0..215d06a072 100644 --- a/tests/test_get_model_definitions_formfeed_escape.py +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -1,4 +1,5 @@ -from typing import Any, Iterator, Set, Type +from collections.abc import Iterator +from typing import Any import fastapi._compat import fastapi.openapi.utils @@ -143,11 +144,11 @@ class SortedTypeSet(set): Set of Types whose `__iter__()` method yields results sorted by the type names """ - def __init__(self, seq: Set[Type[Any]], *, sort_reversed: bool): + def __init__(self, seq: set[type[Any]], *, sort_reversed: bool): super().__init__(seq) self.sort_reversed = sort_reversed - def __iter__(self) -> Iterator[Type[Any]]: + def __iter__(self) -> Iterator[type[Any]]: members_sorted = sorted( super().__iter__(), key=lambda type_: type_.__name__, diff --git a/tests/test_invalid_path_param.py b/tests/test_invalid_path_param.py index d5fa53c1fc..35c00363da 100644 --- a/tests/test_invalid_path_param.py +++ b/tests/test_invalid_path_param.py @@ -1,5 +1,3 @@ -from typing import Dict, List, Tuple - import pytest from fastapi import FastAPI from pydantic import BaseModel @@ -13,7 +11,7 @@ def test_invalid_sequence(): title: str @app.get("/items/{id}") - def read_items(id: List[Item]): + def read_items(id: list[Item]): pass # pragma: no cover @@ -25,7 +23,7 @@ def test_invalid_tuple(): title: str @app.get("/items/{id}") - def read_items(id: Tuple[Item, Item]): + def read_items(id: tuple[Item, Item]): pass # pragma: no cover @@ -37,7 +35,7 @@ def test_invalid_dict(): title: str @app.get("/items/{id}") - def read_items(id: Dict[str, Item]): + def read_items(id: dict[str, Item]): pass # pragma: no cover diff --git a/tests/test_invalid_sequence_param.py b/tests/test_invalid_sequence_param.py index 475786adbf..2b8fd059e1 100644 --- a/tests/test_invalid_sequence_param.py +++ b/tests/test_invalid_sequence_param.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, Tuple +from typing import Optional import pytest from fastapi import FastAPI, Query @@ -13,7 +13,7 @@ def test_invalid_sequence(): title: str @app.get("/items/") - def read_items(q: List[Item] = Query(default=None)): + def read_items(q: list[Item] = Query(default=None)): pass # pragma: no cover @@ -25,7 +25,7 @@ def test_invalid_tuple(): title: str @app.get("/items/") - def read_items(q: Tuple[Item, Item] = Query(default=None)): + def read_items(q: tuple[Item, Item] = Query(default=None)): pass # pragma: no cover @@ -37,7 +37,7 @@ def test_invalid_dict(): title: str @app.get("/items/") - def read_items(q: Dict[str, Item] = Query(default=None)): + def read_items(q: dict[str, Item] = Query(default=None)): pass # pragma: no cover diff --git a/tests/test_multi_body_errors.py b/tests/test_multi_body_errors.py index 33304827a9..6ea405fe70 100644 --- a/tests/test_multi_body_errors.py +++ b/tests/test_multi_body_errors.py @@ -1,5 +1,4 @@ from decimal import Decimal -from typing import List from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI @@ -15,7 +14,7 @@ class Item(BaseModel): @app.post("/items/") -def save_item_no_body(item: List[Item]): +def save_item_no_body(item: list[Item]): return {"item": item} diff --git a/tests/test_multi_query_errors.py b/tests/test_multi_query_errors.py index 8162d986c5..7387a81ddf 100644 --- a/tests/test_multi_query_errors.py +++ b/tests/test_multi_query_errors.py @@ -1,5 +1,3 @@ -from typing import List - from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient @@ -8,7 +6,7 @@ app = FastAPI() @app.get("/items/") -def read_items(q: List[int] = Query(default=None)): +def read_items(q: list[int] = Query(default=None)): return {"q": q} diff --git a/tests/test_no_schema_split.py b/tests/test_no_schema_split.py index b0b5958c1b..6169867ba3 100644 --- a/tests/test_no_schema_split.py +++ b/tests/test_no_schema_split.py @@ -3,7 +3,6 @@ # Made an issue in: # https://github.com/fastapi/fastapi/issues/14247 from enum import Enum -from typing import List from fastapi import FastAPI from fastapi.testclient import TestClient @@ -25,7 +24,7 @@ class MessageEvent(BaseModel): class MessageOutput(BaseModel): body: str = "" - events: List[MessageEvent] = [] + events: list[MessageEvent] = [] class Message(BaseModel): diff --git a/tests/test_openapi_schema_type.py b/tests/test_openapi_schema_type.py index a45ea20c8c..98d9787455 100644 --- a/tests/test_openapi_schema_type.py +++ b/tests/test_openapi_schema_type.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Union +from typing import Optional, Union import pytest from fastapi.openapi.models import Schema, SchemaType @@ -13,7 +13,7 @@ from fastapi.openapi.models import Schema, SchemaType ], ) def test_allowed_schema_type( - type_value: Optional[Union[SchemaType, List[SchemaType]]], + type_value: Optional[Union[SchemaType, list[SchemaType]]], ) -> None: """Test that Schema accepts SchemaType, List[SchemaType] and None for type field.""" schema = Schema(type=type_value) diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index c9a05418bf..bfe5e9a712 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient @@ -11,7 +11,7 @@ from .utils import PYDANTIC_V2, needs_pydanticv2 class SubItem(BaseModel): subname: str sub_description: Optional[str] = None - tags: List[str] = [] + tags: list[str] = [] if PYDANTIC_V2: model_config = {"json_schema_serialization_defaults_required": True} @@ -44,11 +44,11 @@ def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: return item @app.post("/items-list/") - def create_item_list(item: List[Item]): + def create_item_list(item: list[Item]): return item @app.get("/items/") - def read_items() -> List[Item]: + def read_items() -> list[Item]: return [ Item( name="Portal Gun", diff --git a/tests/test_optional_file_list.py b/tests/test_optional_file_list.py index 0228900cf6..6860258643 100644 --- a/tests/test_optional_file_list.py +++ b/tests/test_optional_file_list.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from fastapi import FastAPI, File from fastapi.testclient import TestClient @@ -7,7 +7,7 @@ app = FastAPI() @app.post("/files") -async def upload_files(files: Optional[List[bytes]] = File(None)): +async def upload_files(files: Optional[list[bytes]] = File(None)): if files is None: return {"files_count": 0} return {"files_count": len(files), "sizes": [len(f) for f in files]} diff --git a/tests/test_params_repr.py b/tests/test_params_repr.py index baa172497d..19c2e8d696 100644 --- a/tests/test_params_repr.py +++ b/tests/test_params_repr.py @@ -1,9 +1,9 @@ -from typing import Any, List +from typing import Any from dirty_equals import IsOneOf from fastapi.params import Body, Cookie, Header, Param, Path, Query -test_data: List[Any] = ["teststr", None, ..., 1, []] +test_data: list[Any] = ["teststr", None, ..., 1, []] def get_user(): diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py index 769e5fab62..ebf86b99f0 100644 --- a/tests/test_pydantic_v1_v2_01.py +++ b/tests/test_pydantic_v1_v2_01.py @@ -1,5 +1,5 @@ import sys -from typing import Any, List, Union +from typing import Any, Union from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 @@ -21,7 +21,7 @@ class Item(BaseModel): size: int description: Union[str, None] = None sub: SubItem - multi: List[SubItem] = [] + multi: list[SubItem] = [] app = FastAPI() diff --git a/tests/test_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py index 64f3dd3446..8879b010de 100644 --- a/tests/test_pydantic_v1_v2_list.py +++ b/tests/test_pydantic_v1_v2_list.py @@ -1,5 +1,5 @@ import sys -from typing import Any, List, Union +from typing import Any, Union from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 @@ -21,18 +21,18 @@ class Item(BaseModel): size: int description: Union[str, None] = None sub: SubItem - multi: List[SubItem] = [] + multi: list[SubItem] = [] app = FastAPI() @app.post("/item") -def handle_item(data: Item) -> List[Item]: +def handle_item(data: Item) -> list[Item]: return [data, data] -@app.post("/item-filter", response_model=List[Item]) +@app.post("/item-filter", response_model=list[Item]) def handle_item_filter(data: Item) -> Any: extended_data = data.dict() extended_data.update({"secret_data": "classified", "internal_id": 12345}) @@ -41,14 +41,14 @@ def handle_item_filter(data: Item) -> Any: @app.post("/item-list") -def handle_item_list(data: List[Item]) -> Item: +def handle_item_list(data: list[Item]) -> Item: if data: return data[0] return Item(title="", size=0, sub=SubItem(name="")) @app.post("/item-list-filter", response_model=Item) -def handle_item_list_filter(data: List[Item]) -> Any: +def handle_item_list_filter(data: list[Item]) -> Any: if data: extended_data = data[0].dict() extended_data.update({"secret_data": "classified", "internal_id": 12345}) @@ -58,12 +58,12 @@ def handle_item_list_filter(data: List[Item]) -> Any: @app.post("/item-list-to-list") -def handle_item_list_to_list(data: List[Item]) -> List[Item]: +def handle_item_list_to_list(data: list[Item]) -> list[Item]: return data -@app.post("/item-list-to-list-filter", response_model=List[Item]) -def handle_item_list_to_list_filter(data: List[Item]) -> Any: +@app.post("/item-list-to-list-filter", response_model=list[Item]) +def handle_item_list_to_list_filter(data: list[Item]) -> Any: if data: extended_data = data[0].dict() extended_data.update({"secret_data": "classified", "internal_id": 12345}) diff --git a/tests/test_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py index 54d408827f..e66583cd54 100644 --- a/tests/test_pydantic_v1_v2_mixed.py +++ b/tests/test_pydantic_v1_v2_mixed.py @@ -1,5 +1,5 @@ import sys -from typing import Any, List, Union +from typing import Any, Union from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 @@ -22,7 +22,7 @@ class Item(BaseModel): size: int description: Union[str, None] = None sub: SubItem - multi: List[SubItem] = [] + multi: list[SubItem] = [] class NewSubItem(NewBaseModel): @@ -34,7 +34,7 @@ class NewItem(NewBaseModel): new_size: int new_description: Union[str, None] = None new_sub: NewSubItem - new_multi: List[NewSubItem] = [] + new_multi: list[NewSubItem] = [] app = FastAPI() @@ -93,7 +93,7 @@ def handle_v2_item_to_v1_filter(data: NewItem) -> Any: @app.post("/v1-to-v2/item-to-list") -def handle_v1_item_to_v2_list(data: Item) -> List[NewItem]: +def handle_v1_item_to_v2_list(data: Item) -> list[NewItem]: converted = NewItem( new_title=data.title, new_size=data.size, @@ -105,7 +105,7 @@ def handle_v1_item_to_v2_list(data: Item) -> List[NewItem]: @app.post("/v1-to-v2/list-to-list") -def handle_v1_list_to_v2_list(data: List[Item]) -> List[NewItem]: +def handle_v1_list_to_v2_list(data: list[Item]) -> list[NewItem]: result = [] for item in data: result.append( @@ -120,8 +120,8 @@ def handle_v1_list_to_v2_list(data: List[Item]) -> List[NewItem]: return result -@app.post("/v1-to-v2/list-to-list-filter", response_model=List[NewItem]) -def handle_v1_list_to_v2_list_filter(data: List[Item]) -> Any: +@app.post("/v1-to-v2/list-to-list-filter", response_model=list[NewItem]) +def handle_v1_list_to_v2_list_filter(data: list[Item]) -> Any: result = [] for item in data: converted = { @@ -140,7 +140,7 @@ def handle_v1_list_to_v2_list_filter(data: List[Item]) -> Any: @app.post("/v1-to-v2/list-to-item") -def handle_v1_list_to_v2_item(data: List[Item]) -> NewItem: +def handle_v1_list_to_v2_item(data: list[Item]) -> NewItem: if data: item = data[0] return NewItem( @@ -154,7 +154,7 @@ def handle_v1_list_to_v2_item(data: List[Item]) -> NewItem: @app.post("/v2-to-v1/item-to-list") -def handle_v2_item_to_v1_list(data: NewItem) -> List[Item]: +def handle_v2_item_to_v1_list(data: NewItem) -> list[Item]: converted = Item( title=data.new_title, size=data.new_size, @@ -166,7 +166,7 @@ def handle_v2_item_to_v1_list(data: NewItem) -> List[Item]: @app.post("/v2-to-v1/list-to-list") -def handle_v2_list_to_v1_list(data: List[NewItem]) -> List[Item]: +def handle_v2_list_to_v1_list(data: list[NewItem]) -> list[Item]: result = [] for item in data: result.append( @@ -181,8 +181,8 @@ def handle_v2_list_to_v1_list(data: List[NewItem]) -> List[Item]: return result -@app.post("/v2-to-v1/list-to-list-filter", response_model=List[Item]) -def handle_v2_list_to_v1_list_filter(data: List[NewItem]) -> Any: +@app.post("/v2-to-v1/list-to-list-filter", response_model=list[Item]) +def handle_v2_list_to_v1_list_filter(data: list[NewItem]) -> Any: result = [] for item in data: converted = { @@ -201,7 +201,7 @@ def handle_v2_list_to_v1_list_filter(data: List[NewItem]) -> Any: @app.post("/v2-to-v1/list-to-item") -def handle_v2_list_to_v1_item(data: List[NewItem]) -> Item: +def handle_v2_list_to_v1_item(data: list[NewItem]) -> Item: if data: item = data[0] return Item( diff --git a/tests/test_pydantic_v1_v2_multifile/main.py b/tests/test_pydantic_v1_v2_multifile/main.py index 8985cb7b4c..9397368abf 100644 --- a/tests/test_pydantic_v1_v2_multifile/main.py +++ b/tests/test_pydantic_v1_v2_multifile/main.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import FastAPI from . import modelsv1, modelsv2, modelsv2b @@ -30,7 +28,7 @@ def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item: @app.post("/v1-to-v2/item-to-list") -def handle_v1_item_to_v2_list(data: modelsv1.Item) -> List[modelsv2.Item]: +def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]: converted = modelsv2.Item( new_title=data.title, new_size=data.size, @@ -42,7 +40,7 @@ def handle_v1_item_to_v2_list(data: modelsv1.Item) -> List[modelsv2.Item]: @app.post("/v1-to-v2/list-to-list") -def handle_v1_list_to_v2_list(data: List[modelsv1.Item]) -> List[modelsv2.Item]: +def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]: result = [] for item in data: result.append( @@ -58,7 +56,7 @@ def handle_v1_list_to_v2_list(data: List[modelsv1.Item]) -> List[modelsv2.Item]: @app.post("/v1-to-v2/list-to-item") -def handle_v1_list_to_v2_item(data: List[modelsv1.Item]) -> modelsv2.Item: +def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item: if data: item = data[0] return modelsv2.Item( @@ -74,7 +72,7 @@ def handle_v1_list_to_v2_item(data: List[modelsv1.Item]) -> modelsv2.Item: @app.post("/v2-to-v1/item-to-list") -def handle_v2_item_to_v1_list(data: modelsv2.Item) -> List[modelsv1.Item]: +def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]: converted = modelsv1.Item( title=data.new_title, size=data.new_size, @@ -86,7 +84,7 @@ def handle_v2_item_to_v1_list(data: modelsv2.Item) -> List[modelsv1.Item]: @app.post("/v2-to-v1/list-to-list") -def handle_v2_list_to_v1_list(data: List[modelsv2.Item]) -> List[modelsv1.Item]: +def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]: result = [] for item in data: result.append( @@ -102,7 +100,7 @@ def handle_v2_list_to_v1_list(data: List[modelsv2.Item]) -> List[modelsv1.Item]: @app.post("/v2-to-v1/list-to-item") -def handle_v2_list_to_v1_item(data: List[modelsv2.Item]) -> modelsv1.Item: +def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item: if data: item = data[0] return modelsv1.Item( @@ -130,8 +128,8 @@ def handle_v2_same_name_to_v1( @app.post("/v2-to-v1/list-of-items-to-list-of-items") def handle_v2_items_in_list_to_v1_item_in_list( - data1: List[modelsv2.ItemInList], data2: List[modelsv2b.ItemInList] -) -> List[modelsv1.ItemInList]: + data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList] +) -> list[modelsv1.ItemInList]: result = [] item1 = data1[0] item2 = data2[0] diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv1.py b/tests/test_pydantic_v1_v2_multifile/modelsv1.py index 889291a1a7..0cc8de4559 100644 --- a/tests/test_pydantic_v1_v2_multifile/modelsv1.py +++ b/tests/test_pydantic_v1_v2_multifile/modelsv1.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Union from fastapi._compat.v1 import BaseModel @@ -12,7 +12,7 @@ class Item(BaseModel): size: int description: Union[str, None] = None sub: SubItem - multi: List[SubItem] = [] + multi: list[SubItem] = [] class ItemInList(BaseModel): diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2.py b/tests/test_pydantic_v1_v2_multifile/modelsv2.py index 2c8c6ea356..d80b77e103 100644 --- a/tests/test_pydantic_v1_v2_multifile/modelsv2.py +++ b/tests/test_pydantic_v1_v2_multifile/modelsv2.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Union from pydantic import BaseModel @@ -12,7 +12,7 @@ class Item(BaseModel): new_size: int new_description: Union[str, None] = None new_sub: SubItem - new_multi: List[SubItem] = [] + new_multi: list[SubItem] = [] class ItemInList(BaseModel): diff --git a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py index dc0c06c669..e992bea2e1 100644 --- a/tests/test_pydantic_v1_v2_multifile/modelsv2b.py +++ b/tests/test_pydantic_v1_v2_multifile/modelsv2b.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Union from pydantic import BaseModel @@ -12,7 +12,7 @@ class Item(BaseModel): dup_size: int dup_description: Union[str, None] = None dup_sub: SubItem - dup_multi: List[SubItem] = [] + dup_multi: list[SubItem] = [] class ItemInList(BaseModel): diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py index d2d6f6635b..3e86469908 100644 --- a/tests/test_pydantic_v1_v2_noneable.py +++ b/tests/test_pydantic_v1_v2_noneable.py @@ -1,5 +1,5 @@ import sys -from typing import Any, List, Union +from typing import Any, Union from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 @@ -22,7 +22,7 @@ class Item(BaseModel): size: int description: Union[str, None] = None sub: SubItem - multi: List[SubItem] = [] + multi: list[SubItem] = [] class NewSubItem(NewBaseModel): @@ -34,7 +34,7 @@ class NewItem(NewBaseModel): new_size: int new_description: Union[str, None] = None new_sub: NewSubItem - new_multi: List[NewSubItem] = [] + new_multi: list[NewSubItem] = [] app = FastAPI() diff --git a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py index c9f94563bb..b72b0518a1 100644 --- a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py +++ b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py @@ -2,7 +2,7 @@ from __future__ import annotations import uuid from dataclasses import dataclass, field -from typing import List, Union +from typing import Union from dirty_equals import IsUUID from fastapi import FastAPI @@ -15,7 +15,7 @@ class Item: id: uuid.UUID name: str price: float - tags: List[str] = field(default_factory=list) + tags: list[str] = field(default_factory=list) description: Union[str, None] = None tax: Union[float, None] = None diff --git a/tests/test_regex_deprecated_body.py b/tests/test_regex_deprecated_body.py index 74654ff3ce..cfbff19c87 100644 --- a/tests/test_regex_deprecated_body.py +++ b/tests/test_regex_deprecated_body.py @@ -1,8 +1,9 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient -from typing_extensions import Annotated from .utils import needs_py310 diff --git a/tests/test_regex_deprecated_params.py b/tests/test_regex_deprecated_params.py index 2ce64c6862..7d9988f9f8 100644 --- a/tests/test_regex_deprecated_params.py +++ b/tests/test_regex_deprecated_params.py @@ -1,8 +1,9 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient -from typing_extensions import Annotated from .utils import needs_py310 diff --git a/tests/test_request_body_parameters_media_type.py b/tests/test_request_body_parameters_media_type.py index 8c72fee54a..d1bff9ddf4 100644 --- a/tests/test_request_body_parameters_media_type.py +++ b/tests/test_request_body_parameters_media_type.py @@ -1,5 +1,3 @@ -import typing - from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -28,7 +26,7 @@ async def create_product(data: Product = Body(media_type=media_type, embed=True) @app.post("/shops") async def create_shop( data: Shop = Body(media_type=media_type), - included: typing.List[Product] = Body(default=[], media_type=media_type), + included: list[Product] = Body(default=[], media_type=media_type), ): pass # pragma: no cover diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index 18a2a2f308..b2503a1c44 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -1,11 +1,10 @@ -from typing import List, Union +from typing import Annotated, Union import pytest from dirty_equals import IsDict, IsOneOf, IsPartialDict from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -18,12 +17,12 @@ app = FastAPI() @app.post("/required-list-str", operation_id="required_list_str") -async def read_required_list_str(p: Annotated[List[str], Body(embed=True)]): +async def read_required_list_str(p: Annotated[list[str], Body(embed=True)]): return {"p": p} class BodyModelRequiredListStr(BaseModel): - p: List[str] + p: list[str] @app.post("/model-required-list-str", operation_id="model_required_list_str") @@ -103,13 +102,13 @@ def test_required_list_str(path: str): @app.post("/required-list-alias", operation_id="required_list_alias") async def read_required_list_alias( - p: Annotated[List[str], Body(embed=True, alias="p_alias")], + p: Annotated[list[str], Body(embed=True, alias="p_alias")], ): return {"p": p} class BodyModelRequiredListAlias(BaseModel): - p: List[str] = Field(alias="p_alias") + p: list[str] = Field(alias="p_alias") @app.post("/model-required-list-alias", operation_id="model_required_list_alias") @@ -228,13 +227,13 @@ def test_required_list_alias_by_alias(path: str): "/required-list-validation-alias", operation_id="required_list_validation_alias" ) def read_required_list_validation_alias( - p: Annotated[List[str], Body(embed=True, validation_alias="p_val_alias")], + p: Annotated[list[str], Body(embed=True, validation_alias="p_val_alias")], ): return {"p": p} class BodyModelRequiredListValidationAlias(BaseModel): - p: List[str] = Field(validation_alias="p_val_alias") + p: list[str] = Field(validation_alias="p_val_alias") @app.post( @@ -345,14 +344,14 @@ def test_required_list_validation_alias_by_validation_alias(path: str): ) def read_required_list_alias_and_validation_alias( p: Annotated[ - List[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") + list[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") ], ): return {"p": p} class BodyModelRequiredListAliasAndValidationAlias(BaseModel): - p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias") @app.post( diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py index e2ecdc7f43..ede635f840 100644 --- a/tests/test_request_params/test_body/test_optional_list.py +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -1,11 +1,10 @@ -from typing import List, Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -19,13 +18,13 @@ app = FastAPI() @app.post("/optional-list-str", operation_id="optional_list_str") async def read_optional_list_str( - p: Annotated[Optional[List[str]], Body(embed=True)] = None, + p: Annotated[Optional[list[str]], Body(embed=True)] = None, ): return {"p": p} class BodyModelOptionalListStr(BaseModel): - p: Optional[List[str]] = None + p: Optional[list[str]] = None @app.post("/model-optional-list-str", operation_id="model_optional_list_str") @@ -131,13 +130,13 @@ def test_optional_list_str(path: str): @app.post("/optional-list-alias", operation_id="optional_list_alias") async def read_optional_list_alias( - p: Annotated[Optional[List[str]], Body(embed=True, alias="p_alias")] = None, + p: Annotated[Optional[list[str]], Body(embed=True, alias="p_alias")] = None, ): return {"p": p} class BodyModelOptionalListAlias(BaseModel): - p: Optional[List[str]] = Field(None, alias="p_alias") + p: Optional[list[str]] = Field(None, alias="p_alias") @app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") @@ -264,14 +263,14 @@ def test_optional_list_alias_by_alias(path: str): ) def read_optional_list_validation_alias( p: Annotated[ - Optional[List[str]], Body(embed=True, validation_alias="p_val_alias") + Optional[list[str]], Body(embed=True, validation_alias="p_val_alias") ] = None, ): return {"p": p} class BodyModelOptionalListValidationAlias(BaseModel): - p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") @app.post( @@ -410,7 +409,7 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): ) def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[List[str]], + Optional[list[str]], Body(embed=True, alias="p_alias", validation_alias="p_val_alias"), ] = None, ): @@ -418,7 +417,7 @@ def read_optional_list_alias_and_validation_alias( class BodyModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[List[str]] = Field( + p: Optional[list[str]] = Field( None, alias="p_alias", validation_alias="p_val_alias" ) diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py index a49f5a3675..9e0c7217cc 100644 --- a/tests/test_request_params/test_body/test_optional_str.py +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -1,11 +1,10 @@ -from typing import Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py index 18c660cad2..aa47a4ede3 100644 --- a/tests/test_request_params/test_body/test_required_str.py +++ b/tests/test_request_params/test_body/test_required_str.py @@ -1,11 +1,10 @@ -from typing import Any, Dict, Union +from typing import Annotated, Any, Union import pytest from dirty_equals import IsDict, IsOneOf from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -54,7 +53,7 @@ def test_required_str_schema(path: str): "path", ["/required-str", "/model-required-str"], ) -def test_required_str_missing(path: str, json: Union[Dict[str, Any], None]): +def test_required_str_missing(path: str, json: Union[dict[str, Any], None]): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -140,7 +139,7 @@ def test_required_str_alias_schema(path: str): "path", ["/required-alias", "/model-required-alias"], ) -def test_required_alias_missing(path: str, json: Union[Dict[str, Any], None]): +def test_required_alias_missing(path: str, json: Union[dict[str, Any], None]): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -266,7 +265,7 @@ def test_required_validation_alias_schema(path: str): ], ) def test_required_validation_alias_missing( - path: str, json: Union[Dict[str, Any], None] + path: str, json: Union[dict[str, Any], None] ): client = TestClient(app) response = client.post(path, json=json) @@ -386,7 +385,7 @@ def test_required_alias_and_validation_alias_schema(path: str): ], ) def test_required_alias_and_validation_alias_missing( - path: str, json: Union[Dict[str, Any], None] + path: str, json: Union[dict[str, Any], None] ): client = TestClient(app) response = client.post(path, json=json) diff --git a/tests/test_request_params/test_body/utils.py b/tests/test_request_params/test_body/utils.py index 5151a82d36..bf07394f90 100644 --- a/tests/test_request_params/test_body/utils.py +++ b/tests/test_request_params/test_body/utils.py @@ -1,7 +1,7 @@ -from typing import Any, Dict +from typing import Any -def get_body_model_name(openapi: Dict[str, Any], path: str) -> str: +def get_body_model_name(openapi: dict[str, Any], path: str) -> str: body = openapi["paths"][path]["post"]["requestBody"] body_schema = body["content"]["application/json"]["schema"] return body_schema.get("$ref", "").split("/")[-1] diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py index 1eec45689d..f2d02dae54 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -1,11 +1,10 @@ -from typing import Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py index 6d0fa2ef29..3255857d44 100644 --- a/tests/test_request_params/test_cookie/test_required_str.py +++ b/tests/test_request_params/test_cookie/test_required_str.py @@ -1,9 +1,10 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict, IsOneOf from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index 94a33967f3..52b032e441 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -1,10 +1,9 @@ -from typing import List +from typing import Annotated import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -17,12 +16,12 @@ app = FastAPI() @app.post("/list-bytes", operation_id="list_bytes") -async def read_list_bytes(p: Annotated[List[bytes], File()]): +async def read_list_bytes(p: Annotated[list[bytes], File()]): return {"file_size": [len(file) for file in p]} @app.post("/list-uploadfile", operation_id="list_uploadfile") -async def read_list_uploadfile(p: Annotated[List[UploadFile], File()]): +async def read_list_uploadfile(p: Annotated[list[UploadFile], File()]): return {"file_size": [file.size for file in p]} @@ -122,13 +121,13 @@ def test_list(path: str): @app.post("/list-bytes-alias", operation_id="list_bytes_alias") -async def read_list_bytes_alias(p: Annotated[List[bytes], File(alias="p_alias")]): +async def read_list_bytes_alias(p: Annotated[list[bytes], File(alias="p_alias")]): return {"file_size": [len(file) for file in p]} @app.post("/list-uploadfile-alias", operation_id="list_uploadfile_alias") async def read_list_uploadfile_alias( - p: Annotated[List[UploadFile], File(alias="p_alias")], + p: Annotated[list[UploadFile], File(alias="p_alias")], ): return {"file_size": [file.size for file in p]} @@ -266,7 +265,7 @@ def test_list_alias_by_alias(path: str): @app.post("/list-bytes-validation-alias", operation_id="list_bytes_validation_alias") def read_list_bytes_validation_alias( - p: Annotated[List[bytes], File(validation_alias="p_val_alias")], + p: Annotated[list[bytes], File(validation_alias="p_val_alias")], ): return {"file_size": [len(file) for file in p]} @@ -276,7 +275,7 @@ def read_list_bytes_validation_alias( operation_id="list_uploadfile_validation_alias", ) def read_list_uploadfile_validation_alias( - p: Annotated[List[UploadFile], File(validation_alias="p_val_alias")], + p: Annotated[list[UploadFile], File(validation_alias="p_val_alias")], ): return {"file_size": [file.size for file in p]} @@ -401,7 +400,7 @@ def test_list_validation_alias_by_validation_alias(path: str): operation_id="list_bytes_alias_and_validation_alias", ) def read_list_bytes_alias_and_validation_alias( - p: Annotated[List[bytes], File(alias="p_alias", validation_alias="p_val_alias")], + p: Annotated[list[bytes], File(alias="p_alias", validation_alias="p_val_alias")], ): return {"file_size": [len(file) for file in p]} @@ -412,7 +411,7 @@ def read_list_bytes_alias_and_validation_alias( ) def read_list_uploadfile_alias_and_validation_alias( p: Annotated[ - List[UploadFile], File(alias="p_alias", validation_alias="p_val_alias") + list[UploadFile], File(alias="p_alias", validation_alias="p_val_alias") ], ): return {"file_size": [file.size for file in p]} diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index 2c1ca9530e..b7177e071d 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -1,10 +1,9 @@ -from typing import Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py index 20e36501f8..af9fe552c9 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -1,10 +1,9 @@ -from typing import List, Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -17,13 +16,13 @@ app = FastAPI() @app.post("/optional-list-bytes") -async def read_optional_list_bytes(p: Annotated[Optional[List[bytes]], File()] = None): +async def read_optional_list_bytes(p: Annotated[Optional[list[bytes]], File()] = None): return {"file_size": [len(file) for file in p] if p else None} @app.post("/optional-list-uploadfile") async def read_optional_list_uploadfile( - p: Annotated[Optional[List[UploadFile]], File()] = None, + p: Annotated[Optional[list[UploadFile]], File()] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -103,14 +102,14 @@ def test_optional_list(path: str): @app.post("/optional-list-bytes-alias") async def read_optional_list_bytes_alias( - p: Annotated[Optional[List[bytes]], File(alias="p_alias")] = None, + p: Annotated[Optional[list[bytes]], File(alias="p_alias")] = None, ): return {"file_size": [len(file) for file in p] if p else None} @app.post("/optional-list-uploadfile-alias") async def read_optional_list_uploadfile_alias( - p: Annotated[Optional[List[UploadFile]], File(alias="p_alias")] = None, + p: Annotated[Optional[list[UploadFile]], File(alias="p_alias")] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -204,7 +203,7 @@ def test_optional_list_alias_by_alias(path: str): @app.post("/optional-list-bytes-validation-alias") def read_optional_list_bytes_validation_alias( - p: Annotated[Optional[List[bytes]], File(validation_alias="p_val_alias")] = None, + p: Annotated[Optional[list[bytes]], File(validation_alias="p_val_alias")] = None, ): return {"file_size": [len(file) for file in p] if p else None} @@ -212,7 +211,7 @@ def read_optional_list_bytes_validation_alias( @app.post("/optional-list-uploadfile-validation-alias") def read_optional_list_uploadfile_validation_alias( p: Annotated[ - Optional[List[UploadFile]], File(validation_alias="p_val_alias") + Optional[list[UploadFile]], File(validation_alias="p_val_alias") ] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -314,7 +313,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): @app.post("/optional-list-bytes-alias-and-validation-alias") def read_optional_list_bytes_alias_and_validation_alias( p: Annotated[ - Optional[List[bytes]], File(alias="p_alias", validation_alias="p_val_alias") + Optional[list[bytes]], File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": [len(file) for file in p] if p else None} @@ -323,7 +322,7 @@ def read_optional_list_bytes_alias_and_validation_alias( @app.post("/optional-list-uploadfile-alias-and-validation-alias") def read_optional_list_uploadfile_alias_and_validation_alias( p: Annotated[ - Optional[List[UploadFile]], + Optional[list[UploadFile]], File(alias="p_alias", validation_alias="p_val_alias"), ] = None, ): diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py index f041ac1ccf..2979a1040a 100644 --- a/tests/test_request_params/test_file/test_required.py +++ b/tests/test_request_params/test_file/test_required.py @@ -1,8 +1,9 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_file/utils.py b/tests/test_request_params/test_file/utils.py index e33f64385f..e2a97ccd9b 100644 --- a/tests/test_request_params/test_file/utils.py +++ b/tests/test_request_params/test_file/utils.py @@ -1,7 +1,7 @@ -from typing import Any, Dict +from typing import Any -def get_body_model_name(openapi: Dict[str, Any], path: str) -> str: +def get_body_model_name(openapi: dict[str, Any], path: str) -> str: body = openapi["paths"][path]["post"]["requestBody"] body_schema = body["content"]["multipart/form-data"]["schema"] return body_schema.get("$ref", "").split("/")[-1] diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py index 69a1b6a380..9f45aa755d 100644 --- a/tests/test_request_params/test_form/test_list.py +++ b/tests/test_request_params/test_form/test_list.py @@ -1,11 +1,10 @@ -from typing import List +from typing import Annotated import pytest from dirty_equals import IsDict, IsOneOf, IsPartialDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -18,12 +17,12 @@ app = FastAPI() @app.post("/required-list-str", operation_id="required_list_str") -async def read_required_list_str(p: Annotated[List[str], Form()]): +async def read_required_list_str(p: Annotated[list[str], Form()]): return {"p": p} class FormModelRequiredListStr(BaseModel): - p: List[str] + p: list[str] @app.post("/model-required-list-str", operation_id="model_required_list_str") @@ -101,12 +100,12 @@ def test_required_list_str(path: str): @app.post("/required-list-alias", operation_id="required_list_alias") -async def read_required_list_alias(p: Annotated[List[str], Form(alias="p_alias")]): +async def read_required_list_alias(p: Annotated[list[str], Form(alias="p_alias")]): return {"p": p} class FormModelRequiredListAlias(BaseModel): - p: List[str] = Field(alias="p_alias") + p: list[str] = Field(alias="p_alias") @app.post("/model-required-list-alias", operation_id="model_required_list_alias") @@ -229,13 +228,13 @@ def test_required_list_alias_by_alias(path: str): "/required-list-validation-alias", operation_id="required_list_validation_alias" ) def read_required_list_validation_alias( - p: Annotated[List[str], Form(validation_alias="p_val_alias")], + p: Annotated[list[str], Form(validation_alias="p_val_alias")], ): return {"p": p} class FormModelRequiredListValidationAlias(BaseModel): - p: List[str] = Field(validation_alias="p_val_alias") + p: list[str] = Field(validation_alias="p_val_alias") @app.post( @@ -345,13 +344,13 @@ def test_required_list_validation_alias_by_validation_alias(path: str): operation_id="required_list_alias_and_validation_alias", ) def read_required_list_alias_and_validation_alias( - p: Annotated[List[str], Form(alias="p_alias", validation_alias="p_val_alias")], + p: Annotated[list[str], Form(alias="p_alias", validation_alias="p_val_alias")], ): return {"p": p} class FormModelRequiredListAliasAndValidationAlias(BaseModel): - p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias") @app.post( diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py index 1f779a7ed9..0af6d34777 100644 --- a/tests/test_request_params/test_form/test_optional_list.py +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -1,11 +1,10 @@ -from typing import List, Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -19,13 +18,13 @@ app = FastAPI() @app.post("/optional-list-str", operation_id="optional_list_str") async def read_optional_list_str( - p: Annotated[Optional[List[str]], Form()] = None, + p: Annotated[Optional[list[str]], Form()] = None, ): return {"p": p} class FormModelOptionalListStr(BaseModel): - p: Optional[List[str]] = None + p: Optional[list[str]] = None @app.post("/model-optional-list-str", operation_id="model_optional_list_str") @@ -95,13 +94,13 @@ def test_optional_list_str(path: str): @app.post("/optional-list-alias", operation_id="optional_list_alias") async def read_optional_list_alias( - p: Annotated[Optional[List[str]], Form(alias="p_alias")] = None, + p: Annotated[Optional[list[str]], Form(alias="p_alias")] = None, ): return {"p": p} class FormModelOptionalListAlias(BaseModel): - p: Optional[List[str]] = Field(None, alias="p_alias") + p: Optional[list[str]] = Field(None, alias="p_alias") @app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") @@ -193,13 +192,13 @@ def test_optional_list_alias_by_alias(path: str): "/optional-list-validation-alias", operation_id="optional_list_validation_alias" ) def read_optional_list_validation_alias( - p: Annotated[Optional[List[str]], Form(validation_alias="p_val_alias")] = None, + p: Annotated[Optional[list[str]], Form(validation_alias="p_val_alias")] = None, ): return {"p": p} class FormModelOptionalListValidationAlias(BaseModel): - p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") @app.post( @@ -300,14 +299,14 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): ) def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[List[str]], Form(alias="p_alias", validation_alias="p_val_alias") + Optional[list[str]], Form(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class FormModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[List[str]] = Field( + p: Optional[list[str]] = Field( None, alias="p_alias", validation_alias="p_val_alias" ) diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py index 9698659452..92329216ec 100644 --- a/tests/test_request_params/test_form/test_optional_str.py +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -1,11 +1,10 @@ -from typing import Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py index c901e7b445..1e33038040 100644 --- a/tests/test_request_params/test_form/test_required_str.py +++ b/tests/test_request_params/test_form/test_required_str.py @@ -1,9 +1,10 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_form/utils.py b/tests/test_request_params/test_form/utils.py index d200650dfd..9132173111 100644 --- a/tests/test_request_params/test_form/utils.py +++ b/tests/test_request_params/test_form/utils.py @@ -1,7 +1,7 @@ -from typing import Any, Dict +from typing import Any -def get_body_model_name(openapi: Dict[str, Any], path: str) -> str: +def get_body_model_name(openapi: dict[str, Any], path: str) -> str: body = openapi["paths"][path]["post"]["requestBody"] body_schema = body["content"]["application/x-www-form-urlencoded"]["schema"] return body_schema.get("$ref", "").split("/")[-1] diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py index 4a5f4bb647..62f9f36ff6 100644 --- a/tests/test_request_params/test_header/test_list.py +++ b/tests/test_request_params/test_header/test_list.py @@ -1,11 +1,10 @@ -from typing import List +from typing import Annotated import pytest from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -16,12 +15,12 @@ app = FastAPI() @app.get("/required-list-str") -async def read_required_list_str(p: Annotated[List[str], Header()]): +async def read_required_list_str(p: Annotated[list[str], Header()]): return {"p": p} class HeaderModelRequiredListStr(BaseModel): - p: List[str] + p: list[str] @app.get("/model-required-list-str") @@ -96,12 +95,12 @@ def test_required_list_str(path: str): @app.get("/required-list-alias") -async def read_required_list_alias(p: Annotated[List[str], Header(alias="p_alias")]): +async def read_required_list_alias(p: Annotated[list[str], Header(alias="p_alias")]): return {"p": p} class HeaderModelRequiredListAlias(BaseModel): - p: List[str] = Field(alias="p_alias") + p: list[str] = Field(alias="p_alias") @app.get("/model-required-list-alias") @@ -219,13 +218,13 @@ def test_required_list_alias_by_alias(path: str): @app.get("/required-list-validation-alias") def read_required_list_validation_alias( - p: Annotated[List[str], Header(validation_alias="p_val_alias")], + p: Annotated[list[str], Header(validation_alias="p_val_alias")], ): return {"p": p} class HeaderModelRequiredListValidationAlias(BaseModel): - p: List[str] = Field(validation_alias="p_val_alias") + p: list[str] = Field(validation_alias="p_val_alias") @app.get("/model-required-list-validation-alias") @@ -328,13 +327,13 @@ def test_required_list_validation_alias_by_validation_alias(path: str): @app.get("/required-list-alias-and-validation-alias") def read_required_list_alias_and_validation_alias( - p: Annotated[List[str], Header(alias="p_alias", validation_alias="p_val_alias")], + p: Annotated[list[str], Header(alias="p_alias", validation_alias="p_val_alias")], ): return {"p": p} class HeaderModelRequiredListAliasAndValidationAlias(BaseModel): - p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias") @app.get("/model-required-list-alias-and-validation-alias") diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py index e81025c02b..88243f05a9 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -1,11 +1,10 @@ -from typing import List, Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -17,13 +16,13 @@ app = FastAPI() @app.get("/optional-list-str") async def read_optional_list_str( - p: Annotated[Optional[List[str]], Header()] = None, + p: Annotated[Optional[list[str]], Header()] = None, ): return {"p": p} class HeaderModelOptionalListStr(BaseModel): - p: Optional[List[str]] = None + p: Optional[list[str]] = None @app.get("/model-optional-list-str") @@ -93,13 +92,13 @@ def test_optional_list_str(path: str): @app.get("/optional-list-alias") async def read_optional_list_alias( - p: Annotated[Optional[List[str]], Header(alias="p_alias")] = None, + p: Annotated[Optional[list[str]], Header(alias="p_alias")] = None, ): return {"p": p} class HeaderModelOptionalListAlias(BaseModel): - p: Optional[List[str]] = Field(None, alias="p_alias") + p: Optional[list[str]] = Field(None, alias="p_alias") @app.get("/model-optional-list-alias") @@ -187,13 +186,13 @@ def test_optional_list_alias_by_alias(path: str): @app.get("/optional-list-validation-alias") def read_optional_list_validation_alias( - p: Annotated[Optional[List[str]], Header(validation_alias="p_val_alias")] = None, + p: Annotated[Optional[list[str]], Header(validation_alias="p_val_alias")] = None, ): return {"p": p} class HeaderModelOptionalListValidationAlias(BaseModel): - p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-list-validation-alias") @@ -273,14 +272,14 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): @app.get("/optional-list-alias-and-validation-alias") def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[List[str]], Header(alias="p_alias", validation_alias="p_val_alias") + Optional[list[str]], Header(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class HeaderModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[List[str]] = Field( + p: Optional[list[str]] = Field( None, alias="p_alias", validation_alias="p_val_alias" ) diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py index 5ae9f26701..e40b1669ee 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -1,11 +1,10 @@ -from typing import Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py index d57c66919d..23554d3e4a 100644 --- a/tests/test_request_params/test_header/test_required_str.py +++ b/tests/test_request_params/test_header/test_required_str.py @@ -1,9 +1,10 @@ +from typing import Annotated + import pytest from dirty_equals import AnyThing, IsDict, IsOneOf, IsPartialDict from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py index 6417199674..ecd4eb61cd 100644 --- a/tests/test_request_params/test_path/test_required_str.py +++ b/tests/test_request_params/test_path/test_required_str.py @@ -1,7 +1,8 @@ +from typing import Annotated + import pytest from fastapi import FastAPI, Path from fastapi.testclient import TestClient -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py index 71cbca51fb..6a3000fbf6 100644 --- a/tests/test_request_params/test_query/test_list.py +++ b/tests/test_request_params/test_query/test_list.py @@ -1,11 +1,10 @@ -from typing import List +from typing import Annotated import pytest from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -16,12 +15,12 @@ app = FastAPI() @app.get("/required-list-str") -async def read_required_list_str(p: Annotated[List[str], Query()]): +async def read_required_list_str(p: Annotated[list[str], Query()]): return {"p": p} class QueryModelRequiredListStr(BaseModel): - p: List[str] + p: list[str] @app.get("/model-required-list-str") @@ -96,12 +95,12 @@ def test_required_list_str(path: str): @app.get("/required-list-alias") -async def read_required_list_alias(p: Annotated[List[str], Query(alias="p_alias")]): +async def read_required_list_alias(p: Annotated[list[str], Query(alias="p_alias")]): return {"p": p} class QueryModelRequiredListAlias(BaseModel): - p: List[str] = Field(alias="p_alias") + p: list[str] = Field(alias="p_alias") @app.get("/model-required-list-alias") @@ -219,13 +218,13 @@ def test_required_list_alias_by_alias(path: str): @app.get("/required-list-validation-alias") def read_required_list_validation_alias( - p: Annotated[List[str], Query(validation_alias="p_val_alias")], + p: Annotated[list[str], Query(validation_alias="p_val_alias")], ): return {"p": p} class QueryModelRequiredListValidationAlias(BaseModel): - p: List[str] = Field(validation_alias="p_val_alias") + p: list[str] = Field(validation_alias="p_val_alias") @app.get("/model-required-list-validation-alias") @@ -326,13 +325,13 @@ def test_required_list_validation_alias_by_validation_alias(path: str): @app.get("/required-list-alias-and-validation-alias") def read_required_list_alias_and_validation_alias( - p: Annotated[List[str], Query(alias="p_alias", validation_alias="p_val_alias")], + p: Annotated[list[str], Query(alias="p_alias", validation_alias="p_val_alias")], ): return {"p": p} class QueryModelRequiredListAliasAndValidationAlias(BaseModel): - p: List[str] = Field(alias="p_alias", validation_alias="p_val_alias") + p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias") @app.get("/model-required-list-alias-and-validation-alias") diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py index 5609213364..f4b8ec6a82 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -1,11 +1,10 @@ -from typing import List, Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 @@ -17,13 +16,13 @@ app = FastAPI() @app.get("/optional-list-str") async def read_optional_list_str( - p: Annotated[Optional[List[str]], Query()] = None, + p: Annotated[Optional[list[str]], Query()] = None, ): return {"p": p} class QueryModelOptionalListStr(BaseModel): - p: Optional[List[str]] = None + p: Optional[list[str]] = None @app.get("/model-optional-list-str") @@ -93,13 +92,13 @@ def test_optional_list_str(path: str): @app.get("/optional-list-alias") async def read_optional_list_alias( - p: Annotated[Optional[List[str]], Query(alias="p_alias")] = None, + p: Annotated[Optional[list[str]], Query(alias="p_alias")] = None, ): return {"p": p} class QueryModelOptionalListAlias(BaseModel): - p: Optional[List[str]] = Field(None, alias="p_alias") + p: Optional[list[str]] = Field(None, alias="p_alias") @app.get("/model-optional-list-alias") @@ -187,13 +186,13 @@ def test_optional_list_alias_by_alias(path: str): @app.get("/optional-list-validation-alias") def read_optional_list_validation_alias( - p: Annotated[Optional[List[str]], Query(validation_alias="p_val_alias")] = None, + p: Annotated[Optional[list[str]], Query(validation_alias="p_val_alias")] = None, ): return {"p": p} class QueryModelOptionalListValidationAlias(BaseModel): - p: Optional[List[str]] = Field(None, validation_alias="p_val_alias") + p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-list-validation-alias") @@ -271,14 +270,14 @@ def test_optional_list_validation_alias_by_validation_alias(path: str): @app.get("/optional-list-alias-and-validation-alias") def read_optional_list_alias_and_validation_alias( p: Annotated[ - Optional[List[str]], Query(alias="p_alias", validation_alias="p_val_alias") + Optional[list[str]], Query(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"p": p} class QueryModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[List[str]] = Field( + p: Optional[list[str]] = Field( None, alias="p_alias", validation_alias="p_val_alias" ) diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py index 25e4ea42e6..c7d20e37d1 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -1,11 +1,10 @@ -from typing import Optional +from typing import Annotated, Optional import pytest from dirty_equals import IsDict from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py index aa0731e2c6..ce30f3b1f8 100644 --- a/tests/test_request_params/test_query/test_required_str.py +++ b/tests/test_request_params/test_query/test_required_str.py @@ -1,9 +1,10 @@ +from typing import Annotated + import pytest from dirty_equals import IsDict, IsOneOf from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from typing_extensions import Annotated from tests.utils import needs_pydanticv2 diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py index e162cd39b5..5b241c76b6 100644 --- a/tests/test_response_by_alias.py +++ b/tests/test_response_by_alias.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import FastAPI from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient @@ -45,7 +43,7 @@ def read_model(): return Model(alias="Foo") -@app.get("/list", response_model=List[Model], response_model_by_alias=False) +@app.get("/list", response_model=list[Model], response_model_by_alias=False) def read_list(): return [{"alias": "Foo"}, {"alias": "Bar"}] @@ -60,7 +58,7 @@ def by_alias_model(): return Model(alias="Foo") -@app.get("/by-alias/list", response_model=List[Model]) +@app.get("/by-alias/list", response_model=list[Model]) def by_alias_list(): return [{"alias": "Foo"}, {"alias": "Bar"}] @@ -75,7 +73,7 @@ def no_alias_model(): return ModelNoAlias(name="Foo") -@app.get("/no-alias/list", response_model=List[ModelNoAlias]) +@app.get("/no-alias/list", response_model=list[ModelNoAlias]) def no_alias_list(): return [{"name": "Foo"}, {"name": "Bar"}] diff --git a/tests/test_response_class_no_mediatype.py b/tests/test_response_class_no_mediatype.py index 706929ac36..4dc164bf92 100644 --- a/tests/test_response_class_no_mediatype.py +++ b/tests/test_response_class_no_mediatype.py @@ -1,5 +1,3 @@ -import typing - from fastapi import FastAPI, Response from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -18,7 +16,7 @@ class Error(BaseModel): class JsonApiError(BaseModel): - errors: typing.List[Error] + errors: list[Error] @app.get( diff --git a/tests/test_response_code_no_body.py b/tests/test_response_code_no_body.py index 3ca8708f12..70456a7462 100644 --- a/tests/test_response_code_no_body.py +++ b/tests/test_response_code_no_body.py @@ -1,5 +1,3 @@ -import typing - from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -18,7 +16,7 @@ class Error(BaseModel): class JsonApiError(BaseModel): - errors: typing.List[Error] + errors: list[Error] @app.get( diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index 1745c69b60..44e882a76e 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -1,4 +1,4 @@ -from typing import List, Union +from typing import Union import pytest from fastapi import FastAPI @@ -191,7 +191,7 @@ def response_model_filtering_model_annotation_submodel_return_submodel() -> DBUs return DBUser(name="John", surname="Doe", password_hash="secret") -@app.get("/response_model_list_of_model-no_annotation", response_model=List[User]) +@app.get("/response_model_list_of_model-no_annotation", response_model=list[User]) def response_model_list_of_model_no_annotation(): return [ DBUser(name="John", surname="Doe", password_hash="secret"), @@ -200,7 +200,7 @@ def response_model_list_of_model_no_annotation(): @app.get("/no_response_model-annotation_list_of_model") -def no_response_model_annotation_list_of_model() -> List[User]: +def no_response_model_annotation_list_of_model() -> list[User]: return [ DBUser(name="John", surname="Doe", password_hash="secret"), DBUser(name="Jane", surname="Does", password_hash="secret2"), @@ -208,7 +208,7 @@ def no_response_model_annotation_list_of_model() -> List[User]: @app.get("/no_response_model-annotation_forward_ref_list_of_model") -def no_response_model_annotation_forward_ref_list_of_model() -> "List[User]": +def no_response_model_annotation_forward_ref_list_of_model() -> "list[User]": return [ DBUser(name="John", surname="Doe", password_hash="secret"), DBUser(name="Jane", surname="Does", password_hash="secret2"), diff --git a/tests/test_response_model_data_filter.py b/tests/test_response_model_data_filter.py index a3e0f95f04..358697d6df 100644 --- a/tests/test_response_model_data_filter.py +++ b/tests/test_response_model_data_filter.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -44,7 +42,7 @@ async def read_pet(pet_id: int): return pet -@app.get("/pets/", response_model=List[PetOut]) +@app.get("/pets/", response_model=list[PetOut]) async def read_pets(): user = UserDB( email="johndoe@example.com", diff --git a/tests/test_response_model_data_filter_no_inheritance.py b/tests/test_response_model_data_filter_no_inheritance.py index 64003a8417..c0c2f3a9dc 100644 --- a/tests/test_response_model_data_filter_no_inheritance.py +++ b/tests/test_response_model_data_filter_no_inheritance.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -46,7 +44,7 @@ async def read_pet(pet_id: int): return pet -@app.get("/pets/", response_model=List[PetOut]) +@app.get("/pets/", response_model=list[PetOut]) async def read_pets(): user = UserDB( email="johndoe@example.com", diff --git a/tests/test_response_model_invalid.py b/tests/test_response_model_invalid.py index 88b55a436d..f884b5c74a 100644 --- a/tests/test_response_model_invalid.py +++ b/tests/test_response_model_invalid.py @@ -1,5 +1,3 @@ -from typing import List - import pytest from fastapi import FastAPI from fastapi.exceptions import FastAPIError @@ -22,7 +20,7 @@ def test_invalid_response_model_sub_type_raises(): with pytest.raises(FastAPIError): app = FastAPI() - @app.get("/", response_model=List[NonPydanticModel]) + @app.get("/", response_model=list[NonPydanticModel]) def read_root(): pass # pragma: nocover @@ -40,6 +38,6 @@ def test_invalid_response_model_sub_type_in_responses_raises(): with pytest.raises(FastAPIError): app = FastAPI() - @app.get("/", responses={"500": {"model": List[NonPydanticModel]}}) + @app.get("/", responses={"500": {"model": list[NonPydanticModel]}}) def read_root(): pass # pragma: nocover diff --git a/tests/test_response_model_sub_types.py b/tests/test_response_model_sub_types.py index 660bcee1bb..8036bcb07d 100644 --- a/tests/test_response_model_sub_types.py +++ b/tests/test_response_model_sub_types.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -17,7 +15,7 @@ def valid1(): pass -@app.get("/valid2", responses={"500": {"model": List[int]}}) +@app.get("/valid2", responses={"500": {"model": list[int]}}) def valid2(): pass @@ -27,7 +25,7 @@ def valid3(): pass -@app.get("/valid4", responses={"500": {"model": List[Model]}}) +@app.get("/valid4", responses={"500": {"model": list[Model]}}) def valid4(): pass diff --git a/tests/test_router_events.py b/tests/test_router_events.py index dd7ff3314b..9df299cdaa 100644 --- a/tests/test_router_events.py +++ b/tests/test_router_events.py @@ -1,5 +1,6 @@ +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from typing import AsyncGenerator, Dict, Union +from typing import Union import pytest from fastapi import APIRouter, FastAPI, Request @@ -28,7 +29,7 @@ def test_router_events(state: State) -> None: app = FastAPI() @app.get("/") - def main() -> Dict[str, str]: + def main() -> dict[str, str]: return {"message": "Hello World"} @app.on_event("startup") @@ -96,7 +97,7 @@ def test_app_lifespan_state(state: State) -> None: app = FastAPI(lifespan=lifespan) @app.get("/") - def main() -> Dict[str, str]: + def main() -> dict[str, str]: return {"message": "Hello World"} assert state.app_startup is False @@ -113,19 +114,19 @@ def test_app_lifespan_state(state: State) -> None: def test_router_nested_lifespan_state(state: State) -> None: @asynccontextmanager - async def lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: + async def lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: state.app_startup = True yield {"app": True} state.app_shutdown = True @asynccontextmanager - async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: + async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: state.router_startup = True yield {"router": True} state.router_shutdown = True @asynccontextmanager - async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: + async def subrouter_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: state.sub_router_startup = True yield {"sub_router": True} state.sub_router_shutdown = True @@ -139,7 +140,7 @@ def test_router_nested_lifespan_state(state: State) -> None: app.include_router(router) @app.get("/") - def main(request: Request) -> Dict[str, str]: + def main(request: Request) -> dict[str, str]: assert request.state.app assert request.state.router assert request.state.sub_router @@ -175,7 +176,7 @@ def test_router_nested_lifespan_state_overriding_by_parent() -> None: @asynccontextmanager async def lifespan( app: FastAPI, - ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]: + ) -> AsyncGenerator[dict[str, Union[str, bool]], None]: yield { "app_specific": True, "overridden": "app", @@ -184,7 +185,7 @@ def test_router_nested_lifespan_state_overriding_by_parent() -> None: @asynccontextmanager async def router_lifespan( app: FastAPI, - ) -> AsyncGenerator[Dict[str, Union[str, bool]], None]: + ) -> AsyncGenerator[dict[str, Union[str, bool]], None]: yield { "router_specific": True, "overridden": "router", # should override parent @@ -225,7 +226,7 @@ def test_merged_mixed_state_lifespans() -> None: yield @asynccontextmanager - async def router_lifespan(app: FastAPI) -> AsyncGenerator[Dict[str, bool], None]: + async def router_lifespan(app: FastAPI) -> AsyncGenerator[dict[str, bool], None]: yield {"router": True} @asynccontextmanager diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py index d41f1dc1f0..583007c8b7 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py @@ -1,12 +1,11 @@ # Ref: https://github.com/fastapi/fastapi/issues/14454 -from typing import Optional +from typing import Annotated, Optional from fastapi import APIRouter, Depends, FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient from inline_snapshot import snapshot -from typing_extensions import Annotated oauth2_scheme = OAuth2AuthorizationCodeBearer( authorizationUrl="authorize", diff --git a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py index ff866d4fc9..1c21369d33 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi_simple.py @@ -1,10 +1,11 @@ # Ref: https://github.com/fastapi/fastapi/issues/14454 +from typing import Annotated + from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient from inline_snapshot import snapshot -from typing_extensions import Annotated oauth2_scheme = OAuth2AuthorizationCodeBearer( authorizationUrl="api/oauth/authorize", diff --git a/tests/test_security_scopes.py b/tests/test_security_scopes.py index 248fd2bcc2..fccb026fe2 100644 --- a/tests/test_security_scopes.py +++ b/tests/test_security_scopes.py @@ -1,9 +1,8 @@ -from typing import Dict +from typing import Annotated import pytest from fastapi import Depends, FastAPI, Security from fastapi.testclient import TestClient -from typing_extensions import Annotated @pytest.fixture(name="call_counter") @@ -12,7 +11,7 @@ def call_counter_fixture(): @pytest.fixture(name="app") -def app_fixture(call_counter: Dict[str, int]): +def app_fixture(call_counter: dict[str, int]): def get_db(): call_counter["count"] += 1 return f"db_{call_counter['count']}" @@ -38,7 +37,7 @@ def client_fixture(app: FastAPI): def test_security_scopes_dependency_called_once( - client: TestClient, call_counter: Dict[str, int] + client: TestClient, call_counter: dict[str, int] ): response = client.get("/") diff --git a/tests/test_security_scopes_dont_propagate.py b/tests/test_security_scopes_dont_propagate.py index 2bbcc749d3..c306ed0590 100644 --- a/tests/test_security_scopes_dont_propagate.py +++ b/tests/test_security_scopes_dont_propagate.py @@ -1,11 +1,10 @@ # Ref: https://github.com/tiangolo/fastapi/issues/5623 -from typing import Any, Dict, List +from typing import Annotated, Any from fastapi import FastAPI, Security from fastapi.security import SecurityScopes from fastapi.testclient import TestClient -from typing_extensions import Annotated async def security1(scopes: SecurityScopes): @@ -17,8 +16,8 @@ async def security2(scopes: SecurityScopes): async def dep3( - dep1: Annotated[List[str], Security(security1, scopes=["scope1"])], - dep2: Annotated[List[str], Security(security2, scopes=["scope2"])], + dep1: Annotated[list[str], Security(security1, scopes=["scope1"])], + dep2: Annotated[list[str], Security(security2, scopes=["scope2"])], ): return {"dep1": dep1, "dep2": dep2} @@ -28,7 +27,7 @@ app = FastAPI() @app.get("/scopes") def get_scopes( - dep3: Annotated[Dict[str, Any], Security(dep3, scopes=["scope3"])], + dep3: Annotated[dict[str, Any], Security(dep3, scopes=["scope3"])], ): return dep3 diff --git a/tests/test_security_scopes_sub_dependency.py b/tests/test_security_scopes_sub_dependency.py index 9cc668d8e7..2c64d5f3d5 100644 --- a/tests/test_security_scopes_sub_dependency.py +++ b/tests/test_security_scopes_sub_dependency.py @@ -1,12 +1,12 @@ # Ref: https://github.com/fastapi/fastapi/discussions/6024#discussioncomment-8541913 -from typing import Dict + +from typing import Annotated import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import SecurityScopes from fastapi.testclient import TestClient -from typing_extensions import Annotated @pytest.fixture(name="call_counts") @@ -20,7 +20,7 @@ def call_counts_fixture(): @pytest.fixture(name="app") -def app_fixture(call_counts: Dict[str, int]): +def app_fixture(call_counts: dict[str, int]): def get_db_session(): call_counts["get_db_session"] += 1 return f"db_session_{call_counts['get_db_session']}" @@ -75,7 +75,7 @@ def client_fixture(app: FastAPI): def test_security_scopes_sub_dependency_caching( - client: TestClient, call_counts: Dict[str, int] + client: TestClient, call_counts: dict[str, int] ): response = client.get("/") diff --git a/tests/test_serialize_response.py b/tests/test_serialize_response.py index d823e5e04a..14f88dd931 100644 --- a/tests/test_serialize_response.py +++ b/tests/test_serialize_response.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient @@ -10,7 +10,7 @@ app = FastAPI() class Item(BaseModel): name: str price: Optional[float] = None - owner_ids: Optional[List[int]] = None + owner_ids: Optional[list[int]] = None @app.get("/items/valid", response_model=Item) @@ -23,7 +23,7 @@ def get_coerce(): return {"name": "coerce", "price": "1.0"} -@app.get("/items/validlist", response_model=List[Item]) +@app.get("/items/validlist", response_model=list[Item]) def get_validlist(): return [ {"name": "foo"}, diff --git a/tests/test_serialize_response_dataclass.py b/tests/test_serialize_response_dataclass.py index 1e3bf3b28b..ee695368b8 100644 --- a/tests/test_serialize_response_dataclass.py +++ b/tests/test_serialize_response_dataclass.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from datetime import datetime -from typing import List, Optional +from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient @@ -13,7 +13,7 @@ class Item: name: str date: datetime price: Optional[float] = None - owner_ids: Optional[List[int]] = None + owner_ids: Optional[list[int]] = None @app.get("/items/valid", response_model=Item) @@ -33,7 +33,7 @@ def get_coerce(): return {"name": "coerce", "date": datetime(2021, 7, 26).isoformat(), "price": "1.0"} -@app.get("/items/validlist", response_model=List[Item]) +@app.get("/items/validlist", response_model=list[Item]) def get_validlist(): return [ {"name": "foo", "date": datetime(2021, 7, 26)}, @@ -47,7 +47,7 @@ def get_validlist(): ] -@app.get("/items/objectlist", response_model=List[Item]) +@app.get("/items/objectlist", response_model=list[Item]) def get_objectlist(): return [ Item(name="foo", date=datetime(2021, 7, 26)), diff --git a/tests/test_serialize_response_model.py b/tests/test_serialize_response_model.py index 3bb46b2e9b..79c90c9c29 100644 --- a/tests/test_serialize_response_model.py +++ b/tests/test_serialize_response_model.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Optional from fastapi import FastAPI from pydantic import BaseModel, Field @@ -10,7 +10,7 @@ app = FastAPI() class Item(BaseModel): name: str = Field(alias="aliased_name") price: Optional[float] = None - owner_ids: Optional[List[int]] = None + owner_ids: Optional[list[int]] = None @app.get("/items/valid", response_model=Item) @@ -23,7 +23,7 @@ def get_coerce(): return Item(aliased_name="coerce", price="1.0") -@app.get("/items/validlist", response_model=List[Item]) +@app.get("/items/validlist", response_model=list[Item]) def get_validlist(): return [ Item(aliased_name="foo"), @@ -32,7 +32,7 @@ def get_validlist(): ] -@app.get("/items/validdict", response_model=Dict[str, Item]) +@app.get("/items/validdict", response_model=dict[str, Item]) def get_validdict(): return { "k1": Item(aliased_name="foo"), @@ -59,7 +59,7 @@ def get_coerce_exclude_unset(): @app.get( "/items/validlist-exclude-unset", - response_model=List[Item], + response_model=list[Item], response_model_exclude_unset=True, ) def get_validlist_exclude_unset(): @@ -72,7 +72,7 @@ def get_validlist_exclude_unset(): @app.get( "/items/validdict-exclude-unset", - response_model=Dict[str, Item], + response_model=dict[str, Item], response_model_exclude_unset=True, ) def get_validdict_exclude_unset(): diff --git a/tests/test_stringified_annotation_dependency.py b/tests/test_stringified_annotation_dependency.py index 89bb884b5c..ce88074957 100644 --- a/tests/test_stringified_annotation_dependency.py +++ b/tests/test_stringified_annotation_dependency.py @@ -1,12 +1,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Annotated import pytest from fastapi import Depends, FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot -from typing_extensions import Annotated if TYPE_CHECKING: # pragma: no cover from collections.abc import AsyncGenerator diff --git a/tests/test_stringified_annotations_simple.py b/tests/test_stringified_annotations_simple.py index 9bd6d09d63..6e037fb21f 100644 --- a/tests/test_stringified_annotations_simple.py +++ b/tests/test_stringified_annotations_simple.py @@ -1,8 +1,9 @@ from __future__ import annotations +from typing import Annotated + from fastapi import Depends, FastAPI, Request from fastapi.testclient import TestClient -from typing_extensions import Annotated from .utils import needs_py310 diff --git a/tests/test_tuples.py b/tests/test_tuples.py index ca33d2580c..fbc69a6145 100644 --- a/tests/test_tuples.py +++ b/tests/test_tuples.py @@ -1,5 +1,3 @@ -from typing import List, Tuple - from dirty_equals import IsDict from fastapi import FastAPI, Form from fastapi.testclient import TestClient @@ -9,7 +7,7 @@ app = FastAPI() class ItemGroup(BaseModel): - items: List[Tuple[str, str]] + items: list[tuple[str, str]] class Coordinate(BaseModel): @@ -23,12 +21,12 @@ def post_model_with_tuple(item_group: ItemGroup): @app.post("/tuple-of-models/") -def post_tuple_of_models(square: Tuple[Coordinate, Coordinate]): +def post_tuple_of_models(square: tuple[Coordinate, Coordinate]): return square @app.post("/tuple-form/") -def hello(values: Tuple[int, int] = Form()): +def hello(values: tuple[int, int] = Form()): return values diff --git a/tests/test_tutorial/test_websockets/test_tutorial003.py b/tests/test_tutorial/test_websockets/test_tutorial003.py index f303990f03..0be1fc81d6 100644 --- a/tests/test_tutorial/test_websockets/test_tutorial003.py +++ b/tests/test_tutorial/test_websockets/test_tutorial003.py @@ -35,9 +35,10 @@ def test_get(client: TestClient, html: str): def test_websocket_handle_disconnection(client: TestClient): - with client.websocket_connect("/ws/1234") as connection, client.websocket_connect( - "/ws/5678" - ) as connection_two: + with ( + client.websocket_connect("/ws/1234") as connection, + client.websocket_connect("/ws/5678") as connection_two, + ): connection.send_text("Hello from 1234") data1 = connection.receive_text() assert data1 == "You wrote: Hello from 1234" diff --git a/tests/test_union_body_discriminator.py b/tests/test_union_body_discriminator.py index 6af9e1d226..bf41a72915 100644 --- a/tests/test_union_body_discriminator.py +++ b/tests/test_union_body_discriminator.py @@ -1,11 +1,11 @@ -from typing import Any, Dict, Union +from typing import Annotated, Any, Union from dirty_equals import IsDict from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel, Field -from typing_extensions import Annotated, Literal +from typing_extensions import Literal from .utils import needs_pydanticv2 @@ -32,7 +32,7 @@ def test_discriminator_pydantic_v2() -> None: @app.post("/items/") def save_union_body_discriminator( item: Item, q: Annotated[str, Field(description="Query string")] - ) -> Dict[str, Any]: + ) -> dict[str, Any]: return {"item": item} client = TestClient(app) diff --git a/tests/test_union_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py index 14145e6f60..f8108c8df4 100644 --- a/tests/test_union_body_discriminator_annotated.py +++ b/tests/test_union_body_discriminator_annotated.py @@ -1,13 +1,12 @@ # Ref: https://github.com/fastapi/fastapi/discussions/14495 -from typing import Union +from typing import Annotated, Union import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel -from typing_extensions import Annotated from .utils import needs_pydanticv2 diff --git a/tests/test_union_forms.py b/tests/test_union_forms.py index cbe98ea825..018949f0c7 100644 --- a/tests/test_union_forms.py +++ b/tests/test_union_forms.py @@ -1,9 +1,8 @@ -from typing import Union +from typing import Annotated, Union from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_validate_response.py b/tests/test_validate_response.py index cd97007a44..938d419566 100644 --- a/tests/test_validate_response.py +++ b/tests/test_validate_response.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Union +from typing import Optional, Union import pytest from fastapi import FastAPI @@ -12,7 +12,7 @@ app = FastAPI() class Item(BaseModel): name: str price: Optional[float] = None - owner_ids: Optional[List[int]] = None + owner_ids: Optional[list[int]] = None @app.get("/items/invalid", response_model=Item) @@ -38,7 +38,7 @@ def get_innerinvalid(): return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]} -@app.get("/items/invalidlist", response_model=List[Item]) +@app.get("/items/invalidlist", response_model=list[Item]) def get_invalidlist(): return [ {"name": "foo"}, diff --git a/tests/test_validate_response_dataclass.py b/tests/test_validate_response_dataclass.py index 0415988a0b..67282bcde1 100644 --- a/tests/test_validate_response_dataclass.py +++ b/tests/test_validate_response_dataclass.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Optional import pytest from fastapi import FastAPI @@ -13,7 +13,7 @@ app = FastAPI() class Item: name: str price: Optional[float] = None - owner_ids: Optional[List[int]] = None + owner_ids: Optional[list[int]] = None @app.get("/items/invalid", response_model=Item) @@ -26,7 +26,7 @@ def get_innerinvalid(): return {"name": "double invalid", "price": "foo", "owner_ids": ["foo", "bar"]} -@app.get("/items/invalidlist", response_model=List[Item]) +@app.get("/items/invalidlist", response_model=list[Item]) def get_invalidlist(): return [ {"name": "foo"}, diff --git a/tests/test_validate_response_recursive/app.py b/tests/test_validate_response_recursive/app.py index d23d279808..8f76572ba6 100644 --- a/tests/test_validate_response_recursive/app.py +++ b/tests/test_validate_response_recursive/app.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi import FastAPI from fastapi._compat import PYDANTIC_V2 from pydantic import BaseModel @@ -8,17 +6,17 @@ app = FastAPI() class RecursiveItem(BaseModel): - sub_items: List["RecursiveItem"] = [] + sub_items: list["RecursiveItem"] = [] name: str class RecursiveSubitemInSubmodel(BaseModel): - sub_items2: List["RecursiveItemViaSubmodel"] = [] + sub_items2: list["RecursiveItemViaSubmodel"] = [] name: str class RecursiveItemViaSubmodel(BaseModel): - sub_items1: List[RecursiveSubitemInSubmodel] = [] + sub_items1: list[RecursiveSubitemInSubmodel] = [] name: str diff --git a/tests/test_webhooks_security.py b/tests/test_webhooks_security.py index 21a694cb54..982ae1e21d 100644 --- a/tests/test_webhooks_security.py +++ b/tests/test_webhooks_security.py @@ -1,10 +1,10 @@ from datetime import datetime +from typing import Annotated from fastapi import FastAPI, Security from fastapi.security import HTTPBearer from fastapi.testclient import TestClient from pydantic import BaseModel -from typing_extensions import Annotated app = FastAPI() diff --git a/tests/test_ws_dependencies.py b/tests/test_ws_dependencies.py index ccb1c4b7da..51b982c00e 100644 --- a/tests/test_ws_dependencies.py +++ b/tests/test_ws_dependencies.py @@ -1,16 +1,15 @@ import json -from typing import List +from typing import Annotated from fastapi import APIRouter, Depends, FastAPI, WebSocket from fastapi.testclient import TestClient -from typing_extensions import Annotated -def dependency_list() -> List[str]: +def dependency_list() -> list[str]: return [] -DepList = Annotated[List[str], Depends(dependency_list)] +DepList = Annotated[list[str], Depends(dependency_list)] def create_dependency(name: str): From 3f75f512555b66988e169384c95da3b458352063 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Dec 2025 21:26:22 +0000 Subject: [PATCH 111/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 5db95c2964..f1cb43dbd9 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -11,6 +11,10 @@ hide: * 🔧 Drop support for Python 3.8. PR [#14563](https://github.com/fastapi/fastapi/pull/14563) by [@tiangolo](https://github.com/tiangolo). +### Refactors + +* ♻️ Upgrade internal syntax to Python 3.9+ 🎉. PR [#14564](https://github.com/fastapi/fastapi/pull/14564) by [@tiangolo](https://github.com/tiangolo). + ### Docs * ⚰️ Remove Python 3.8 from CI and remove Python 3.8 examples from source docs. PR [#14559](https://github.com/fastapi/fastapi/pull/14559) by [@tiangolo](https://github.com/tiangolo). From 241ca9a533590f16fe2e3ecebd79054d4925c521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 22:35:20 +0100 Subject: [PATCH 112/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f1cb43dbd9..058457bfa2 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -10,6 +10,7 @@ hide: ### Breaking Changes * 🔧 Drop support for Python 3.8. PR [#14563](https://github.com/fastapi/fastapi/pull/14563) by [@tiangolo](https://github.com/tiangolo). + * This would actually not be a _breaking_ change as no code would really break. Any Python 3.8 installer would just refuse to install the latest version of FastAPI and would only install 0.124.4. Only marking it as a "breaking change" to make it visible. ### Refactors @@ -17,7 +18,7 @@ hide: ### Docs -* ⚰️ Remove Python 3.8 from CI and remove Python 3.8 examples from source docs. PR [#14559](https://github.com/fastapi/fastapi/pull/14559) by [@tiangolo](https://github.com/tiangolo). +* ⚰️ Remove Python 3.8 from CI and remove Python 3.8 examples from source docs. PR [#14559](https://github.com/fastapi/fastapi/pull/14559) by [@YuriiMotov](https://github.com/YuriiMotov) and [@tiangolo](https://github.com/tiangolo). ### Translations From c75f17d483bfc6f79cc94d1d6acfc6b080005eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 17 Dec 2025 22:37:19 +0100 Subject: [PATCH 113/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 058457bfa2..6c771e34d5 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.125.0 + ### Breaking Changes * 🔧 Drop support for Python 3.8. PR [#14563](https://github.com/fastapi/fastapi/pull/14563) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index e02969c55f..2f13448467 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.124.4" +__version__ = "0.125.0" from starlette import status as status From caee1d3123113808f6f3fbfb78a5ff809e9e085b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 18 Dec 2025 05:24:09 -0800 Subject: [PATCH 114/140] =?UTF-8?q?=F0=9F=91=B7=20Add=20performance=20test?= =?UTF-8?q?s=20with=20CodSpeed=20(#14558)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .github/workflows/test.yml | 13 + .gitignore | 2 + requirements-tests.txt | 1 + tests/benchmarks/__init__.py | 0 tests/benchmarks/test_general_performance.py | 396 +++++++++++++++++++ 5 files changed, 412 insertions(+) create mode 100644 tests/benchmarks/__init__.py create mode 100644 tests/benchmarks/test_general_performance.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a839a928a..cc906eaf6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,6 +66,10 @@ jobs: python-version: "3.13" pydantic-version: "pydantic>=2.0.2,<3.0.0" coverage: coverage + - os: ubuntu-latest + python-version: "3.13" + pydantic-version: "pydantic>=2.0.2,<3.0.0" + coverage: coverage - os: ubuntu-latest python-version: "3.14" pydantic-version: "pydantic>=2.0.2,<3.0.0" @@ -100,6 +104,15 @@ jobs: env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} + - name: CodSpeed benchmarks + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' && matrix.pydantic-version == 'pydantic>=2.0.2,<3.0.0' + uses: CodSpeedHQ/action@v4 + env: + COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} + CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} + with: + mode: simulation + run: coverage run -m pytest tests/ --codspeed # Do not store coverage for all possible combinations to avoid file size max errors in Smokeshow - name: Store coverage files if: matrix.coverage == 'coverage' diff --git a/.gitignore b/.gitignore index 6016ffa598..3dc12ca951 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ archive.zip # Ignore while the setup still depends on requirements.txt files uv.lock + +.codspeed diff --git a/requirements-tests.txt b/requirements-tests.txt index c5de4157e7..ee188b496c 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -11,6 +11,7 @@ PyJWT==2.9.0 pyyaml >=5.3.1,<7.0.0 pwdlib[argon2] >=0.2.1 inline-snapshot>=0.21.1 +pytest-codspeed==4.2.0 # types types-ujson ==5.10.0.20240515 types-orjson ==3.6.2 diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py new file mode 100644 index 0000000000..dca3613d00 --- /dev/null +++ b/tests/benchmarks/test_general_performance.py @@ -0,0 +1,396 @@ +import json +import sys +from collections.abc import Iterator +from typing import Annotated, Any + +import pytest +from fastapi import Depends, FastAPI +from fastapi.testclient import TestClient + +if "--codspeed" not in sys.argv: + pytest.skip( + "Benchmark tests are skipped by default; run with --codspeed.", + allow_module_level=True, + ) + +LARGE_ITEMS: list[dict[str, Any]] = [ + { + "id": i, + "name": f"item-{i}", + "values": list(range(25)), + "meta": { + "active": True, + "group": i % 10, + "tag": f"t{i % 5}", + }, + } + for i in range(300) +] + +LARGE_METADATA: dict[str, Any] = { + "source": "benchmark", + "version": 1, + "flags": {"a": True, "b": False, "c": True}, + "notes": ["x" * 50, "y" * 50, "z" * 50], +} + +LARGE_PAYLOAD: dict[str, Any] = {"items": LARGE_ITEMS, "metadata": LARGE_METADATA} + + +def dep_a(): + return 40 + + +def dep_b(a: Annotated[int, Depends(dep_a)]): + return a + 2 + + +@pytest.fixture( + scope="module", + params=[ + "pydantic-v2", + "pydantic-v1", + ], +) +def basemodel_class(request: pytest.FixtureRequest) -> type[Any]: + if request.param == "pydantic-v2": + from pydantic import BaseModel + + return BaseModel + else: + from pydantic.v1 import BaseModel + + return BaseModel + + +@pytest.fixture(scope="module") +def app(basemodel_class: type[Any]) -> FastAPI: + class ItemIn(basemodel_class): + name: str + value: int + + class ItemOut(basemodel_class): + name: str + value: int + dep: int + + class LargeIn(basemodel_class): + items: list[dict[str, Any]] + metadata: dict[str, Any] + + class LargeOut(basemodel_class): + items: list[dict[str, Any]] + metadata: dict[str, Any] + + app = FastAPI() + + @app.post("/sync/validated", response_model=ItemOut) + def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name=item.name, value=item.value, dep=dep) + + @app.get("/sync/dict-no-response-model") + def sync_dict_no_response_model(): + return {"name": "foo", "value": 123} + + @app.get("/sync/dict-with-response-model", response_model=ItemOut) + def sync_dict_with_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return {"name": "foo", "value": 123, "dep": dep} + + @app.get("/sync/model-no-response-model") + def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name="foo", value=123, dep=dep) + + @app.get("/sync/model-with-response-model", response_model=ItemOut) + def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name="foo", value=123, dep=dep) + + @app.post("/async/validated", response_model=ItemOut) + async def async_validated( + item: ItemIn, + dep: Annotated[int, Depends(dep_b)], + ): + return ItemOut(name=item.name, value=item.value, dep=dep) + + @app.post("/sync/large-receive") + def sync_large_receive(payload: LargeIn): + return {"received": len(payload.items)} + + @app.post("/async/large-receive") + async def async_large_receive(payload: LargeIn): + return {"received": len(payload.items)} + + @app.get("/sync/large-dict-no-response-model") + def sync_large_dict_no_response_model(): + return LARGE_PAYLOAD + + @app.get("/sync/large-dict-with-response-model", response_model=LargeOut) + def sync_large_dict_with_response_model(): + return LARGE_PAYLOAD + + @app.get("/sync/large-model-no-response-model") + def sync_large_model_no_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + @app.get("/sync/large-model-with-response-model", response_model=LargeOut) + def sync_large_model_with_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + @app.get("/async/large-dict-no-response-model") + async def async_large_dict_no_response_model(): + return LARGE_PAYLOAD + + @app.get("/async/large-dict-with-response-model", response_model=LargeOut) + async def async_large_dict_with_response_model(): + return LARGE_PAYLOAD + + @app.get("/async/large-model-no-response-model") + async def async_large_model_no_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + @app.get("/async/large-model-with-response-model", response_model=LargeOut) + async def async_large_model_with_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + + @app.get("/async/dict-no-response-model") + async def async_dict_no_response_model(): + return {"name": "foo", "value": 123} + + @app.get("/async/dict-with-response-model", response_model=ItemOut) + async def async_dict_with_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return {"name": "foo", "value": 123, "dep": dep} + + @app.get("/async/model-no-response-model") + async def async_model_no_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return ItemOut(name="foo", value=123, dep=dep) + + @app.get("/async/model-with-response-model", response_model=ItemOut) + async def async_model_with_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return ItemOut(name="foo", value=123, dep=dep) + + return app + + +@pytest.fixture(scope="module") +def client(app: FastAPI) -> Iterator[TestClient]: + with TestClient(app) as client: + yield client + + +def _bench_get(benchmark, client: TestClient, path: str) -> tuple[int, bytes]: + warmup = client.get(path) + assert warmup.status_code == 200 + + def do_request() -> tuple[int, bytes]: + response = client.get(path) + return response.status_code, response.content + + return benchmark(do_request) + + +def _bench_post_json( + benchmark, client: TestClient, path: str, json: dict[str, Any] +) -> tuple[int, bytes]: + warmup = client.post(path, json=json) + assert warmup.status_code == 200 + + def do_request() -> tuple[int, bytes]: + response = client.post(path, json=json) + return response.status_code, response.content + + return benchmark(do_request) + + +def test_sync_receiving_validated_pydantic_model(benchmark, client: TestClient) -> None: + status_code, body = _bench_post_json( + benchmark, + client, + "/sync/validated", + json={"name": "foo", "value": 123}, + ) + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_sync_return_dict_without_response_model(benchmark, client: TestClient) -> None: + status_code, body = _bench_get(benchmark, client, "/sync/dict-no-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123}' + + +def test_sync_return_dict_with_response_model(benchmark, client: TestClient) -> None: + status_code, body = _bench_get(benchmark, client, "/sync/dict-with-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_sync_return_model_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get(benchmark, client, "/sync/model-no-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_sync_return_model_with_response_model(benchmark, client: TestClient) -> None: + status_code, body = _bench_get(benchmark, client, "/sync/model-with-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_async_receiving_validated_pydantic_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_post_json( + benchmark, client, "/async/validated", json={"name": "foo", "value": 123} + ) + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_async_return_dict_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get(benchmark, client, "/async/dict-no-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123}' + + +def test_async_return_dict_with_response_model(benchmark, client: TestClient) -> None: + status_code, body = _bench_get(benchmark, client, "/async/dict-with-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_async_return_model_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get(benchmark, client, "/async/model-no-response-model") + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_async_return_model_with_response_model(benchmark, client: TestClient) -> None: + status_code, body = _bench_get( + benchmark, client, "/async/model-with-response-model" + ) + assert status_code == 200 + assert body == b'{"name":"foo","value":123,"dep":42}' + + +def test_sync_receiving_large_payload(benchmark, client: TestClient) -> None: + status_code, body = _bench_post_json( + benchmark, + client, + "/sync/large-receive", + json=LARGE_PAYLOAD, + ) + assert status_code == 200 + assert body == b'{"received":300}' + + +def test_async_receiving_large_payload(benchmark, client: TestClient) -> None: + status_code, body = _bench_post_json( + benchmark, + client, + "/async/large-receive", + json=LARGE_PAYLOAD, + ) + assert status_code == 200 + assert body == b'{"received":300}' + + +def _expected_large_payload_json_bytes() -> bytes: + return json.dumps( + LARGE_PAYLOAD, + ensure_ascii=False, + allow_nan=False, + separators=(",", ":"), + ).encode("utf-8") + + +def test_sync_return_large_dict_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/sync/large-dict-no-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_sync_return_large_dict_with_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/sync/large-dict-with-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_sync_return_large_model_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/sync/large-model-no-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_sync_return_large_model_with_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/sync/large-model-with-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_async_return_large_dict_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/async/large-dict-no-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_async_return_large_dict_with_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/async/large-dict-with-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_async_return_large_model_without_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/async/large-model-no-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() + + +def test_async_return_large_model_with_response_model( + benchmark, client: TestClient +) -> None: + status_code, body = _bench_get( + benchmark, client, "/async/large-model-with-response-model" + ) + assert status_code == 200 + assert body == _expected_large_payload_json_bytes() From f58d846015ca2d6fa50536160827444b5f999416 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Dec 2025 13:24:47 +0000 Subject: [PATCH 115/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 6c771e34d5..ebec6bff66 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 👷 Add performance tests with CodSpeed. PR [#14558](https://github.com/fastapi/fastapi/pull/14558) by [@tiangolo](https://github.com/tiangolo). + ## 0.125.0 ### Breaking Changes From 09ab90ed3598fbb2fa255229191b90b74d67505f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 19 Dec 2025 04:44:55 -0800 Subject: [PATCH 116/140] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Use=20prek=20as=20?= =?UTF-8?q?a=20pre-commit=20alternative=20(#14572)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .github/workflows/pre-commit.yml | 16 ++++++++++------ requirements.txt | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index fa0574d7d1..e628ce541d 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -21,14 +21,21 @@ jobs: name: Checkout PR for own repo if: env.IS_FORK == 'false' with: - # To be able to commit it needs more than the last commit + # To be able to commit it needs to fetch the head of the branch, not the + # merge commit ref: ${{ github.head_ref }} + # And it needs the full history to be able to compute diffs + fetch-depth: 0 # A token other than the default GITHUB_TOKEN is needed to be able to trigger CI token: ${{ secrets.PRE_COMMIT }} # pre-commit lite ci needs the default checkout configs to work - uses: actions/checkout@v5 name: Checkout PR for fork if: env.IS_FORK == 'true' + with: + # To be able to commit it needs the head branch of the PR, the remote one + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v6 with: @@ -44,12 +51,9 @@ jobs: run: | uv venv uv pip install -r requirements.txt - - name: Run pre-commit + - name: Run prek - pre-commit id: precommit - run: | - # Fetch the base branch for comparison - git fetch origin ${{ github.base_ref }} - uvx pre-commit run --from-ref origin/${{ github.base_ref }} --to-ref HEAD --show-diff-on-failure + run: uvx prek run --from-ref origin/${GITHUB_BASE_REF} --to-ref HEAD --show-diff-on-failure continue-on-error: true - name: Commit and push changes if: env.IS_FORK == 'false' diff --git a/requirements.txt b/requirements.txt index 1cff1a5a9b..3bbab64a42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,6 @@ -r requirements-tests.txt -r requirements-docs.txt -r requirements-translations.txt -pre-commit >=4.5.0,<5.0.0 +prek==0.2.22 # For generating screenshots playwright From 19abc42efe7385d836267782818043ef0876fd0e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 Dec 2025 12:45:20 +0000 Subject: [PATCH 117/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ebec6bff66..ee1a6ab135 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* ⬆️ Use prek as a pre-commit alternative. PR [#14572](https://github.com/fastapi/fastapi/pull/14572) by [@tiangolo](https://github.com/tiangolo). * 👷 Add performance tests with CodSpeed. PR [#14558](https://github.com/fastapi/fastapi/pull/14558) by [@tiangolo](https://github.com/tiangolo). ## 0.125.0 From 75d4f9c09837901d60fe910cad40d0a42a99b586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 19 Dec 2025 04:51:53 -0800 Subject: [PATCH 118/140] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20Ukrainian,=20generated=20from=20the=20existing=20tran?= =?UTF-8?q?slations=20(#14548)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/uk/llm-prompt.md | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/uk/llm-prompt.md diff --git a/docs/uk/llm-prompt.md b/docs/uk/llm-prompt.md new file mode 100644 index 0000000000..d55d36ab5c --- /dev/null +++ b/docs/uk/llm-prompt.md @@ -0,0 +1,46 @@ +### Target language + +Translate to Ukrainian (українська). + +Language code: uk. + +### Grammar and tone + +1) Use polite/formal address consistent with existing Ukrainian docs (use “ви/ваш”). +2) Keep the tone concise and technical. + +### Headings + +1) Follow existing Ukrainian heading style; keep headings short and instructional. +2) Do not add trailing punctuation to headings. + +### Quotes + +1) Prefer Ukrainian guillemets «…» for quoted terms in prose, matching existing Ukrainian docs. +2) Never change quotes inside inline code, code blocks, URLs, or file paths. + +### Ellipsis + +1) Keep ellipsis style consistent with existing Ukrainian docs. +2) Never change `...` in code, URLs, or CLI examples. + +### Preferred translations / glossary + +Use the following preferred translations when they apply in documentation prose: + +- request (HTTP): запит +- response (HTTP): відповідь +- path operation: операція шляху +- path operation function: функція операції шляху + +### `///` admonitions + +1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +2) If a title is present, prefer these canonical titles (choose one canonical form where variants exist): + +- `/// note | Примітка` +- `/// note | Технічні деталі` +- `/// tip | Порада` +- `/// warning | Попередження` +- `/// info | Інформація` +- `/// danger | Обережно` From 261c11b218e0af717d6b08ee33c875c9c32595f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 19 Dec 2025 12:52:40 +0000 Subject: [PATCH 119/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ee1a6ab135..2576a062ec 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* 🔧 Add LLM prompt file for Ukrainian, generated from the existing translations. PR [#14548](https://github.com/fastapi/fastapi/pull/14548) by [@tiangolo](https://github.com/tiangolo). + ### Internal * ⬆️ Use prek as a pre-commit alternative. PR [#14572](https://github.com/fastapi/fastapi/pull/14572) by [@tiangolo](https://github.com/tiangolo). From d70ed5eceb01ebd3056e31f88805b0cc3d09ed83 Mon Sep 17 00:00:00 2001 From: Paras Verma <134628559+paras-verma7454@users.noreply.github.com> Date: Sat, 20 Dec 2025 12:10:21 +0530 Subject: [PATCH 120/140] =?UTF-8?q?=F0=9F=93=9D=20Fix=20duplicated=20varia?= =?UTF-8?q?ble=20in=20`docs=5Fsrc/python=5Ftypes/tutorial005=5Fpy39.py`=20?= =?UTF-8?q?(#14565)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix duplicated variable in python types example --- docs_src/python_types/tutorial005_py39.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs_src/python_types/tutorial005_py39.py b/docs_src/python_types/tutorial005_py39.py index 08ab44a941..6c8edb0ec4 100644 --- a/docs_src/python_types/tutorial005_py39.py +++ b/docs_src/python_types/tutorial005_py39.py @@ -1,2 +1,2 @@ def get_items(item_a: str, item_b: int, item_c: float, item_d: bool, item_e: bytes): - return item_a, item_b, item_c, item_d, item_d, item_e + return item_a, item_b, item_c, item_d, item_e From 5c7dceb80f7a5ec49356c8ff72b39596596d385b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Dec 2025 06:40:44 +0000 Subject: [PATCH 121/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 2576a062ec..259546d7ed 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Docs + +* 📝 Fix duplicated variable in `docs_src/python_types/tutorial005_py39.py`. PR [#14565](https://github.com/fastapi/fastapi/pull/14565) by [@paras-verma7454](https://github.com/paras-verma7454). + ### Translations * 🔧 Add LLM prompt file for Ukrainian, generated from the existing translations. PR [#14548](https://github.com/fastapi/fastapi/pull/14548) by [@tiangolo](https://github.com/tiangolo). From e2cd8a420110132bd08893d743fdb617f59fd15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 20 Dec 2025 07:55:38 -0800 Subject: [PATCH 122/140] =?UTF-8?q?=E2=9E=96=20Drop=20support=20for=20Pyda?= =?UTF-8?q?ntic=20v1,=20keeping=20short=20temporary=20support=20for=20Pyda?= =?UTF-8?q?ntic=20v2's=20`pydantic.v1`=20(#14575)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] --- .github/workflows/test.yml | 25 +- .../path-operation-advanced-configuration.md | 32 - docs/en/docs/advanced/settings.md | 44 - ...migrate-from-pydantic-v1-to-pydantic-v2.md | 14 +- .../docs/how-to/separate-openapi-schemas.md | 4 +- docs/en/docs/tutorial/body-updates.md | 16 - docs/en/docs/tutorial/body.md | 8 - docs/en/docs/tutorial/extra-models.md | 30 +- .../tutorial/query-params-str-validations.md | 14 - docs/en/docs/tutorial/response-model.md | 14 - docs/en/docs/tutorial/schema-extra-example.md | 24 +- docs_src/body/tutorial002_py310.py | 2 +- docs_src/body/tutorial002_py39.py | 2 +- docs_src/body/tutorial003_py310.py | 2 +- docs_src/body/tutorial003_py39.py | 2 +- docs_src/body/tutorial004_py310.py | 2 +- docs_src/body/tutorial004_py39.py | 2 +- docs_src/body_updates/tutorial002_py310.py | 4 +- docs_src/body_updates/tutorial002_py39.py | 4 +- .../tutorial002_pv1_an_py310.py | 20 - .../tutorial002_pv1_an_py39.py | 20 - .../tutorial002_pv1_py310.py | 18 - .../tutorial002_pv1_py39.py | 20 - docs_src/extra_models/tutorial001_py310.py | 2 +- docs_src/extra_models/tutorial001_py39.py | 2 +- docs_src/extra_models/tutorial002_py310.py | 2 +- docs_src/extra_models/tutorial002_py39.py | 2 +- .../tutorial002_pv1_an_py310.py | 22 - .../tutorial002_pv1_an_py39.py | 22 - .../tutorial002_pv1_py310.py | 20 - .../tutorial002_pv1_py39.py | 22 - .../tutorial007_pv1_py39.py | 2 +- .../tutorial002_pv1_an_py310.py | 21 - .../tutorial002_pv1_an_py39.py | 21 - .../tutorial002_pv1_py310.py | 21 - .../tutorial002_pv1_py39.py | 21 - .../tutorial002_pv1_an_py39.py | 5 +- .../tutorial002_pv1_py39.py | 5 +- .../tutorial001_pv1_py310.py | 2 +- .../tutorial001_pv1_py39.py | 2 +- docs_src/settings/app03_an_py39/config_pv1.py | 2 +- docs_src/settings/app03_py39/config_pv1.py | 2 +- docs_src/settings/tutorial001_pv1_py39.py | 2 +- fastapi/_compat/__init__.py | 1 - fastapi/_compat/main.py | 204 ++--- fastapi/_compat/may_v1.py | 2 +- fastapi/_compat/v1.py | 179 ++--- fastapi/_compat/v2.py | 4 - fastapi/datastructures.py | 16 - fastapi/dependencies/utils.py | 13 +- fastapi/encoders.py | 4 +- fastapi/openapi/models.py | 24 +- fastapi/params.py | 82 +- fastapi/routing.py | 12 +- fastapi/temp_pydantic_v1_params.py | 27 +- fastapi/utils.py | 21 +- pyproject.toml | 15 +- tests/test_additional_properties_bool.py | 8 +- tests/test_ambiguous_params.py | 6 +- tests/test_arbitrary_types.py | 5 - tests/test_compat.py | 8 +- tests/test_compat_params_v1.py | 133 +-- tests/test_computed_fields.py | 4 - tests/test_custom_schema_fields.py | 30 +- tests/test_datastructures.py | 6 - tests/test_datetime_custom_encoder.py | 7 +- .../test_filter_pydantic_sub_model/app_pv1.py | 10 +- .../test_filter_pydantic_sub_model_pv1.py | 178 ++-- tests/test_filter_pydantic_sub_model_pv2.py | 213 ++--- tests/test_forms_single_model.py | 8 +- ...t_get_model_definitions_formfeed_escape.py | 316 ++++---- tests/test_inherited_custom_class.py | 7 +- tests/test_jsonable_encoder.py | 68 +- tests/test_no_schema_split.py | 46 +- ...t_openapi_separate_input_output_schemas.py | 40 +- tests/test_pydantic_v1_v2_01.py | 78 +- tests/test_pydantic_v1_v2_list.py | 40 +- tests/test_pydantic_v1_v2_mixed.py | 226 ++---- .../test_multifile.py | 757 ++++++------------ tests/test_pydantic_v1_v2_noneable.py | 193 ++--- ..._query_cookie_header_model_extra_params.py | 8 +- tests/test_read_with_orm_mode.py | 7 +- tests/test_request_param_model_by_alias.py | 10 +- .../test_body/test_list.py | 11 - .../test_body/test_optional_list.py | 9 - .../test_body/test_optional_str.py | 15 - .../test_body/test_required_str.py | 11 - .../test_cookie/test_optional_str.py | 11 - .../test_cookie/test_required_str.py | 11 - .../test_file/test_list.py | 11 - .../test_file/test_optional.py | 11 - .../test_file/test_optional_list.py | 11 - .../test_file/test_required.py | 11 - .../test_form/test_list.py | 11 - .../test_form/test_optional_list.py | 11 - .../test_form/test_optional_str.py | 11 - .../test_form/test_required_str.py | 11 - .../test_header/test_list.py | 11 - .../test_header/test_optional_list.py | 11 - .../test_header/test_optional_str.py | 11 - .../test_header/test_required_str.py | 11 - .../test_path/test_required_str.py | 6 - .../test_query/test_list.py | 11 - .../test_query/test_optional_list.py | 11 - .../test_query/test_optional_str.py | 11 - .../test_query/test_required_str.py | 11 - tests/test_response_by_alias.py | 27 +- tests/test_schema_compat_pydantic_v2.py | 4 +- tests/test_schema_extra_examples.py | 12 +- tests/test_schema_ref_pydantic_v2.py | 4 - .../test_body_updates/test_tutorial001.py | 137 +--- .../test_tutorial001.py | 5 - .../test_tutorial002.py | 75 +- .../test_dataclasses/test_tutorial003.py | 137 +--- .../test_tutorial002.py | 14 +- .../test_tutorial004.py | 100 +-- .../test_tutorial007.py | 6 - .../test_tutorial005.py | 100 +-- .../test_tutorial001.py | 6 - .../test_tutorial002.py | 5 - .../test_tutorial003.py | 4 - .../test_tutorial004.py | 4 - .../test_tutorial002.py | 14 +- .../test_tutorial015.py | 6 +- .../test_tutorial002.py | 9 - .../test_tutorial002_pv1.py | 84 -- .../test_tutorial001.py | 138 +--- .../test_tutorial001.py | 4 +- .../test_tutorial001_pv1.py | 179 +++-- .../test_tutorial001.py | 3 +- .../test_tutorial002.py | 3 +- .../test_tutorial/test_settings/test_app02.py | 4 - .../test_tutorial/test_settings/test_app03.py | 4 +- .../test_settings/test_tutorial001.py | 4 +- tests/test_union_body_discriminator.py | 3 - ...test_union_body_discriminator_annotated.py | 5 - tests/test_validate_response_recursive/app.py | 11 +- tests/utils.py | 30 +- 138 files changed, 1272 insertions(+), 3658 deletions(-) delete mode 100644 docs_src/cookie_param_models/tutorial002_pv1_an_py310.py delete mode 100644 docs_src/cookie_param_models/tutorial002_pv1_an_py39.py delete mode 100644 docs_src/cookie_param_models/tutorial002_pv1_py310.py delete mode 100644 docs_src/cookie_param_models/tutorial002_pv1_py39.py delete mode 100644 docs_src/header_param_models/tutorial002_pv1_an_py310.py delete mode 100644 docs_src/header_param_models/tutorial002_pv1_an_py39.py delete mode 100644 docs_src/header_param_models/tutorial002_pv1_py310.py delete mode 100644 docs_src/header_param_models/tutorial002_pv1_py39.py delete mode 100644 docs_src/query_param_models/tutorial002_pv1_an_py310.py delete mode 100644 docs_src/query_param_models/tutorial002_pv1_an_py39.py delete mode 100644 docs_src/query_param_models/tutorial002_pv1_py310.py delete mode 100644 docs_src/query_param_models/tutorial002_pv1_py39.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc906eaf6d..eb2b6b64ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,35 +44,22 @@ jobs: matrix: os: [ windows-latest, macos-latest ] python-version: [ "3.14" ] - pydantic-version: [ "pydantic>=2.0.2,<3.0.0" ] include: - os: ubuntu-latest python-version: "3.9" - pydantic-version: "pydantic>=1.10.0,<2.0.0" coverage: coverage - os: macos-latest python-version: "3.10" - pydantic-version: "pydantic>=2.0.2,<3.0.0" - - os: windows-latest - python-version: "3.11" - pydantic-version: "pydantic>=1.10.0,<2.0.0" - - os: ubuntu-latest - python-version: "3.12" - pydantic-version: "pydantic>=2.0.2,<3.0.0" - - os: macos-latest - python-version: "3.13" - pydantic-version: "pydantic>=1.10.0,<2.0.0" - - os: windows-latest - python-version: "3.13" - pydantic-version: "pydantic>=2.0.2,<3.0.0" coverage: coverage + - os: windows-latest + python-version: "3.12" + coverage: coverage + # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks - os: ubuntu-latest python-version: "3.13" - pydantic-version: "pydantic>=2.0.2,<3.0.0" coverage: coverage - os: ubuntu-latest python-version: "3.14" - pydantic-version: "pydantic>=2.0.2,<3.0.0" coverage: coverage fail-fast: false runs-on: ${{ matrix.os }} @@ -96,8 +83,6 @@ jobs: pyproject.toml - name: Install Dependencies run: uv pip install -r requirements-tests.txt - - name: Install Pydantic - run: uv pip install "${{ matrix.pydantic-version }}" - run: mkdir coverage - name: Test run: bash scripts/test.sh @@ -105,7 +90,7 @@ jobs: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - name: CodSpeed benchmarks - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' && matrix.pydantic-version == 'pydantic>=2.0.2,<3.0.0' + if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' uses: CodSpeedHQ/action@v4 env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} diff --git a/docs/en/docs/advanced/path-operation-advanced-configuration.md b/docs/en/docs/advanced/path-operation-advanced-configuration.md index 01196af79e..e0e3c96a0e 100644 --- a/docs/en/docs/advanced/path-operation-advanced-configuration.md +++ b/docs/en/docs/advanced/path-operation-advanced-configuration.md @@ -153,48 +153,16 @@ And you could do this even if the data type in the request is not JSON. For example, in this application we don't use FastAPI's integrated functionality to extract the JSON Schema from Pydantic models nor the automatic validation for JSON. In fact, we are declaring the request content type as YAML, not JSON: -//// tab | Pydantic v2 - {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[15:20, 22] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[15:20, 22] *} - -//// - -/// info - -In Pydantic version 1 the method to get the JSON Schema for a model was called `Item.schema()`, in Pydantic version 2, the method is called `Item.model_json_schema()`. - -/// - Nevertheless, although we are not using the default integrated functionality, we are still using a Pydantic model to manually generate the JSON Schema for the data that we want to receive in YAML. Then we use the request directly, and extract the body as `bytes`. This means that FastAPI won't even try to parse the request payload as JSON. And then in our code, we parse that YAML content directly, and then we are again using the same Pydantic model to validate the YAML content: -//// tab | Pydantic v2 - {* ../../docs_src/path_operation_advanced_configuration/tutorial007_py39.py hl[24:31] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py hl[24:31] *} - -//// - -/// info - -In Pydantic version 1 the method to parse and validate an object was `Item.parse_obj()`, in Pydantic version 2, the method is called `Item.model_validate()`. - -/// - /// tip Here we reuse the same Pydantic model. diff --git a/docs/en/docs/advanced/settings.md b/docs/en/docs/advanced/settings.md index 268983f1ef..cded9b80b8 100644 --- a/docs/en/docs/advanced/settings.md +++ b/docs/en/docs/advanced/settings.md @@ -46,12 +46,6 @@ $ pip install "fastapi[all]" -/// info - -In Pydantic v1 it came included with the main package. Now it is distributed as this independent package so that you can choose to install it or not if you don't need that functionality. - -/// - ### Create the `Settings` object { #create-the-settings-object } Import `BaseSettings` from Pydantic and create a sub-class, very much like with a Pydantic model. @@ -60,24 +54,8 @@ The same way as with Pydantic models, you declare class attributes with type ann You can use all the same validation features and tools you use for Pydantic models, like different data types and additional validations with `Field()`. -//// tab | Pydantic v2 - {* ../../docs_src/settings/tutorial001_py39.py hl[2,5:8,11] *} -//// - -//// tab | Pydantic v1 - -/// info - -In Pydantic v1 you would import `BaseSettings` directly from `pydantic` instead of from `pydantic_settings`. - -/// - -{* ../../docs_src/settings/tutorial001_pv1_py39.py hl[2,5:8,11] *} - -//// - /// tip If you want something quick to copy and paste, don't use this example, use the last one below. @@ -215,8 +193,6 @@ APP_NAME="ChimichangApp" And then update your `config.py` with: -//// tab | Pydantic v2 - {* ../../docs_src/settings/app03_an_py39/config.py hl[9] *} /// tip @@ -225,26 +201,6 @@ The `model_config` attribute is used just for Pydantic configuration. You can re /// -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/settings/app03_an_py39/config_pv1.py hl[9:10] *} - -/// tip - -The `Config` class is used just for Pydantic configuration. You can read more at Pydantic Model Config. - -/// - -//// - -/// info - -In Pydantic version 1 the configuration was done in an internal class `Config`, in Pydantic version 2 it's done in an attribute `model_config`. This attribute takes a `dict`, and to get autocompletion and inline errors you can import and use `SettingsConfigDict` to define that `dict`. - -/// - Here we define the config `env_file` inside of your Pydantic `Settings` class, and set the value to the filename with the dotenv file we want to use. ### Creating the `Settings` only once with `lru_cache` { #creating-the-settings-only-once-with-lru-cache } diff --git a/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md index e85d122be8..fc90220b89 100644 --- a/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md +++ b/docs/en/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md @@ -2,21 +2,23 @@ If you have an old FastAPI app, you might be using Pydantic version 1. -FastAPI has had support for either Pydantic v1 or v2 since version 0.100.0. +FastAPI version 0.100.0 had support for either Pydantic v1 or v2. It would use whichever you had installed. -If you had installed Pydantic v2, it would use it. If instead you had Pydantic v1, it would use that. +FastAPI version 0.119.0 introduced partial support for Pydantic v1 from inside of Pydantic v2 (as `pydantic.v1`), to facilitate the migration to v2. -Pydantic v1 is now deprecated and support for it will be removed in the next versions of FastAPI, you should **migrate to Pydantic v2**. This way you will get the latest features, improvements, and fixes. +FastAPI 0.126.0 dropped support for Pydantic v1, while still supporting `pydantic.v1` for a little while. /// warning -Also, the Pydantic team stopped support for Pydantic v1 for the latest versions of Python, starting with **Python 3.14**. +The Pydantic team stopped support for Pydantic v1 for the latest versions of Python, starting with **Python 3.14**. + +This includes `pydantic.v1`, which is no longer supported in Python 3.14 and above. If you want to use the latest features of Python, you will need to make sure you use Pydantic v2. /// -If you have an old FastAPI app with Pydantic v1, here I'll show you how to migrate it to Pydantic v2, and the **new features in FastAPI 0.119.0** to help you with a gradual migration. +If you have an old FastAPI app with Pydantic v1, here I'll show you how to migrate it to Pydantic v2, and the **features in FastAPI 0.119.0** to help you with a gradual migration. ## Official Guide { #official-guide } @@ -44,7 +46,7 @@ After this, you can run the tests and check if everything works. If it does, you ## Pydantic v1 in v2 { #pydantic-v1-in-v2 } -Pydantic v2 includes everything from Pydantic v1 as a submodule `pydantic.v1`. +Pydantic v2 includes everything from Pydantic v1 as a submodule `pydantic.v1`. But this is no longer supported in versions above Python 3.13. This means that you can install the latest version of Pydantic v2 and import and use the old Pydantic v1 components from this submodule, as if you had the old Pydantic v1 installed. diff --git a/docs/en/docs/how-to/separate-openapi-schemas.md b/docs/en/docs/how-to/separate-openapi-schemas.md index 3c78a56d36..d790c600bb 100644 --- a/docs/en/docs/how-to/separate-openapi-schemas.md +++ b/docs/en/docs/how-to/separate-openapi-schemas.md @@ -1,6 +1,6 @@ # Separate OpenAPI Schemas for Input and Output or Not { #separate-openapi-schemas-for-input-and-output-or-not } -When using **Pydantic v2**, the generated OpenAPI is a bit more exact and **correct** than before. 😎 +Since **Pydantic v2** was released, the generated OpenAPI is a bit more exact and **correct** than before. 😎 In fact, in some cases, it will even have **two JSON Schemas** in OpenAPI for the same Pydantic model, for input and output, depending on if they have **default values**. @@ -100,5 +100,3 @@ And now there will be one single schema for input and output for the model, only
- -This is the same behavior as in Pydantic v1. 🤓 diff --git a/docs/en/docs/tutorial/body-updates.md b/docs/en/docs/tutorial/body-updates.md index baeb53ec6f..1b7fd70661 100644 --- a/docs/en/docs/tutorial/body-updates.md +++ b/docs/en/docs/tutorial/body-updates.md @@ -50,14 +50,6 @@ If you want to receive partial updates, it's very useful to use the parameter `e Like `item.model_dump(exclude_unset=True)`. -/// info - -In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`. - -The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2. - -/// - That would generate a `dict` with only the data that was set when creating the `item` model, excluding default values. Then you can use this to generate a `dict` with only the data that was set (sent in the request), omitting default values: @@ -68,14 +60,6 @@ Then you can use this to generate a `dict` with only the data that was set (sent Now, you can create a copy of the existing model using `.model_copy()`, and pass the `update` parameter with a `dict` containing the data to update. -/// info - -In Pydantic v1 the method was called `.copy()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_copy()`. - -The examples here use `.copy()` for compatibility with Pydantic v1, but you should use `.model_copy()` instead if you can use Pydantic v2. - -/// - Like `stored_item_model.model_copy(update=update_data)`: {* ../../docs_src/body_updates/tutorial002_py310.py hl[33] *} diff --git a/docs/en/docs/tutorial/body.md b/docs/en/docs/tutorial/body.md index 25087b8406..2d0dfcbb59 100644 --- a/docs/en/docs/tutorial/body.md +++ b/docs/en/docs/tutorial/body.md @@ -128,14 +128,6 @@ Inside of the function, you can access all the attributes of the model object di {* ../../docs_src/body/tutorial002_py310.py *} -/// info - -In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`. - -The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2. - -/// - ## Request body + path parameters { #request-body-path-parameters } You can declare path parameters and request body at the same time. diff --git a/docs/en/docs/tutorial/extra-models.md b/docs/en/docs/tutorial/extra-models.md index 7079bf73da..d82df561fa 100644 --- a/docs/en/docs/tutorial/extra-models.md +++ b/docs/en/docs/tutorial/extra-models.md @@ -22,22 +22,13 @@ Here's a general idea of how the models could look like with their password fiel {* ../../docs_src/extra_models/tutorial001_py310.py hl[7,9,14,20,22,27:28,31:33,38:39] *} +### About `**user_in.model_dump()` { #about-user-in-model-dump } -/// info - -In Pydantic v1 the method was called `.dict()`, it was deprecated (but still supported) in Pydantic v2, and renamed to `.model_dump()`. - -The examples here use `.dict()` for compatibility with Pydantic v1, but you should use `.model_dump()` instead if you can use Pydantic v2. - -/// - -### About `**user_in.dict()` { #about-user-in-dict } - -#### Pydantic's `.dict()` { #pydantics-dict } +#### Pydantic's `.model_dump()` { #pydantics-model-dump } `user_in` is a Pydantic model of class `UserIn`. -Pydantic models have a `.dict()` method that returns a `dict` with the model's data. +Pydantic models have a `.model_dump()` method that returns a `dict` with the model's data. So, if we create a Pydantic object `user_in` like: @@ -48,7 +39,7 @@ user_in = UserIn(username="john", password="secret", email="john.doe@example.com and then we call: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() ``` we now have a `dict` with the data in the variable `user_dict` (it's a `dict` instead of a Pydantic model object). @@ -104,20 +95,20 @@ UserInDB( #### A Pydantic model from the contents of another { #a-pydantic-model-from-the-contents-of-another } -As in the example above we got `user_dict` from `user_in.dict()`, this code: +As in the example above we got `user_dict` from `user_in.model_dump()`, this code: ```Python -user_dict = user_in.dict() +user_dict = user_in.model_dump() UserInDB(**user_dict) ``` would be equivalent to: ```Python -UserInDB(**user_in.dict()) +UserInDB(**user_in.model_dump()) ``` -...because `user_in.dict()` is a `dict`, and then we make Python "unpack" it by passing it to `UserInDB` prefixed with `**`. +...because `user_in.model_dump()` is a `dict`, and then we make Python "unpack" it by passing it to `UserInDB` prefixed with `**`. So, we get a Pydantic model from the data in another Pydantic model. @@ -126,7 +117,7 @@ So, we get a Pydantic model from the data in another Pydantic model. And then adding the extra keyword argument `hashed_password=hashed_password`, like in: ```Python -UserInDB(**user_in.dict(), hashed_password=hashed_password) +UserInDB(**user_in.model_dump(), hashed_password=hashed_password) ``` ...ends up being like: @@ -181,7 +172,6 @@ When defining a its `exclude_unset` parameter to achieve this. - -/// - -/// info - You can also use: * `response_model_exclude_defaults=True` diff --git a/docs/en/docs/tutorial/schema-extra-example.md b/docs/en/docs/tutorial/schema-extra-example.md index f3ddaf3697..daaea73c87 100644 --- a/docs/en/docs/tutorial/schema-extra-example.md +++ b/docs/en/docs/tutorial/schema-extra-example.md @@ -8,36 +8,14 @@ Here are several ways to do it. You can declare `examples` for a Pydantic model that will be added to the generated JSON Schema. -//// tab | Pydantic v2 - {* ../../docs_src/schema_extra_example/tutorial001_py310.py hl[13:24] *} -//// - -//// tab | Pydantic v1 - -{* ../../docs_src/schema_extra_example/tutorial001_pv1_py310.py hl[13:23] *} - -//// - That extra info will be added as-is to the output **JSON Schema** for that model, and it will be used in the API docs. -//// tab | Pydantic v2 - -In Pydantic version 2, you would use the attribute `model_config`, that takes a `dict` as described in Pydantic's docs: Configuration. +You can use the attribute `model_config` that takes a `dict` as described in Pydantic's docs: Configuration. You can set `"json_schema_extra"` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`. -//// - -//// tab | Pydantic v1 - -In Pydantic version 1, you would use an internal class `Config` and `schema_extra`, as described in Pydantic's docs: Schema customization. - -You can set `schema_extra` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`. - -//// - /// tip You could use the same technique to extend the JSON Schema and add your own custom extra info. diff --git a/docs_src/body/tutorial002_py310.py b/docs_src/body/tutorial002_py310.py index 454c45c886..a829a4dc9d 100644 --- a/docs_src/body/tutorial002_py310.py +++ b/docs_src/body/tutorial002_py310.py @@ -14,7 +14,7 @@ app = FastAPI() @app.post("/items/") async def create_item(item: Item): - item_dict = item.dict() + item_dict = item.model_dump() if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) diff --git a/docs_src/body/tutorial002_py39.py b/docs_src/body/tutorial002_py39.py index 5cd86216b3..fb212e8e79 100644 --- a/docs_src/body/tutorial002_py39.py +++ b/docs_src/body/tutorial002_py39.py @@ -16,7 +16,7 @@ app = FastAPI() @app.post("/items/") async def create_item(item: Item): - item_dict = item.dict() + item_dict = item.model_dump() if item.tax is not None: price_with_tax = item.price + item.tax item_dict.update({"price_with_tax": price_with_tax}) diff --git a/docs_src/body/tutorial003_py310.py b/docs_src/body/tutorial003_py310.py index 440b210e6b..51ac8aafac 100644 --- a/docs_src/body/tutorial003_py310.py +++ b/docs_src/body/tutorial003_py310.py @@ -14,4 +14,4 @@ app = FastAPI() @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item): - return {"item_id": item_id, **item.dict()} + return {"item_id": item_id, **item.model_dump()} diff --git a/docs_src/body/tutorial003_py39.py b/docs_src/body/tutorial003_py39.py index 2f33cc0389..636ba2275a 100644 --- a/docs_src/body/tutorial003_py39.py +++ b/docs_src/body/tutorial003_py39.py @@ -16,4 +16,4 @@ app = FastAPI() @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item): - return {"item_id": item_id, **item.dict()} + return {"item_id": item_id, **item.model_dump()} diff --git a/docs_src/body/tutorial004_py310.py b/docs_src/body/tutorial004_py310.py index b352b70ab6..53b10d97b6 100644 --- a/docs_src/body/tutorial004_py310.py +++ b/docs_src/body/tutorial004_py310.py @@ -14,7 +14,7 @@ app = FastAPI() @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item, q: str | None = None): - result = {"item_id": item_id, **item.dict()} + result = {"item_id": item_id, **item.model_dump()} if q: result.update({"q": q}) return result diff --git a/docs_src/body/tutorial004_py39.py b/docs_src/body/tutorial004_py39.py index 0671e0a278..2c157abfa1 100644 --- a/docs_src/body/tutorial004_py39.py +++ b/docs_src/body/tutorial004_py39.py @@ -16,7 +16,7 @@ app = FastAPI() @app.put("/items/{item_id}") async def update_item(item_id: int, item: Item, q: Union[str, None] = None): - result = {"item_id": item_id, **item.dict()} + result = {"item_id": item_id, **item.model_dump()} if q: result.update({"q": q}) return result diff --git a/docs_src/body_updates/tutorial002_py310.py b/docs_src/body_updates/tutorial002_py310.py index 3498414966..e5db711108 100644 --- a/docs_src/body_updates/tutorial002_py310.py +++ b/docs_src/body_updates/tutorial002_py310.py @@ -29,7 +29,7 @@ async def read_item(item_id: str): async def update_item(item_id: str, item: Item): stored_item_data = items[item_id] stored_item_model = Item(**stored_item_data) - update_data = item.dict(exclude_unset=True) - updated_item = stored_item_model.copy(update=update_data) + update_data = item.model_dump(exclude_unset=True) + updated_item = stored_item_model.model_copy(update=update_data) items[item_id] = jsonable_encoder(updated_item) return updated_item diff --git a/docs_src/body_updates/tutorial002_py39.py b/docs_src/body_updates/tutorial002_py39.py index eb35b35215..eddd7af716 100644 --- a/docs_src/body_updates/tutorial002_py39.py +++ b/docs_src/body_updates/tutorial002_py39.py @@ -31,7 +31,7 @@ async def read_item(item_id: str): async def update_item(item_id: str, item: Item): stored_item_data = items[item_id] stored_item_model = Item(**stored_item_data) - update_data = item.dict(exclude_unset=True) - updated_item = stored_item_model.copy(update=update_data) + update_data = item.model_dump(exclude_unset=True) + updated_item = stored_item_model.model_copy(update=update_data) items[item_id] = jsonable_encoder(updated_item) return updated_item diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py deleted file mode 100644 index ac00360b60..0000000000 --- a/docs_src/cookie_param_models/tutorial002_pv1_an_py310.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Annotated - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - class Config: - extra = "forbid" - - session_id: str - fatebook_tracker: str | None = None - googall_tracker: str | None = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py b/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py deleted file mode 100644 index 573caea4b1..0000000000 --- a/docs_src/cookie_param_models/tutorial002_pv1_an_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Annotated, Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - class Config: - extra = "forbid" - - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Annotated[Cookies, Cookie()]): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_py310.py b/docs_src/cookie_param_models/tutorial002_pv1_py310.py deleted file mode 100644 index 2c59aad123..0000000000 --- a/docs_src/cookie_param_models/tutorial002_pv1_py310.py +++ /dev/null @@ -1,18 +0,0 @@ -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - class Config: - extra = "forbid" - - session_id: str - fatebook_tracker: str | None = None - googall_tracker: str | None = None - - -@app.get("/items/") -async def read_items(cookies: Cookies = Cookie()): - return cookies diff --git a/docs_src/cookie_param_models/tutorial002_pv1_py39.py b/docs_src/cookie_param_models/tutorial002_pv1_py39.py deleted file mode 100644 index 13f78b850e..0000000000 --- a/docs_src/cookie_param_models/tutorial002_pv1_py39.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Union - -from fastapi import Cookie, FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Cookies(BaseModel): - class Config: - extra = "forbid" - - session_id: str - fatebook_tracker: Union[str, None] = None - googall_tracker: Union[str, None] = None - - -@app.get("/items/") -async def read_items(cookies: Cookies = Cookie()): - return cookies diff --git a/docs_src/extra_models/tutorial001_py310.py b/docs_src/extra_models/tutorial001_py310.py index 669386ae6e..cf39142e4a 100644 --- a/docs_src/extra_models/tutorial001_py310.py +++ b/docs_src/extra_models/tutorial001_py310.py @@ -30,7 +30,7 @@ def fake_password_hasher(raw_password: str): def fake_save_user(user_in: UserIn): hashed_password = fake_password_hasher(user_in.password) - user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) + user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password) print("User saved! ..not really") return user_in_db diff --git a/docs_src/extra_models/tutorial001_py39.py b/docs_src/extra_models/tutorial001_py39.py index 4be56cd2a7..327ffcdf09 100644 --- a/docs_src/extra_models/tutorial001_py39.py +++ b/docs_src/extra_models/tutorial001_py39.py @@ -32,7 +32,7 @@ def fake_password_hasher(raw_password: str): def fake_save_user(user_in: UserIn): hashed_password = fake_password_hasher(user_in.password) - user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) + user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password) print("User saved! ..not really") return user_in_db diff --git a/docs_src/extra_models/tutorial002_py310.py b/docs_src/extra_models/tutorial002_py310.py index 5b8ed7de3d..e8a4f5f29b 100644 --- a/docs_src/extra_models/tutorial002_py310.py +++ b/docs_src/extra_models/tutorial002_py310.py @@ -28,7 +28,7 @@ def fake_password_hasher(raw_password: str): def fake_save_user(user_in: UserIn): hashed_password = fake_password_hasher(user_in.password) - user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) + user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password) print("User saved! ..not really") return user_in_db diff --git a/docs_src/extra_models/tutorial002_py39.py b/docs_src/extra_models/tutorial002_py39.py index 70fa16441d..6543796015 100644 --- a/docs_src/extra_models/tutorial002_py39.py +++ b/docs_src/extra_models/tutorial002_py39.py @@ -30,7 +30,7 @@ def fake_password_hasher(raw_password: str): def fake_save_user(user_in: UserIn): hashed_password = fake_password_hasher(user_in.password) - user_in_db = UserInDB(**user_in.dict(), hashed_password=hashed_password) + user_in_db = UserInDB(**user_in.model_dump(), hashed_password=hashed_password) print("User saved! ..not really") return user_in_db diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py310.py b/docs_src/header_param_models/tutorial002_pv1_an_py310.py deleted file mode 100644 index e99e24ea55..0000000000 --- a/docs_src/header_param_models/tutorial002_pv1_an_py310.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Annotated - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - class Config: - extra = "forbid" - - host: str - save_data: bool - if_modified_since: str | None = None - traceparent: str | None = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_an_py39.py b/docs_src/header_param_models/tutorial002_pv1_an_py39.py deleted file mode 100644 index 18398b726c..0000000000 --- a/docs_src/header_param_models/tutorial002_pv1_an_py39.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Annotated, Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - class Config: - extra = "forbid" - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: Annotated[CommonHeaders, Header()]): - return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_py310.py b/docs_src/header_param_models/tutorial002_pv1_py310.py deleted file mode 100644 index 3dbff9d7bf..0000000000 --- a/docs_src/header_param_models/tutorial002_pv1_py310.py +++ /dev/null @@ -1,20 +0,0 @@ -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - class Config: - extra = "forbid" - - host: str - save_data: bool - if_modified_since: str | None = None - traceparent: str | None = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/header_param_models/tutorial002_pv1_py39.py b/docs_src/header_param_models/tutorial002_pv1_py39.py deleted file mode 100644 index 86e19be0d1..0000000000 --- a/docs_src/header_param_models/tutorial002_pv1_py39.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Union - -from fastapi import FastAPI, Header -from pydantic import BaseModel - -app = FastAPI() - - -class CommonHeaders(BaseModel): - class Config: - extra = "forbid" - - host: str - save_data: bool - if_modified_since: Union[str, None] = None - traceparent: Union[str, None] = None - x_tag: list[str] = [] - - -@app.get("/items/") -async def read_items(headers: CommonHeaders = Header()): - return headers diff --git a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py index 831966553f..849f648e12 100644 --- a/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py +++ b/docs_src/path_operation_advanced_configuration/tutorial007_pv1_py39.py @@ -1,6 +1,6 @@ import yaml from fastapi import FastAPI, HTTPException, Request -from pydantic import BaseModel, ValidationError +from pydantic.v1 import BaseModel, ValidationError app = FastAPI() diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py310.py b/docs_src/query_param_models/tutorial002_pv1_an_py310.py deleted file mode 100644 index d635aae88f..0000000000 --- a/docs_src/query_param_models/tutorial002_pv1_an_py310.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - class Config: - extra = "forbid" - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_an_py39.py b/docs_src/query_param_models/tutorial002_pv1_an_py39.py deleted file mode 100644 index d635aae88f..0000000000 --- a/docs_src/query_param_models/tutorial002_pv1_an_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Annotated, Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - class Config: - extra = "forbid" - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: Annotated[FilterParams, Query()]): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_py310.py b/docs_src/query_param_models/tutorial002_pv1_py310.py deleted file mode 100644 index 9ffdeefc06..0000000000 --- a/docs_src/query_param_models/tutorial002_pv1_py310.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - class Config: - extra = "forbid" - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/query_param_models/tutorial002_pv1_py39.py b/docs_src/query_param_models/tutorial002_pv1_py39.py deleted file mode 100644 index 9ffdeefc06..0000000000 --- a/docs_src/query_param_models/tutorial002_pv1_py39.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Literal - -from fastapi import FastAPI, Query -from pydantic import BaseModel, Field - -app = FastAPI() - - -class FilterParams(BaseModel): - class Config: - extra = "forbid" - - limit: int = Field(100, gt=0, le=100) - offset: int = Field(0, ge=0) - order_by: Literal["created_at", "updated_at"] = "created_at" - tags: list[str] = [] - - -@app.get("/items/") -async def read_items(filter_query: FilterParams = Query()): - return filter_query diff --git a/docs_src/request_form_models/tutorial002_pv1_an_py39.py b/docs_src/request_form_models/tutorial002_pv1_an_py39.py index 942d5d4118..392e6873cb 100644 --- a/docs_src/request_form_models/tutorial002_pv1_an_py39.py +++ b/docs_src/request_form_models/tutorial002_pv1_an_py39.py @@ -1,7 +1,8 @@ from typing import Annotated -from fastapi import FastAPI, Form -from pydantic import BaseModel +from fastapi import FastAPI +from fastapi.temp_pydantic_v1_params import Form +from pydantic.v1 import BaseModel app = FastAPI() diff --git a/docs_src/request_form_models/tutorial002_pv1_py39.py b/docs_src/request_form_models/tutorial002_pv1_py39.py index d5f7db2a67..da160b3a54 100644 --- a/docs_src/request_form_models/tutorial002_pv1_py39.py +++ b/docs_src/request_form_models/tutorial002_pv1_py39.py @@ -1,5 +1,6 @@ -from fastapi import FastAPI, Form -from pydantic import BaseModel +from fastapi import FastAPI +from fastapi.temp_pydantic_v1_params import Form +from pydantic.v1 import BaseModel app = FastAPI() diff --git a/docs_src/schema_extra_example/tutorial001_pv1_py310.py b/docs_src/schema_extra_example/tutorial001_pv1_py310.py index ec83f1112f..b13b8a8c2c 100644 --- a/docs_src/schema_extra_example/tutorial001_pv1_py310.py +++ b/docs_src/schema_extra_example/tutorial001_pv1_py310.py @@ -1,5 +1,5 @@ from fastapi import FastAPI -from pydantic import BaseModel +from pydantic.v1 import BaseModel app = FastAPI() diff --git a/docs_src/schema_extra_example/tutorial001_pv1_py39.py b/docs_src/schema_extra_example/tutorial001_pv1_py39.py index 6ab96ff859..3240b35d6d 100644 --- a/docs_src/schema_extra_example/tutorial001_pv1_py39.py +++ b/docs_src/schema_extra_example/tutorial001_pv1_py39.py @@ -1,7 +1,7 @@ from typing import Union from fastapi import FastAPI -from pydantic import BaseModel +from pydantic.v1 import BaseModel app = FastAPI() diff --git a/docs_src/settings/app03_an_py39/config_pv1.py b/docs_src/settings/app03_an_py39/config_pv1.py index e1c3ee3006..7ae66ef77c 100644 --- a/docs_src/settings/app03_an_py39/config_pv1.py +++ b/docs_src/settings/app03_an_py39/config_pv1.py @@ -1,4 +1,4 @@ -from pydantic import BaseSettings +from pydantic.v1 import BaseSettings class Settings(BaseSettings): diff --git a/docs_src/settings/app03_py39/config_pv1.py b/docs_src/settings/app03_py39/config_pv1.py index e1c3ee3006..7ae66ef77c 100644 --- a/docs_src/settings/app03_py39/config_pv1.py +++ b/docs_src/settings/app03_py39/config_pv1.py @@ -1,4 +1,4 @@ -from pydantic import BaseSettings +from pydantic.v1 import BaseSettings class Settings(BaseSettings): diff --git a/docs_src/settings/tutorial001_pv1_py39.py b/docs_src/settings/tutorial001_pv1_py39.py index 0cfd1b6632..20ad2bbf62 100644 --- a/docs_src/settings/tutorial001_pv1_py39.py +++ b/docs_src/settings/tutorial001_pv1_py39.py @@ -1,5 +1,5 @@ from fastapi import FastAPI -from pydantic import BaseSettings +from pydantic.v1 import BaseSettings class Settings(BaseSettings): diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py index 0aadd68de2..fd1df8c6a7 100644 --- a/fastapi/_compat/__init__.py +++ b/fastapi/_compat/__init__.py @@ -11,7 +11,6 @@ from .main import _is_model_class as _is_model_class from .main import _is_model_field as _is_model_field from .main import _is_undefined as _is_undefined from .main import _model_dump as _model_dump -from .main import _model_rebuild as _model_rebuild from .main import copy_field_info as copy_field_info from .main import create_body_model as create_body_model from .main import evaluate_forwardref as evaluate_forwardref diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py index 2043a66781..95053a2374 100644 --- a/fastapi/_compat/main.py +++ b/fastapi/_compat/main.py @@ -6,43 +6,26 @@ from typing import ( ) from fastapi._compat import may_v1 -from fastapi._compat.shared import PYDANTIC_V2, lenient_issubclass +from fastapi._compat.shared import lenient_issubclass from fastapi.types import ModelNameMap from pydantic import BaseModel from typing_extensions import Literal +from . import v2 from .model_field import ModelField - -if PYDANTIC_V2: - from .v2 import BaseConfig as BaseConfig - from .v2 import FieldInfo as FieldInfo - from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError - from .v2 import RequiredParam as RequiredParam - from .v2 import Undefined as Undefined - from .v2 import UndefinedType as UndefinedType - from .v2 import Url as Url - from .v2 import Validator as Validator - from .v2 import evaluate_forwardref as evaluate_forwardref - from .v2 import get_missing_field_error as get_missing_field_error - from .v2 import ( - with_info_plain_validator_function as with_info_plain_validator_function, - ) -else: - from .v1 import BaseConfig as BaseConfig # type: ignore[assignment] - from .v1 import FieldInfo as FieldInfo - from .v1 import ( # type: ignore[assignment] - PydanticSchemaGenerationError as PydanticSchemaGenerationError, - ) - from .v1 import RequiredParam as RequiredParam - from .v1 import Undefined as Undefined - from .v1 import UndefinedType as UndefinedType - from .v1 import Url as Url # type: ignore[assignment] - from .v1 import Validator as Validator - from .v1 import evaluate_forwardref as evaluate_forwardref - from .v1 import get_missing_field_error as get_missing_field_error - from .v1 import ( # type: ignore[assignment] - with_info_plain_validator_function as with_info_plain_validator_function, - ) +from .v2 import BaseConfig as BaseConfig +from .v2 import FieldInfo as FieldInfo +from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError +from .v2 import RequiredParam as RequiredParam +from .v2 import Undefined as Undefined +from .v2 import UndefinedType as UndefinedType +from .v2 import Url as Url +from .v2 import Validator as Validator +from .v2 import evaluate_forwardref as evaluate_forwardref +from .v2 import get_missing_field_error as get_missing_field_error +from .v2 import ( + with_info_plain_validator_function as with_info_plain_validator_function, +) @lru_cache @@ -50,7 +33,7 @@ def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: if lenient_issubclass(model, may_v1.BaseModel): from fastapi._compat import v1 - return v1.get_model_fields(model) + return v1.get_model_fields(model) # type: ignore[arg-type,return-value] else: from . import v2 @@ -60,11 +43,8 @@ def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: def _is_undefined(value: object) -> bool: if isinstance(value, may_v1.UndefinedType): return True - elif PYDANTIC_V2: - from . import v2 - return isinstance(value, v2.UndefinedType) - return False + return isinstance(value, v2.UndefinedType) def _get_model_config(model: BaseModel) -> Any: @@ -72,10 +52,8 @@ def _get_model_config(model: BaseModel) -> Any: from fastapi._compat import v1 return v1._get_model_config(model) - elif PYDANTIC_V2: - from . import v2 - return v2._get_model_config(model) + return v2._get_model_config(model) def _model_dump( @@ -85,20 +63,15 @@ def _model_dump( from fastapi._compat import v1 return v1._model_dump(model, mode=mode, **kwargs) - elif PYDANTIC_V2: - from . import v2 - return v2._model_dump(model, mode=mode, **kwargs) + return v2._model_dump(model, mode=mode, **kwargs) def _is_error_wrapper(exc: Exception) -> bool: if isinstance(exc, may_v1.ErrorWrapper): return True - elif PYDANTIC_V2: - from . import v2 - return isinstance(exc, v2.ErrorWrapper) - return False + return isinstance(exc, v2.ErrorWrapper) def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: @@ -106,11 +79,8 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: from fastapi._compat import v1 return v1.copy_field_info(field_info=field_info, annotation=annotation) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.copy_field_info(field_info=field_info, annotation=annotation) + return v2.copy_field_info(field_info=field_info, annotation=annotation) def create_body_model( @@ -120,11 +90,8 @@ def create_body_model( from fastapi._compat import v1 return v1.create_body_model(fields=fields, model_name=model_name) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type] + return v2.create_body_model(fields=fields, model_name=model_name) # type: ignore[arg-type] def get_annotation_from_field_info( @@ -136,13 +103,10 @@ def get_annotation_from_field_info( return v1.get_annotation_from_field_info( annotation=annotation, field_info=field_info, field_name=field_name ) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.get_annotation_from_field_info( - annotation=annotation, field_info=field_info, field_name=field_name - ) + return v2.get_annotation_from_field_info( + annotation=annotation, field_info=field_info, field_name=field_name + ) def is_bytes_field(field: ModelField) -> bool: @@ -150,11 +114,8 @@ def is_bytes_field(field: ModelField) -> bool: from fastapi._compat import v1 return v1.is_bytes_field(field) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.is_bytes_field(field) # type: ignore[arg-type] + return v2.is_bytes_field(field) # type: ignore[arg-type] def is_bytes_sequence_field(field: ModelField) -> bool: @@ -162,11 +123,8 @@ def is_bytes_sequence_field(field: ModelField) -> bool: from fastapi._compat import v1 return v1.is_bytes_sequence_field(field) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.is_bytes_sequence_field(field) # type: ignore[arg-type] + return v2.is_bytes_sequence_field(field) # type: ignore[arg-type] def is_scalar_field(field: ModelField) -> bool: @@ -174,23 +132,12 @@ def is_scalar_field(field: ModelField) -> bool: from fastapi._compat import v1 return v1.is_scalar_field(field) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.is_scalar_field(field) # type: ignore[arg-type] + return v2.is_scalar_field(field) # type: ignore[arg-type] def is_scalar_sequence_field(field: ModelField) -> bool: - if isinstance(field, may_v1.ModelField): - from fastapi._compat import v1 - - return v1.is_scalar_sequence_field(field) - else: - assert PYDANTIC_V2 - from . import v2 - - return v2.is_scalar_sequence_field(field) # type: ignore[arg-type] + return v2.is_scalar_sequence_field(field) # type: ignore[arg-type] def is_sequence_field(field: ModelField) -> bool: @@ -198,11 +145,8 @@ def is_sequence_field(field: ModelField) -> bool: from fastapi._compat import v1 return v1.is_sequence_field(field) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.is_sequence_field(field) # type: ignore[arg-type] + return v2.is_sequence_field(field) # type: ignore[arg-type] def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: @@ -210,22 +154,8 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: from fastapi._compat import v1 return v1.serialize_sequence_value(field=field, value=value) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type] - - -def _model_rebuild(model: type[BaseModel]) -> None: - if lenient_issubclass(model, may_v1.BaseModel): - from fastapi._compat import v1 - - v1._model_rebuild(model) - elif PYDANTIC_V2: - from . import v2 - - v2._model_rebuild(model) + return v2.serialize_sequence_value(field=field, value=value) # type: ignore[arg-type] def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: @@ -236,27 +166,18 @@ def get_compat_model_name_map(fields: list[ModelField]) -> ModelNameMap: from fastapi._compat import v1 v1_flat_models = v1.get_flat_models_from_fields( - v1_model_fields, known_models=set() + v1_model_fields, # type: ignore[arg-type] + known_models=set(), ) all_flat_models = v1_flat_models else: all_flat_models = set() - if PYDANTIC_V2: - from . import v2 - v2_model_fields = [ - field for field in fields if isinstance(field, v2.ModelField) - ] - v2_flat_models = v2.get_flat_models_from_fields( - v2_model_fields, known_models=set() - ) - all_flat_models = all_flat_models.union(v2_flat_models) + v2_model_fields = [field for field in fields if isinstance(field, v2.ModelField)] + v2_flat_models = v2.get_flat_models_from_fields(v2_model_fields, known_models=set()) + all_flat_models = all_flat_models.union(v2_flat_models) # type: ignore[arg-type] - model_name_map = v2.get_model_name_map(all_flat_models) - return model_name_map - from fastapi._compat import v1 - - model_name_map = v1.get_model_name_map(all_flat_models) + model_name_map = v2.get_model_name_map(all_flat_models) # type: ignore[arg-type] return model_name_map @@ -275,29 +196,23 @@ def get_definitions( if sys.version_info < (3, 14): v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)] v1_field_maps, v1_definitions = may_v1.get_definitions( - fields=v1_fields, + fields=v1_fields, # type: ignore[arg-type] model_name_map=model_name_map, separate_input_output_schemas=separate_input_output_schemas, ) - if not PYDANTIC_V2: - return v1_field_maps, v1_definitions - else: - from . import v2 - v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] - v2_field_maps, v2_definitions = v2.get_definitions( - fields=v2_fields, - model_name_map=model_name_map, - separate_input_output_schemas=separate_input_output_schemas, - ) - all_definitions = {**v1_definitions, **v2_definitions} - all_field_maps = {**v1_field_maps, **v2_field_maps} - return all_field_maps, all_definitions + v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] + v2_field_maps, v2_definitions = v2.get_definitions( + fields=v2_fields, + model_name_map=model_name_map, + separate_input_output_schemas=separate_input_output_schemas, + ) + all_definitions = {**v1_definitions, **v2_definitions} + all_field_maps = {**v1_field_maps, **v2_field_maps} # type: ignore[misc] + return all_field_maps, all_definitions # Pydantic v1 is not supported since Python 3.14 else: - from . import v2 - v2_fields = [field for field in fields if isinstance(field, v2.ModelField)] v2_field_maps, v2_definitions = v2.get_definitions( fields=v2_fields, @@ -326,33 +241,24 @@ def get_schema_from_model_field( field_mapping=field_mapping, separate_input_output_schemas=separate_input_output_schemas, ) - else: - assert PYDANTIC_V2 - from . import v2 - return v2.get_schema_from_model_field( - field=field, # type: ignore[arg-type] - model_name_map=model_name_map, - field_mapping=field_mapping, # type: ignore[arg-type] - separate_input_output_schemas=separate_input_output_schemas, - ) + return v2.get_schema_from_model_field( + field=field, # type: ignore[arg-type] + model_name_map=model_name_map, + field_mapping=field_mapping, # type: ignore[arg-type] + separate_input_output_schemas=separate_input_output_schemas, + ) def _is_model_field(value: Any) -> bool: if isinstance(value, may_v1.ModelField): return True - elif PYDANTIC_V2: - from . import v2 - return isinstance(value, v2.ModelField) - return False + return isinstance(value, v2.ModelField) def _is_model_class(value: Any) -> bool: if lenient_issubclass(value, may_v1.BaseModel): return True - elif PYDANTIC_V2: - from . import v2 - return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined] - return False + return lenient_issubclass(value, v2.BaseModel) # type: ignore[attr-defined] diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py index c772162283..3ac86aa98b 100644 --- a/fastapi/_compat/may_v1.py +++ b/fastapi/_compat/may_v1.py @@ -102,7 +102,7 @@ def _normalize_errors(errors: Sequence[Any]) -> list[dict[str, Any]]: use_errors: list[Any] = [] for error in errors: if isinstance(error, ErrorWrapper): - new_errors = ValidationError( # type: ignore[call-arg] + new_errors = ValidationError( errors=[error], model=RequestErrorModel ).errors() use_errors.extend(new_errors) diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py index b29a61f734..b0a9dd35f1 100644 --- a/fastapi/_compat/v1.py +++ b/fastapi/_compat/v1.py @@ -11,6 +11,44 @@ from typing import ( from fastapi._compat import shared from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX from fastapi.types import ModelNameMap +from pydantic.v1 import BaseConfig as BaseConfig +from pydantic.v1 import BaseModel as BaseModel +from pydantic.v1 import ValidationError as ValidationError +from pydantic.v1 import create_model as create_model +from pydantic.v1.class_validators import Validator as Validator +from pydantic.v1.color import Color as Color +from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper +from pydantic.v1.fields import ( + SHAPE_FROZENSET, + SHAPE_LIST, + SHAPE_SEQUENCE, + SHAPE_SET, + SHAPE_SINGLETON, + SHAPE_TUPLE, + SHAPE_TUPLE_ELLIPSIS, +) +from pydantic.v1.fields import FieldInfo as FieldInfo +from pydantic.v1.fields import ModelField as ModelField +from pydantic.v1.fields import Undefined as Undefined +from pydantic.v1.fields import UndefinedType as UndefinedType +from pydantic.v1.networks import AnyUrl as AnyUrl +from pydantic.v1.networks import NameEmail as NameEmail +from pydantic.v1.schema import TypeModelSet as TypeModelSet +from pydantic.v1.schema import field_schema, model_process_schema +from pydantic.v1.schema import ( + get_annotation_from_field_info as get_annotation_from_field_info, +) +from pydantic.v1.schema import ( + get_flat_models_from_field as get_flat_models_from_field, +) +from pydantic.v1.schema import ( + get_flat_models_from_fields as get_flat_models_from_fields, +) +from pydantic.v1.schema import get_model_name_map as get_model_name_map +from pydantic.v1.types import SecretBytes as SecretBytes +from pydantic.v1.types import SecretStr as SecretStr +from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref +from pydantic.v1.utils import lenient_issubclass as lenient_issubclass from pydantic.version import VERSION as PYDANTIC_VERSION from typing_extensions import Literal @@ -20,103 +58,6 @@ PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2 # shadowing typing.Required. RequiredParam: Any = Ellipsis -if not PYDANTIC_V2: - from pydantic import BaseConfig as BaseConfig - from pydantic import BaseModel as BaseModel - from pydantic import ValidationError as ValidationError - from pydantic import create_model as create_model - from pydantic.class_validators import Validator as Validator - from pydantic.color import Color as Color - from pydantic.error_wrappers import ErrorWrapper as ErrorWrapper - from pydantic.errors import MissingError - from pydantic.fields import ( # type: ignore[attr-defined] - SHAPE_FROZENSET, - SHAPE_LIST, - SHAPE_SEQUENCE, - SHAPE_SET, - SHAPE_SINGLETON, - SHAPE_TUPLE, - SHAPE_TUPLE_ELLIPSIS, - ) - from pydantic.fields import FieldInfo as FieldInfo - from pydantic.fields import ModelField as ModelField # type: ignore[attr-defined] - from pydantic.fields import Undefined as Undefined # type: ignore[attr-defined] - from pydantic.fields import ( # type: ignore[attr-defined] - UndefinedType as UndefinedType, - ) - from pydantic.networks import AnyUrl as AnyUrl - from pydantic.networks import NameEmail as NameEmail - from pydantic.schema import TypeModelSet as TypeModelSet - from pydantic.schema import ( - field_schema, - model_process_schema, - ) - from pydantic.schema import ( - get_annotation_from_field_info as get_annotation_from_field_info, - ) - from pydantic.schema import get_flat_models_from_field as get_flat_models_from_field - from pydantic.schema import ( - get_flat_models_from_fields as get_flat_models_from_fields, - ) - from pydantic.schema import get_model_name_map as get_model_name_map - from pydantic.types import SecretBytes as SecretBytes - from pydantic.types import SecretStr as SecretStr - from pydantic.typing import evaluate_forwardref as evaluate_forwardref - from pydantic.utils import lenient_issubclass as lenient_issubclass - - -else: - from pydantic.v1 import BaseConfig as BaseConfig # type: ignore[assignment] - from pydantic.v1 import BaseModel as BaseModel # type: ignore[assignment] - from pydantic.v1 import ( # type: ignore[assignment] - ValidationError as ValidationError, - ) - from pydantic.v1 import create_model as create_model # type: ignore[no-redef] - from pydantic.v1.class_validators import Validator as Validator - from pydantic.v1.color import Color as Color # type: ignore[assignment] - from pydantic.v1.error_wrappers import ErrorWrapper as ErrorWrapper - from pydantic.v1.errors import MissingError - from pydantic.v1.fields import ( - SHAPE_FROZENSET, - SHAPE_LIST, - SHAPE_SEQUENCE, - SHAPE_SET, - SHAPE_SINGLETON, - SHAPE_TUPLE, - SHAPE_TUPLE_ELLIPSIS, - ) - from pydantic.v1.fields import FieldInfo as FieldInfo # type: ignore[assignment] - from pydantic.v1.fields import ModelField as ModelField - from pydantic.v1.fields import Undefined as Undefined - from pydantic.v1.fields import UndefinedType as UndefinedType - from pydantic.v1.networks import AnyUrl as AnyUrl - from pydantic.v1.networks import ( # type: ignore[assignment] - NameEmail as NameEmail, - ) - from pydantic.v1.schema import TypeModelSet as TypeModelSet - from pydantic.v1.schema import ( - field_schema, - model_process_schema, - ) - from pydantic.v1.schema import ( - get_annotation_from_field_info as get_annotation_from_field_info, - ) - from pydantic.v1.schema import ( - get_flat_models_from_field as get_flat_models_from_field, - ) - from pydantic.v1.schema import ( - get_flat_models_from_fields as get_flat_models_from_fields, - ) - from pydantic.v1.schema import get_model_name_map as get_model_name_map - from pydantic.v1.types import ( # type: ignore[assignment] - SecretBytes as SecretBytes, - ) - from pydantic.v1.types import ( # type: ignore[assignment] - SecretStr as SecretStr, - ) - from pydantic.v1.typing import evaluate_forwardref as evaluate_forwardref - from pydantic.v1.utils import lenient_issubclass as lenient_issubclass - GetJsonSchemaHandler = Any JsonSchemaValue = dict[str, Any] @@ -200,24 +141,6 @@ def is_pv1_scalar_field(field: ModelField) -> bool: return True -def is_pv1_scalar_sequence_field(field: ModelField) -> bool: - if (field.shape in sequence_shapes) and not lenient_issubclass( - field.type_, BaseModel - ): - if field.sub_fields is not None: - for sub_field in field.sub_fields: - if not is_pv1_scalar_field(sub_field): - return False - return True - if shared._annotation_is_sequence(field.type_): - return True - return False - - -def _model_rebuild(model: type[BaseModel]) -> None: - model.update_forward_refs() - - def _model_dump( model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any ) -> Any: @@ -225,7 +148,7 @@ def _model_dump( def _get_model_config(model: BaseModel) -> Any: - return model.__config__ # type: ignore[attr-defined] + return model.__config__ def get_schema_from_model_field( @@ -237,8 +160,10 @@ def get_schema_from_model_field( ], separate_input_output_schemas: bool = True, ) -> dict[str, Any]: - return field_schema( # type: ignore[no-any-return] - field, model_name_map=model_name_map, ref_prefix=REF_PREFIX + return field_schema( + field, + model_name_map=model_name_map, # type: ignore[arg-type] + ref_prefix=REF_PREFIX, )[0] @@ -257,7 +182,7 @@ def get_definitions( dict[str, dict[str, Any]], ]: models = get_flat_models_from_fields(fields, known_models=set()) - return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) + return {}, get_model_definitions(flat_models=models, model_name_map=model_name_map) # type: ignore[arg-type] def is_scalar_field(field: ModelField) -> bool: @@ -268,12 +193,8 @@ def is_sequence_field(field: ModelField) -> bool: return field.shape in sequence_shapes or shared._annotation_is_sequence(field.type_) -def is_scalar_sequence_field(field: ModelField) -> bool: - return is_pv1_scalar_sequence_field(field) - - def is_bytes_field(field: ModelField) -> bool: - return lenient_issubclass(field.type_, bytes) # type: ignore[no-any-return] + return lenient_issubclass(field.type_, bytes) def is_bytes_sequence_field(field: ModelField) -> bool: @@ -288,20 +209,14 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: return sequence_shape_to_type[field.shape](value) # type: ignore[no-any-return] -def get_missing_field_error(loc: tuple[str, ...]) -> dict[str, Any]: - missing_field_error = ErrorWrapper(MissingError(), loc=loc) - new_error = ValidationError([missing_field_error], RequestErrorModel) - return new_error.errors()[0] # type: ignore[return-value] - - def create_body_model( *, fields: Sequence[ModelField], model_name: str ) -> type[BaseModel]: BodyModel = create_model(model_name) for f in fields: - BodyModel.__fields__[f.name] = f # type: ignore[index] + BodyModel.__fields__[f.name] = f return BodyModel def get_model_fields(model: type[BaseModel]) -> list[ModelField]: - return list(model.__fields__.values()) # type: ignore[attr-defined] + return list(model.__fields__.values()) diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index c4fa49e403..cbcb98e1a2 100644 --- a/fastapi/_compat/v2.py +++ b/fastapi/_compat/v2.py @@ -216,10 +216,6 @@ def get_annotation_from_field_info( return annotation -def _model_rebuild(model: type[BaseModel]) -> None: - model.model_rebuild() - - def _model_dump( model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any ) -> Any: diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index b38a326def..492cbfcccb 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -1,4 +1,3 @@ -from collections.abc import Iterable from typing import ( Annotated, Any, @@ -135,27 +134,12 @@ class UploadFile(StarletteUploadFile): """ return await super().close() - @classmethod - def __get_validators__(cls: type["UploadFile"]) -> Iterable[Callable[..., Any]]: - yield cls.validate - - @classmethod - def validate(cls: type["UploadFile"], v: Any) -> Any: - if not isinstance(v, StarletteUploadFile): - raise ValueError(f"Expected UploadFile, received: {type(v)}") - return v - @classmethod def _validate(cls, __input_value: Any, _: Any) -> "UploadFile": if not isinstance(__input_value, StarletteUploadFile): raise ValueError(f"Expected UploadFile, received: {type(__input_value)}") return cast(UploadFile, __input_value) - # TODO: remove when deprecating Pydantic v1 - @classmethod - def __modify_schema__(cls, field_schema: dict[str, Any]) -> None: - field_schema.update({"type": "string", "format": "binary"}) - @classmethod def __get_pydantic_json_schema__( cls, core_schema: CoreSchema, handler: GetJsonSchemaHandler diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 3db006a918..0ba93524c4 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -18,7 +18,6 @@ from typing import ( import anyio from fastapi import params from fastapi._compat import ( - PYDANTIC_V2, ModelField, RequiredParam, Undefined, @@ -410,7 +409,8 @@ def analyze_param( if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)): # Copy `field_info` because we mutate `field_info.default` below. field_info = copy_field_info( - field_info=fastapi_annotation, annotation=use_annotation + field_info=fastapi_annotation, # type: ignore[arg-type] + annotation=use_annotation, ) assert field_info.default in { Undefined, @@ -444,10 +444,9 @@ def analyze_param( "Cannot specify FastAPI annotations in `Annotated` and default value" f" together for {param_name!r}" ) - field_info = value - if PYDANTIC_V2: - if isinstance(field_info, FieldInfo): - field_info.annotation = type_annotation + field_info = value # type: ignore[assignment] + if isinstance(field_info, FieldInfo): + field_info.annotation = type_annotation # Get Depends from type annotation if depends is not None and depends.dependency is None: @@ -485,7 +484,7 @@ def analyze_param( field_info = params.File(annotation=use_annotation, default=default_value) elif not field_annotation_is_scalar(annotation=type_annotation): if annotation_is_pydantic_v1(use_annotation): - field_info = temp_pydantic_v1_params.Body( + field_info = temp_pydantic_v1_params.Body( # type: ignore[assignment] annotation=use_annotation, default=default_value ) else: diff --git a/fastapi/encoders.py b/fastapi/encoders.py index cbeeee4559..549da32797 100644 --- a/fastapi/encoders.py +++ b/fastapi/encoders.py @@ -228,11 +228,11 @@ def jsonable_encoder( # TODO: remove when deprecating Pydantic v1 encoders: dict[Any, Any] = {} if isinstance(obj, may_v1.BaseModel): - encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined] + encoders = getattr(obj.__config__, "json_encoders", {}) if custom_encoder: encoders = {**encoders, **custom_encoder} obj_dict = _model_dump( - obj, + obj, # type: ignore[arg-type] mode="json", include=include, exclude=exclude, diff --git a/fastapi/openapi/models.py b/fastapi/openapi/models.py index 7aa80f5cb0..680f678325 100644 --- a/fastapi/openapi/models.py +++ b/fastapi/openapi/models.py @@ -3,11 +3,9 @@ from enum import Enum from typing import Annotated, Any, Callable, Optional, Union from fastapi._compat import ( - PYDANTIC_V2, CoreSchema, GetJsonSchemaHandler, JsonSchemaValue, - _model_rebuild, with_info_plain_validator_function, ) from fastapi.logger import logger @@ -57,13 +55,7 @@ except ImportError: # pragma: no cover class BaseModelWithConfig(BaseModel): - if PYDANTIC_V2: - model_config = {"extra": "allow"} - - else: - - class Config: - extra = "allow" + model_config = {"extra": "allow"} class Contact(BaseModelWithConfig): @@ -226,13 +218,7 @@ class Example(TypedDict, total=False): value: Optional[Any] externalValue: Optional[AnyUrl] - if PYDANTIC_V2: # type: ignore [misc] - __pydantic_config__ = {"extra": "allow"} - - else: - - class Config: - extra = "allow" + __pydantic_config__ = {"extra": "allow"} # type: ignore[misc] class ParameterInType(Enum): @@ -447,6 +433,6 @@ class OpenAPI(BaseModelWithConfig): externalDocs: Optional[ExternalDocumentation] = None -_model_rebuild(Schema) -_model_rebuild(Operation) -_model_rebuild(Encoding) +Schema.model_rebuild() +Operation.model_rebuild() +Encoding.model_rebuild() diff --git a/fastapi/params.py b/fastapi/params.py index 4990d0e70e..c776c4a59e 100644 --- a/fastapi/params.py +++ b/fastapi/params.py @@ -9,8 +9,6 @@ from pydantic.fields import FieldInfo from typing_extensions import Literal, deprecated from ._compat import ( - PYDANTIC_V2, - PYDANTIC_VERSION_MINOR_TUPLE, Undefined, ) @@ -111,29 +109,24 @@ class Param(FieldInfo): # type: ignore[misc] stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra - if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): - self.deprecated = deprecated - else: - kwargs["deprecated"] = deprecated - if PYDANTIC_V2: - if serialization_alias in (_Unset, None) and isinstance(alias, str): - serialization_alias = alias - if validation_alias in (_Unset, None): - validation_alias = alias - kwargs.update( - { - "annotation": annotation, - "alias_priority": alias_priority, - "validation_alias": validation_alias, - "serialization_alias": serialization_alias, - "strict": strict, - "json_schema_extra": current_json_schema_extra, - } - ) - kwargs["pattern"] = pattern or regex - else: - kwargs["regex"] = pattern or regex - kwargs.update(**current_json_schema_extra) + kwargs["deprecated"] = deprecated + + if serialization_alias in (_Unset, None) and isinstance(alias, str): + serialization_alias = alias + if validation_alias in (_Unset, None): + validation_alias = alias + kwargs.update( + { + "annotation": annotation, + "alias_priority": alias_priority, + "validation_alias": validation_alias, + "serialization_alias": serialization_alias, + "strict": strict, + "json_schema_extra": current_json_schema_extra, + } + ) + kwargs["pattern"] = pattern or regex + use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} super().__init__(**use_kwargs) @@ -571,29 +564,22 @@ class Body(FieldInfo): # type: ignore[misc] stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra - if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): - self.deprecated = deprecated - else: - kwargs["deprecated"] = deprecated - if PYDANTIC_V2: - if serialization_alias in (_Unset, None) and isinstance(alias, str): - serialization_alias = alias - if validation_alias in (_Unset, None): - validation_alias = alias - kwargs.update( - { - "annotation": annotation, - "alias_priority": alias_priority, - "validation_alias": validation_alias, - "serialization_alias": serialization_alias, - "strict": strict, - "json_schema_extra": current_json_schema_extra, - } - ) - kwargs["pattern"] = pattern or regex - else: - kwargs["regex"] = pattern or regex - kwargs.update(**current_json_schema_extra) + kwargs["deprecated"] = deprecated + if serialization_alias in (_Unset, None) and isinstance(alias, str): + serialization_alias = alias + if validation_alias in (_Unset, None): + validation_alias = alias + kwargs.update( + { + "annotation": annotation, + "alias_priority": alias_priority, + "validation_alias": validation_alias, + "serialization_alias": serialization_alias, + "strict": strict, + "json_schema_extra": current_json_schema_extra, + } + ) + kwargs["pattern"] = pattern or regex use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} diff --git a/fastapi/routing.py b/fastapi/routing.py index fa6904a6b6..a1f2e44bb4 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -1,4 +1,3 @@ -import dataclasses import email.message import functools import inspect @@ -30,6 +29,7 @@ from fastapi._compat import ( _model_dump, _normalize_errors, lenient_issubclass, + may_v1, ) from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.dependencies.models import Dependant @@ -58,7 +58,6 @@ from fastapi.utils import ( get_value_or_default, is_body_allowed_for_status_code, ) -from pydantic import BaseModel from starlette import routing from starlette._exception_handler import wrap_app_handling_exceptions from starlette._utils import is_async_callable @@ -153,8 +152,8 @@ def _prepare_response_content( exclude_defaults: bool = False, exclude_none: bool = False, ) -> Any: - if isinstance(res, BaseModel): - read_with_orm_mode = getattr(_get_model_config(res), "read_with_orm_mode", None) + if isinstance(res, may_v1.BaseModel): + read_with_orm_mode = getattr(_get_model_config(res), "read_with_orm_mode", None) # type: ignore[arg-type] if read_with_orm_mode: # Let from_orm extract the data from this model instead of converting # it now to a dict. @@ -162,7 +161,7 @@ def _prepare_response_content( # access instead of dict iteration, e.g. lazy relationships. return res return _model_dump( - res, + res, # type: ignore[arg-type] by_alias=True, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, @@ -188,9 +187,6 @@ def _prepare_response_content( ) for k, v in res.items() } - elif dataclasses.is_dataclass(res): - assert not isinstance(res, type) - return dataclasses.asdict(res) return res diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py index b6c804dc44..1bda0ea9b2 100644 --- a/fastapi/temp_pydantic_v1_params.py +++ b/fastapi/temp_pydantic_v1_params.py @@ -6,12 +6,11 @@ from fastapi.params import ParamTypes from typing_extensions import deprecated from ._compat.may_v1 import FieldInfo, Undefined -from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE _Unset: Any = Undefined -class Param(FieldInfo): # type: ignore[misc] +class Param(FieldInfo): in_: ParamTypes def __init__( @@ -98,10 +97,7 @@ class Param(FieldInfo): # type: ignore[misc] stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra - if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): - self.deprecated = deprecated - else: - kwargs["deprecated"] = deprecated + kwargs["deprecated"] = deprecated kwargs["regex"] = pattern or regex kwargs.update(**current_json_schema_extra) use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} @@ -112,7 +108,7 @@ class Param(FieldInfo): # type: ignore[misc] return f"{self.__class__.__name__}({self.default})" -class Path(Param): # type: ignore[misc] +class Path(Param): in_ = ParamTypes.path def __init__( @@ -198,7 +194,7 @@ class Path(Param): # type: ignore[misc] ) -class Query(Param): # type: ignore[misc] +class Query(Param): in_ = ParamTypes.query def __init__( @@ -282,7 +278,7 @@ class Query(Param): # type: ignore[misc] ) -class Header(Param): # type: ignore[misc] +class Header(Param): in_ = ParamTypes.header def __init__( @@ -368,7 +364,7 @@ class Header(Param): # type: ignore[misc] ) -class Cookie(Param): # type: ignore[misc] +class Cookie(Param): in_ = ParamTypes.cookie def __init__( @@ -452,7 +448,7 @@ class Cookie(Param): # type: ignore[misc] ) -class Body(FieldInfo): # type: ignore[misc] +class Body(FieldInfo): def __init__( self, default: Any = Undefined, @@ -541,10 +537,7 @@ class Body(FieldInfo): # type: ignore[misc] stacklevel=4, ) current_json_schema_extra = json_schema_extra or extra - if PYDANTIC_VERSION_MINOR_TUPLE < (2, 7): - self.deprecated = deprecated - else: - kwargs["deprecated"] = deprecated + kwargs["deprecated"] = deprecated kwargs["regex"] = pattern or regex kwargs.update(**current_json_schema_extra) @@ -556,7 +549,7 @@ class Body(FieldInfo): # type: ignore[misc] return f"{self.__class__.__name__}({self.default})" -class Form(Body): # type: ignore[misc] +class Form(Body): def __init__( self, default: Any = Undefined, @@ -640,7 +633,7 @@ class Form(Body): # type: ignore[misc] ) -class File(Form): # type: ignore[misc] +class File(Form): def __init__( self, default: Any = Undefined, diff --git a/fastapi/utils.py b/fastapi/utils.py index 5b93b9af8e..c4631d7ed2 100644 --- a/fastapi/utils.py +++ b/fastapi/utils.py @@ -1,7 +1,6 @@ import re import warnings from collections.abc import MutableMapping -from dataclasses import is_dataclass from typing import ( TYPE_CHECKING, Any, @@ -13,7 +12,6 @@ from weakref import WeakKeyDictionary import fastapi from fastapi._compat import ( - PYDANTIC_V2, BaseConfig, ModelField, PydanticSchemaGenerationError, @@ -29,6 +27,8 @@ from pydantic import BaseModel from pydantic.fields import FieldInfo from typing_extensions import Literal +from ._compat import v2 + if TYPE_CHECKING: # pragma: nocover from .routing import APIRoute @@ -105,14 +105,12 @@ def create_model_field( from fastapi._compat import v1 try: - return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] + return v1.ModelField(**v1_kwargs) # type: ignore[return-value] except RuntimeError: raise fastapi.exceptions.FastAPIError( _invalid_args_message.format(type_=type_) ) from None - elif PYDANTIC_V2: - from ._compat import v2 - + else: field_info = field_info or FieldInfo( annotation=type_, default=default, alias=alias ) @@ -128,7 +126,7 @@ def create_model_field( from fastapi._compat import v1 try: - return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return] + return v1.ModelField(**v1_kwargs) except RuntimeError: raise fastapi.exceptions.FastAPIError( _invalid_args_message.format(type_=type_) @@ -140,11 +138,8 @@ def create_cloned_field( *, cloned_types: Optional[MutableMapping[type[BaseModel], type[BaseModel]]] = None, ) -> ModelField: - if PYDANTIC_V2: - from ._compat import v2 - - if isinstance(field, v2.ModelField): - return field + if isinstance(field, v2.ModelField): + return field from fastapi._compat import v1 @@ -154,8 +149,6 @@ def create_cloned_field( cloned_types = _CLONED_TYPES_CACHE original_type = field.type_ - if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"): - original_type = original_type.__pydantic_model__ use_type = original_type if lenient_issubclass(original_type, v1.BaseModel): original_type = cast(type[v1.BaseModel], original_type) diff --git a/pyproject.toml b/pyproject.toml index dd16e1f8fa..ae97cb71bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ classifiers = [ "Framework :: AsyncIO", "Framework :: FastAPI", "Framework :: Pydantic", - "Framework :: Pydantic :: 1", "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "Programming Language :: Python :: 3 :: Only", @@ -45,7 +44,7 @@ classifiers = [ ] dependencies = [ "starlette>=0.40.0,<0.51.0", - "pydantic>=1.7.4,!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0", + "pydantic>=2.7.0", "typing-extensions>=4.8.0", "annotated-doc>=0.0.2", ] @@ -71,11 +70,10 @@ standard = [ "email-validator >=2.0.0", # Uvicorn with uvloop "uvicorn[standard] >=0.12.0", - # TODO: this should be part of some pydantic optional extra dependencies # # Settings management - # "pydantic-settings >=2.0.0", + "pydantic-settings >=2.0.0", # # Extra Pydantic data types - # "pydantic-extra-types >=2.0.0", + "pydantic-extra-types >=2.0.0", ] standard-no-fastapi-cloud-cli = [ @@ -90,11 +88,10 @@ standard-no-fastapi-cloud-cli = [ "email-validator >=2.0.0", # Uvicorn with uvloop "uvicorn[standard] >=0.12.0", - # TODO: this should be part of some pydantic optional extra dependencies # # Settings management - # "pydantic-settings >=2.0.0", + "pydantic-settings >=2.0.0", # # Extra Pydantic data types - # "pydantic-extra-types >=2.0.0", + "pydantic-extra-types >=2.0.0", ] all = [ @@ -183,8 +180,6 @@ filterwarnings = [ # Ref: https://github.com/python-trio/trio/pull/3054 # Remove once there's a new version of Trio 'ignore:The `hash` argument is deprecated*:DeprecationWarning:trio', - # Ignore flaky coverage / pytest warning about SQLite connection, only applies to Python 3.13 and Pydantic v1 - 'ignore:Exception ignored in. = (3, 14): skip_module_if_py_gte_314() @@ -588,23 +588,14 @@ def test_openapi_schema(): "required": True, "content": { "application/json": { - "schema": pydantic_snapshot( - v1=snapshot( + "schema": { + "title": "Body", + "allOf": [ { "$ref": "#/components/schemas/Body_update_item_items__item_id__put" } - ), - v2=snapshot( - { - "title": "Body", - "allOf": [ - { - "$ref": "#/components/schemas/Body_update_item_items__item_id__put" - } - ], - } - ), - ), + ], + } } }, }, @@ -809,23 +800,14 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v1=snapshot( + "schema": { + "allOf": [ { "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post" } - ), - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_create_item_embed_items_embed__post" - } - ], - "title": "Body", - } - ), - ), + ], + "title": "Body", + } } }, "required": True, @@ -855,23 +837,14 @@ def test_openapi_schema(): "requestBody": { "content": { "application/x-www-form-urlencoded": { - "schema": pydantic_snapshot( - v1=snapshot( + "schema": { + "allOf": [ { "$ref": "#/components/schemas/Body_submit_form_form_data__post" } - ), - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_submit_form_form_data__post" - } - ], - "title": "Body", - } - ), - ), + ], + "title": "Body", + } } }, "required": True, @@ -901,23 +874,14 @@ def test_openapi_schema(): "requestBody": { "content": { "multipart/form-data": { - "schema": pydantic_snapshot( - v1=snapshot( + "schema": { + "allOf": [ { "$ref": "#/components/schemas/Body_upload_file_upload__post" } - ), - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_upload_file_upload__post" - } - ], - "title": "Body", - } - ), - ), + ], + "title": "Body", + }, } }, "required": True, @@ -947,23 +911,14 @@ def test_openapi_schema(): "requestBody": { "content": { "multipart/form-data": { - "schema": pydantic_snapshot( - v1=snapshot( + "schema": { + "allOf": [ { "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post" } - ), - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Body_upload_multiple_files_upload_multiple__post" - } - ], - "title": "Body", - } - ), - ), + ], + "title": "Body", + } } }, "required": True, @@ -990,21 +945,12 @@ def test_openapi_schema(): "components": { "schemas": { "Body_create_item_embed_items_embed__post": { - "properties": pydantic_snapshot( - v1=snapshot( - {"item": {"$ref": "#/components/schemas/Item"}} - ), - v2=snapshot( - { - "item": { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - } - } - ), - ), + "properties": { + "item": { + "allOf": [{"$ref": "#/components/schemas/Item"}], + "title": "Item", + } + }, "type": "object", "required": ["item"], "title": "Body_create_item_embed_items_embed__post", @@ -1030,17 +976,10 @@ def test_openapi_schema(): }, "Body_update_item_items__item_id__put": { "properties": { - "item": pydantic_snapshot( - v1=snapshot({"$ref": "#/components/schemas/Item"}), - v2=snapshot( - { - "allOf": [ - {"$ref": "#/components/schemas/Item"} - ], - "title": "Item", - } - ), - ), + "item": { + "allOf": [{"$ref": "#/components/schemas/Item"}], + "title": "Item", + }, "importance": { "type": "integer", "maximum": 10.0, diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py index f2e42999b3..e7f969f7cf 100644 --- a/tests/test_computed_fields.py +++ b/tests/test_computed_fields.py @@ -2,8 +2,6 @@ import pytest from fastapi import FastAPI from fastapi.testclient import TestClient -from .utils import needs_pydanticv2 - @pytest.fixture(name="client") def get_client(request): @@ -35,7 +33,6 @@ def get_client(request): @pytest.mark.parametrize("client", [True, False], indirect=True) @pytest.mark.parametrize("path", ["/", "/responses"]) -@needs_pydanticv2 def test_get(client: TestClient, path: str): response = client.get(path) assert response.status_code == 200, response.text @@ -43,7 +40,6 @@ def test_get(client: TestClient, path: str): @pytest.mark.parametrize("client", [True, False], indirect=True) -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py index bc3e1c5ec3..60b795e9ba 100644 --- a/tests/test_custom_schema_fields.py +++ b/tests/test_custom_schema_fields.py @@ -1,12 +1,8 @@ from typing import Annotated, Optional from fastapi import FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient -from pydantic import BaseModel - -if PYDANTIC_V2: - from pydantic import WithJsonSchema +from pydantic import BaseModel, WithJsonSchema app = FastAPI() @@ -14,23 +10,15 @@ app = FastAPI() class Item(BaseModel): name: str - if PYDANTIC_V2: - description: Annotated[ - Optional[str], WithJsonSchema({"type": ["string", "null"]}) - ] = None + description: Annotated[ + Optional[str], WithJsonSchema({"type": ["string", "null"]}) + ] = None - model_config = { - "json_schema_extra": { - "x-something-internal": {"level": 4}, - } + model_config = { + "json_schema_extra": { + "x-something-internal": {"level": 4}, } - else: - description: Optional[str] = None # type: ignore[no-redef] - - class Config: - schema_extra = { - "x-something-internal": {"level": 4}, - } + } @app.get("/foo", response_model=Item) @@ -55,7 +43,7 @@ item_schema = { }, "description": { "title": "Description", - "type": ["string", "null"] if PYDANTIC_V2 else "string", + "type": ["string", "null"], }, }, } diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py index c175147bc3..29a70cae0c 100644 --- a/tests/test_datastructures.py +++ b/tests/test_datastructures.py @@ -7,12 +7,6 @@ from fastapi.datastructures import Default from fastapi.testclient import TestClient -# TODO: remove when deprecating Pydantic v1 -def test_upload_file_invalid(): - with pytest.raises(ValueError): - UploadFile.validate("not a Starlette UploadFile") - - def test_upload_file_invalid_pydantic_v2(): with pytest.raises(ValueError): UploadFile._validate("not a Starlette UploadFile", {}) diff --git a/tests/test_datetime_custom_encoder.py b/tests/test_datetime_custom_encoder.py index 3aa77c0b1d..822651f4f1 100644 --- a/tests/test_datetime_custom_encoder.py +++ b/tests/test_datetime_custom_encoder.py @@ -4,10 +4,9 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel -from .utils import needs_pydanticv1, needs_pydanticv2 +from .utils import needs_pydanticv1 -@needs_pydanticv2 def test_pydanticv2(): from pydantic import field_serializer @@ -34,7 +33,9 @@ def test_pydanticv2(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 def test_pydanticv1(): - class ModelWithDatetimeField(BaseModel): + from pydantic import v1 + + class ModelWithDatetimeField(v1.BaseModel): dt_field: datetime class Config: diff --git a/tests/test_filter_pydantic_sub_model/app_pv1.py b/tests/test_filter_pydantic_sub_model/app_pv1.py index 657e8c5d12..0b6ab53e04 100644 --- a/tests/test_filter_pydantic_sub_model/app_pv1.py +++ b/tests/test_filter_pydantic_sub_model/app_pv1.py @@ -1,7 +1,7 @@ from typing import Optional from fastapi import Depends, FastAPI -from pydantic import BaseModel, validator +from pydantic.v1 import BaseModel, validator app = FastAPI() @@ -18,6 +18,7 @@ class ModelA(BaseModel): name: str description: Optional[str] = None model_b: ModelB + tags: dict[str, str] = {} @validator("name") def lower_username(cls, name: str, values): @@ -32,4 +33,9 @@ async def get_model_c() -> ModelC: @app.get("/model/{name}", response_model=ModelA) async def get_model_a(name: str, model_c=Depends(get_model_c)): - return {"name": name, "description": "model-a-desc", "model_b": model_c} + return { + "name": name, + "description": "model-a-desc", + "model_b": model_c, + "tags": {"key1": "value1", "key2": "value2"}, + } diff --git a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py index 48732dbf06..b464b4f572 100644 --- a/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py +++ b/tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py @@ -1,6 +1,7 @@ import pytest from fastapi.exceptions import ResponseValidationError from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ..utils import needs_pydanticv1 @@ -21,6 +22,7 @@ def test_filter_sub_model(client: TestClient): "name": "modelA", "description": "model-a-desc", "model_b": {"username": "test-user"}, + "tags": {"key1": "value1", "key2": "value2"}, } @@ -41,90 +43,104 @@ def test_validator_is_cloned(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/model/{name}": { - "get": { - "summary": "Get Model A", - "operationId": "get_model_a_model__name__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Name", "type": "string"}, - "name": "name", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ModelA"} - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/model/{name}": { + "get": { + "summary": "Get Model A", + "operationId": "get_model_a_model__name__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Name", "type": "string"}, + "name": "name", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelA" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ModelA": { + "title": "ModelA", + "required": ["name", "model_b"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": {"title": "Description", "type": "string"}, + "model_b": {"$ref": "#/components/schemas/ModelB"}, + "tags": { + "additionalProperties": {"type": "string"}, + "type": "object", + "title": "Tags", + "default": {}, }, }, }, + "ModelB": { + "title": "ModelB", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"} + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ModelA": { - "title": "ModelA", - "required": ["name", "model_b"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "model_b": {"$ref": "#/components/schemas/ModelB"}, - }, - }, - "ModelB": { - "title": "ModelB", - "required": ["username"], - "type": "object", - "properties": {"username": {"title": "Username", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py index 2e2c26ddcb..d70f530435 100644 --- a/tests/test_filter_pydantic_sub_model_pv2.py +++ b/tests/test_filter_pydantic_sub_model_pv2.py @@ -5,8 +5,7 @@ from dirty_equals import HasRepr, IsDict, IsOneOf from fastapi import Depends, FastAPI from fastapi.exceptions import ResponseValidationError from fastapi.testclient import TestClient - -from .utils import needs_pydanticv2 +from inline_snapshot import snapshot @pytest.fixture(name="client") @@ -25,6 +24,7 @@ def get_client(): name: str description: Optional[str] = None foo: ModelB + tags: dict[str, str] = {} @field_validator("name") def lower_username(cls, name: str, info: ValidationInfo): @@ -37,13 +37,17 @@ def get_client(): @app.get("/model/{name}", response_model=ModelA) async def get_model_a(name: str, model_c=Depends(get_model_c)): - return {"name": name, "description": "model-a-desc", "foo": model_c} + return { + "name": name, + "description": "model-a-desc", + "foo": model_c, + "tags": {"key1": "value1", "key2": "value2"}, + } client = TestClient(app) return client -@needs_pydanticv2 def test_filter_sub_model(client: TestClient): response = client.get("/model/modelA") assert response.status_code == 200, response.text @@ -51,10 +55,10 @@ def test_filter_sub_model(client: TestClient): "name": "modelA", "description": "model-a-desc", "foo": {"username": "test-user"}, + "tags": {"key1": "value1", "key2": "value2"}, } -@needs_pydanticv2 def test_validator_is_cloned(client: TestClient): with pytest.raises(ResponseValidationError) as err: client.get("/model/modelX") @@ -79,106 +83,119 @@ def test_validator_is_cloned(client: TestClient): ] -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/model/{name}": { - "get": { - "summary": "Get Model A", - "operationId": "get_model_a_model__name__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Name", "type": "string"}, - "name": "name", - "in": "path", - } - ], - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/ModelA"} - } + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/model/{name}": { + "get": { + "summary": "Get Model A", + "operationId": "get_model_a_model__name__get", + "parameters": [ + { + "required": True, + "schema": {"title": "Name", "type": "string"}, + "name": "name", + "in": "path", + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ModelA" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, }, }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + } + }, + }, + "ModelA": { + "title": "ModelA", + "required": IsOneOf( + ["name", "description", "foo"], + # TODO remove when deprecating Pydantic v1 + ["name", "foo"], + ), + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], } + ) + | + # TODO remove when deprecating Pydantic v1 + IsDict({"title": "Description", "type": "string"}), + "foo": {"$ref": "#/components/schemas/ModelB"}, + "tags": { + "additionalProperties": {"type": "string"}, + "type": "object", + "title": "Tags", + "default": {}, }, }, }, + "ModelB": { + "title": "ModelB", + "required": ["username"], + "type": "object", + "properties": { + "username": {"title": "Username", "type": "string"} + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "ModelA": { - "title": "ModelA", - "required": IsOneOf( - ["name", "description", "foo"], - # TODO remove when deprecating Pydantic v1 - ["name", "foo"], - ), - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": IsDict( - { - "title": "Description", - "anyOf": [{"type": "string"}, {"type": "null"}], - } - ) - | - # TODO remove when deprecating Pydantic v1 - IsDict({"title": "Description", "type": "string"}), - "foo": {"$ref": "#/components/schemas/ModelB"}, - }, - }, - "ModelB": { - "title": "ModelB", - "required": ["username"], - "type": "object", - "properties": {"username": {"title": "Username", "type": "string"}}, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } + }, + } + ) diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py index b149b76539..c401cc9374 100644 --- a/tests/test_forms_single_model.py +++ b/tests/test_forms_single_model.py @@ -2,7 +2,6 @@ from typing import Annotated, Optional from dirty_equals import IsDict from fastapi import FastAPI, Form -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -20,12 +19,7 @@ class FormModel(BaseModel): class FormModelExtraAllow(BaseModel): param: str - if PYDANTIC_V2: - model_config = {"extra": "allow"} - else: - - class Config: - extra = "allow" + model_config = {"extra": "allow"} @app.post("/form/") diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py index 215d06a072..50d799a571 100644 --- a/tests/test_get_model_definitions_formfeed_escape.py +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -1,181 +1,169 @@ -from collections.abc import Iterator -from typing import Any - -import fastapi._compat -import fastapi.openapi.utils -import pydantic.schema import pytest from fastapi import FastAPI -from pydantic import BaseModel -from starlette.testclient import TestClient +from fastapi.testclient import TestClient +from inline_snapshot import snapshot from .utils import needs_pydanticv1 -class Address(BaseModel): - """ - This is a public description of an Address - \f - You can't see this part of the docstring, it's private! - """ +@pytest.fixture( + name="client", + params=[ + pytest.param("pydantic-v1", marks=needs_pydanticv1), + "pydantic-v2", + ], +) +def client_fixture(request: pytest.FixtureRequest) -> TestClient: + if request.param == "pydantic-v1": + from pydantic.v1 import BaseModel + else: + from pydantic import BaseModel - line_1: str - city: str - state_province: str + class Address(BaseModel): + """ + This is a public description of an Address + \f + You can't see this part of the docstring, it's private! + """ + + line_1: str + city: str + state_province: str + + class Facility(BaseModel): + id: str + address: Address + + app = FastAPI() + + @app.get("/facilities/{facility_id}") + def get_facility(facility_id: str) -> Facility: + return Facility( + id=facility_id, + address=Address(line_1="123 Main St", city="Anytown", state_province="CA"), + ) + + client = TestClient(app) + return client -class Facility(BaseModel): - id: str - address: Address +def test_get(client: TestClient): + response = client.get("/facilities/42") + assert response.status_code == 200, response.text + assert response.json() == { + "id": "42", + "address": { + "line_1": "123 Main St", + "city": "Anytown", + "state_province": "CA", + }, + } -app = FastAPI() - -client = TestClient(app) - - -@app.get("/facilities/{facility_id}") -def get_facility(facility_id: str) -> Facility: ... - - -openapi_schema = { - "components": { - "schemas": { - "Address": { - # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring - "description": "This is a public description of an Address\n", - "properties": { - "city": {"title": "City", "type": "string"}, - "line_1": {"title": "Line 1", "type": "string"}, - "state_province": {"title": "State Province", "type": "string"}, - }, - "required": ["line_1", "city", "state_province"], - "title": "Address", - "type": "object", - }, - "Facility": { - "properties": { - "address": {"$ref": "#/components/schemas/Address"}, - "id": {"title": "Id", "type": "string"}, - }, - "required": ["id", "address"], - "title": "Facility", - "type": "object", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "title": "Detail", - "type": "array", - } - }, - "title": "HTTPValidationError", - "type": "object", - }, - "ValidationError": { - "properties": { - "loc": { - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - "title": "Location", - "type": "array", - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - "required": ["loc", "msg", "type"], - "title": "ValidationError", - "type": "object", - }, - } - }, - "info": {"title": "FastAPI", "version": "0.1.0"}, - "openapi": "3.1.0", - "paths": { - "/facilities/{facility_id}": { - "get": { - "operationId": "get_facility_facilities__facility_id__get", - "parameters": [ - { - "in": "path", - "name": "facility_id", - "required": True, - "schema": {"title": "Facility Id", "type": "string"}, - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Facility"} - } - }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - "description": "Validation Error", - }, - }, - "summary": "Get Facility", - } - } - }, -} - - -def test_openapi_schema(): +def test_openapi_schema(client: TestClient): """ Sanity check to ensure our app's openapi schema renders as we expect """ response = client.get("/openapi.json") assert response.status_code == 200, response.text - assert response.json() == openapi_schema - - -class SortedTypeSet(set): - """ - Set of Types whose `__iter__()` method yields results sorted by the type names - """ - - def __init__(self, seq: set[type[Any]], *, sort_reversed: bool): - super().__init__(seq) - self.sort_reversed = sort_reversed - - def __iter__(self) -> Iterator[type[Any]]: - members_sorted = sorted( - super().__iter__(), - key=lambda type_: type_.__name__, - reverse=self.sort_reversed, - ) - yield from members_sorted - - -@needs_pydanticv1 -@pytest.mark.parametrize("sort_reversed", [True, False]) -def test_model_description_escaped_with_formfeed(sort_reversed: bool): - """ - Regression test for bug fixed by https://github.com/fastapi/fastapi/pull/6039. - - Test `get_model_definitions` with models passed in different order. - """ - from fastapi._compat import v1 - - all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes) - - flat_models = v1.get_flat_models_from_fields(all_fields, known_models=set()) - model_name_map = pydantic.schema.get_model_name_map(flat_models) - - expected_address_description = "This is a public description of an Address\n" - - models = v1.get_model_definitions( - flat_models=SortedTypeSet(flat_models, sort_reversed=sort_reversed), - model_name_map=model_name_map, + assert response.json() == snapshot( + { + "components": { + "schemas": { + "Address": { + # NOTE: the description of this model shows only the public-facing text, before the `\f` in docstring + "description": "This is a public description of an Address\n", + "properties": { + "city": {"title": "City", "type": "string"}, + "line_1": {"title": "Line 1", "type": "string"}, + "state_province": { + "title": "State Province", + "type": "string", + }, + }, + "required": ["line_1", "city", "state_province"], + "title": "Address", + "type": "object", + }, + "Facility": { + "properties": { + "address": {"$ref": "#/components/schemas/Address"}, + "id": {"title": "Id", "type": "string"}, + }, + "required": ["id", "address"], + "title": "Facility", + "type": "object", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "title": "Detail", + "type": "array", + } + }, + "title": "HTTPValidationError", + "type": "object", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "title": "Location", + "type": "array", + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + "required": ["loc", "msg", "type"], + "title": "ValidationError", + "type": "object", + }, + } + }, + "info": {"title": "FastAPI", "version": "0.1.0"}, + "openapi": "3.1.0", + "paths": { + "/facilities/{facility_id}": { + "get": { + "operationId": "get_facility_facilities__facility_id__get", + "parameters": [ + { + "in": "path", + "name": "facility_id", + "required": True, + "schema": {"title": "Facility Id", "type": "string"}, + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Facility" + } + } + }, + "description": "Successful Response", + }, + "422": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + "description": "Validation Error", + }, + }, + "summary": "Get Facility", + } + } + }, + } ) - assert models["Address"]["description"] == expected_address_description diff --git a/tests/test_inherited_custom_class.py b/tests/test_inherited_custom_class.py index fe9350f4ef..7f29fe33ed 100644 --- a/tests/test_inherited_custom_class.py +++ b/tests/test_inherited_custom_class.py @@ -5,7 +5,7 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel -from .utils import needs_pydanticv1, needs_pydanticv2 +from .utils import needs_pydanticv1 class MyUuid: @@ -26,7 +26,6 @@ class MyUuid: raise TypeError("vars() argument must have __dict__ attribute") -@needs_pydanticv2 def test_pydanticv2(): from pydantic import field_serializer @@ -73,6 +72,8 @@ def test_pydanticv2(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 def test_pydanticv1(): + from pydantic import v1 + app = FastAPI() @app.get("/fast_uuid") @@ -84,7 +85,7 @@ def test_pydanticv1(): vars(asyncpg_uuid) return {"fast_uuid": asyncpg_uuid} - class SomeCustomClass(BaseModel): + class SomeCustomClass(v1.BaseModel): class Config: arbitrary_types_allowed = True json_encoders = {uuid.UUID: str} diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 3b6513e27b..81bf94ece0 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -8,11 +8,11 @@ from pathlib import PurePath, PurePosixPath, PureWindowsPath from typing import Optional import pytest -from fastapi._compat import PYDANTIC_V2, Undefined +from fastapi._compat import Undefined from fastapi.encoders import jsonable_encoder from pydantic import BaseModel, Field, ValidationError -from .utils import needs_pydanticv1, needs_pydanticv2 +from .utils import needs_pydanticv1 class Person: @@ -59,12 +59,7 @@ class RoleEnum(Enum): class ModelWithConfig(BaseModel): role: Optional[RoleEnum] = None - if PYDANTIC_V2: - model_config = {"use_enum_values": True} - else: - - class Config: - use_enum_values = True + model_config = {"use_enum_values": True} class ModelWithAlias(BaseModel): @@ -89,6 +84,18 @@ def test_encode_dict(): } +def test_encode_dict_include_exclude_list(): + pet = {"name": "Firulais", "owner": {"name": "Foo"}} + assert jsonable_encoder(pet) == {"name": "Firulais", "owner": {"name": "Foo"}} + assert jsonable_encoder(pet, include=["name"]) == {"name": "Firulais"} + assert jsonable_encoder(pet, exclude=["owner"]) == {"name": "Firulais"} + assert jsonable_encoder(pet, include=[]) == {} + assert jsonable_encoder(pet, exclude=[]) == { + "name": "Firulais", + "owner": {"name": "Foo"}, + } + + def test_encode_class(): person = Person(name="Foo") pet = Pet(owner=person, name="Firulais") @@ -130,7 +137,6 @@ def test_encode_unsupported(): jsonable_encoder(unserializable) -@needs_pydanticv2 def test_encode_custom_json_encoders_model_pydanticv2(): from pydantic import field_serializer @@ -153,7 +159,9 @@ def test_encode_custom_json_encoders_model_pydanticv2(): # TODO: remove when deprecating Pydantic v1 @needs_pydanticv1 def test_encode_custom_json_encoders_model_pydanticv1(): - class ModelWithCustomEncoder(BaseModel): + from pydantic import v1 + + class ModelWithCustomEncoder(v1.BaseModel): dt_field: datetime class Config: @@ -208,10 +216,12 @@ def test_encode_model_with_default(): @needs_pydanticv1 def test_custom_encoders(): + from pydantic import v1 + class safe_datetime(datetime): pass - class MyModel(BaseModel): + class MyModel(v1.BaseModel): dt_field: safe_datetime instance = MyModel(dt_field=safe_datetime.now()) @@ -244,12 +254,7 @@ def test_encode_model_with_pure_path(): class ModelWithPath(BaseModel): path: PurePath - if PYDANTIC_V2: - model_config = {"arbitrary_types_allowed": True} - else: - - class Config: - arbitrary_types_allowed = True + model_config = {"arbitrary_types_allowed": True} test_path = PurePath("/foo", "bar") obj = ModelWithPath(path=test_path) @@ -260,12 +265,7 @@ def test_encode_model_with_pure_posix_path(): class ModelWithPath(BaseModel): path: PurePosixPath - if PYDANTIC_V2: - model_config = {"arbitrary_types_allowed": True} - else: - - class Config: - arbitrary_types_allowed = True + model_config = {"arbitrary_types_allowed": True} obj = ModelWithPath(path=PurePosixPath("/foo", "bar")) assert jsonable_encoder(obj) == {"path": "/foo/bar"} @@ -275,45 +275,44 @@ def test_encode_model_with_pure_windows_path(): class ModelWithPath(BaseModel): path: PureWindowsPath - if PYDANTIC_V2: - model_config = {"arbitrary_types_allowed": True} - else: - - class Config: - arbitrary_types_allowed = True + model_config = {"arbitrary_types_allowed": True} obj = ModelWithPath(path=PureWindowsPath("/foo", "bar")) assert jsonable_encoder(obj) == {"path": "\\foo\\bar"} +def test_encode_pure_path(): + test_path = PurePath("/foo", "bar") + + assert jsonable_encoder({"path": test_path}) == {"path": str(test_path)} + + @needs_pydanticv1 def test_encode_root(): - class ModelWithRoot(BaseModel): + from pydantic import v1 + + class ModelWithRoot(v1.BaseModel): __root__: str model = ModelWithRoot(__root__="Foo") assert jsonable_encoder(model) == "Foo" -@needs_pydanticv2 def test_decimal_encoder_float(): data = {"value": Decimal(1.23)} assert jsonable_encoder(data) == {"value": 1.23} -@needs_pydanticv2 def test_decimal_encoder_int(): data = {"value": Decimal(2)} assert jsonable_encoder(data) == {"value": 2} -@needs_pydanticv2 def test_decimal_encoder_nan(): data = {"value": Decimal("NaN")} assert isnan(jsonable_encoder(data)["value"]) -@needs_pydanticv2 def test_decimal_encoder_infinity(): data = {"value": Decimal("Infinity")} assert isinf(jsonable_encoder(data)["value"]) @@ -330,7 +329,6 @@ def test_encode_deque_encodes_child_models(): assert jsonable_encoder(dq)[0]["test"] == "test" -@needs_pydanticv2 def test_encode_pydantic_undefined(): data = {"value": Undefined} assert jsonable_encoder(data) == {"value": None} diff --git a/tests/test_no_schema_split.py b/tests/test_no_schema_split.py index 6169867ba3..131a3755e7 100644 --- a/tests/test_no_schema_split.py +++ b/tests/test_no_schema_split.py @@ -9,8 +9,6 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel, Field -from tests.utils import pydantic_snapshot - class MessageEventType(str, Enum): alpha = "alpha" @@ -126,47 +124,21 @@ def test_openapi_schema(): }, "MessageEvent": { "properties": { - "event_type": pydantic_snapshot( - v2=snapshot( - { - "$ref": "#/components/schemas/MessageEventType", - "default": "alpha", - } - ), - v1=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/MessageEventType" - } - ], - "default": "alpha", - } - ), - ), + "event_type": { + "$ref": "#/components/schemas/MessageEventType", + "default": "alpha", + }, "output": {"type": "string", "title": "Output"}, }, "type": "object", "required": ["output"], "title": "MessageEvent", }, - "MessageEventType": pydantic_snapshot( - v2=snapshot( - { - "type": "string", - "enum": ["alpha", "beta"], - "title": "MessageEventType", - } - ), - v1=snapshot( - { - "type": "string", - "enum": ["alpha", "beta"], - "title": "MessageEventType", - "description": "An enumeration.", - } - ), - ), + "MessageEventType": { + "type": "string", + "enum": ["alpha", "beta"], + "title": "MessageEventType", + }, "MessageOutput": { "properties": { "body": {"type": "string", "title": "Body", "default": ""}, diff --git a/tests/test_openapi_separate_input_output_schemas.py b/tests/test_openapi_separate_input_output_schemas.py index bfe5e9a712..1891f0bde0 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -3,37 +3,30 @@ from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot -from pydantic import BaseModel - -from .utils import PYDANTIC_V2, needs_pydanticv2 +from pydantic import BaseModel, computed_field class SubItem(BaseModel): subname: str sub_description: Optional[str] = None tags: list[str] = [] - if PYDANTIC_V2: - model_config = {"json_schema_serialization_defaults_required": True} + model_config = {"json_schema_serialization_defaults_required": True} class Item(BaseModel): name: str description: Optional[str] = None sub: Optional[SubItem] = None - if PYDANTIC_V2: - model_config = {"json_schema_serialization_defaults_required": True} + model_config = {"json_schema_serialization_defaults_required": True} -if PYDANTIC_V2: - from pydantic import computed_field +class WithComputedField(BaseModel): + name: str - class WithComputedField(BaseModel): - name: str - - @computed_field - @property - def computed_field(self) -> str: - return f"computed {self.name}" + @computed_field + @property + def computed_field(self) -> str: + return f"computed {self.name}" def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: @@ -58,13 +51,11 @@ def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: Item(name="Plumbus"), ] - if PYDANTIC_V2: - - @app.post("/with-computed-field/") - def create_with_computed_field( - with_computed_field: WithComputedField, - ) -> WithComputedField: - return with_computed_field + @app.post("/with-computed-field/") + def create_with_computed_field( + with_computed_field: WithComputedField, + ) -> WithComputedField: + return with_computed_field client = TestClient(app) return client @@ -151,7 +142,6 @@ def test_read_items(): ) -@needs_pydanticv2 def test_with_computed_field(): client = get_app_client() client_no = get_app_client(separate_input_output_schemas=False) @@ -168,7 +158,6 @@ def test_with_computed_field(): ) -@needs_pydanticv2 def test_openapi_schema(): client = get_app_client() response = client.get("/openapi.json") @@ -449,7 +438,6 @@ def test_openapi_schema(): ) -@needs_pydanticv2 def test_openapi_schema_no_separate(): client = get_app_client(separate_input_output_schemas=False) response = client.get("/openapi.json") diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py index ebf86b99f0..83536cafa2 100644 --- a/tests/test_pydantic_v1_v2_01.py +++ b/tests/test_pydantic_v1_v2_01.py @@ -1,7 +1,7 @@ import sys from typing import Any, Union -from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 +from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() @@ -225,21 +225,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/SubItem" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/SubItem"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -275,21 +266,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/SubItem" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/SubItem"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -325,21 +307,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -373,21 +346,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, diff --git a/tests/test_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py index 8879b010de..4ddcbf240d 100644 --- a/tests/test_pydantic_v1_v2_list.py +++ b/tests/test_pydantic_v1_v2_list.py @@ -1,7 +1,7 @@ import sys from typing import Any, Union -from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 +from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() @@ -375,21 +375,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -429,21 +420,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, diff --git a/tests/test_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py index e66583cd54..61e5bb5827 100644 --- a/tests/test_pydantic_v1_v2_mixed.py +++ b/tests/test_pydantic_v1_v2_mixed.py @@ -1,7 +1,7 @@ import sys from typing import Any, Union -from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 +from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() @@ -668,38 +668,20 @@ def test_v2_to_v1_validation_error(): assert response.status_code == 422, response.text assert response.json() == snapshot( { - "detail": pydantic_snapshot( - v2=snapshot( - [ - { - "type": "missing", - "loc": ["body", "new_size"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - { - "type": "missing", - "loc": ["body", "new_sub"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - ] - ), - v1=snapshot( - [ - { - "loc": ["body", "new_size"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "new_sub"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - ), - ) + "detail": [ + { + "type": "missing", + "loc": ["body", "new_size"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + { + "type": "missing", + "loc": ["body", "new_sub"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + ] } ) @@ -717,23 +699,12 @@ def test_v2_to_v1_nested_validation_error(): assert response.json() == snapshot( { "detail": [ - pydantic_snapshot( - v2=snapshot( - { - "type": "missing", - "loc": ["body", "new_sub", "new_sub_name"], - "msg": "Field required", - "input": {"wrong_field": "value"}, - } - ), - v1=snapshot( - { - "loc": ["body", "new_sub", "new_sub_name"], - "msg": "field required", - "type": "value_error.missing", - } - ), - ) + { + "type": "missing", + "loc": ["body", "new_sub", "new_sub_name"], + "msg": "Field required", + "input": {"wrong_field": "value"}, + } ] } ) @@ -777,38 +748,20 @@ def test_v2_list_validation_error(): assert response.status_code == 422, response.text assert response.json() == snapshot( { - "detail": pydantic_snapshot( - v2=snapshot( - [ - { - "type": "missing", - "loc": ["body", 1, "new_size"], - "msg": "Field required", - "input": {"new_title": "Invalid"}, - }, - { - "type": "missing", - "loc": ["body", 1, "new_sub"], - "msg": "Field required", - "input": {"new_title": "Invalid"}, - }, - ] - ), - v1=snapshot( - [ - { - "loc": ["body", 1, "new_size"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", 1, "new_sub"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - ), - ) + "detail": [ + { + "type": "missing", + "loc": ["body", 1, "new_size"], + "msg": "Field required", + "input": {"new_title": "Invalid"}, + }, + { + "type": "missing", + "loc": ["body", 1, "new_sub"], + "msg": "Field required", + "input": {"new_title": "Invalid"}, + }, + ] } ) @@ -844,31 +797,18 @@ def test_invalid_list_structure_v2(): assert response.status_code == 422, response.text assert response.json() == snapshot( { - "detail": pydantic_snapshot( - v2=snapshot( - [ - { - "type": "list_type", - "loc": ["body"], - "msg": "Input should be a valid list", - "input": { - "new_title": "Not a list", - "new_size": 100, - "new_sub": {"new_sub_name": "Sub"}, - }, - } - ] - ), - v1=snapshot( - [ - { - "loc": ["body"], - "msg": "value is not a valid list", - "type": "type_error.list", - } - ] - ), - ) + "detail": [ + { + "type": "list_type", + "loc": ["body"], + "msg": "Input should be a valid list", + "input": { + "new_title": "Not a list", + "new_size": 100, + "new_sub": {"new_sub_name": "Sub"}, + }, + } + ] } ) @@ -888,21 +828,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -938,21 +869,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -1056,21 +978,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -1440,17 +1353,10 @@ def test_openapi_schema(): "properties": { "new_title": {"type": "string", "title": "New Title"}, "new_size": {"type": "integer", "title": "New Size"}, - "new_description": pydantic_snapshot( - v2=snapshot( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - } - ), - v1=snapshot( - {"type": "string", "title": "New Description"} - ), - ), + "new_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + }, "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, "new_multi": { "items": {"$ref": "#/components/schemas/NewSubItem"}, diff --git a/tests/test_pydantic_v1_v2_multifile/test_multifile.py b/tests/test_pydantic_v1_v2_multifile/test_multifile.py index e66d102fb3..32d9019616 100644 --- a/tests/test_pydantic_v1_v2_multifile/test_multifile.py +++ b/tests/test_pydantic_v1_v2_multifile/test_multifile.py @@ -1,6 +1,6 @@ import sys -from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 +from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() @@ -292,23 +292,14 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ { "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" } - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -344,18 +335,9 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - } - ), - v1=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - } - ), - ), + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + }, } }, "required": True, @@ -391,23 +373,14 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ { "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item" } - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -535,18 +508,9 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - } - ), - v1=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - } - ), - ), + "schema": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + }, } }, "required": True, @@ -587,18 +551,9 @@ def test_openapi_schema(): "content": { "application/json": { "schema": { - "items": pydantic_snapshot( - v2=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - } - ), - v1=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - } - ), - ), + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + }, "type": "array", "title": "Data", } @@ -642,18 +597,9 @@ def test_openapi_schema(): "content": { "application/json": { "schema": { - "items": pydantic_snapshot( - v2=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - } - ), - v1=snapshot( - { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - } - ), - ), + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + }, "type": "array", "title": "Data", } @@ -767,460 +713,239 @@ def test_openapi_schema(): }, }, "components": { - "schemas": pydantic_snapshot( - v1=snapshot( - { - "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { - "properties": { - "data1": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" - }, - "type": "array", - "title": "Data1", - }, - "data2": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" - }, - "type": "array", - "title": "Data2", - }, + "schemas": { + "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { + "properties": { + "data1": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" }, - "type": "object", - "required": ["data1", "data2"], - "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", + "type": "array", + "title": "Data1", }, - "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { - "properties": { - "item1": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item" - }, - "item2": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" - }, + "data2": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" }, - "type": "object", - "required": ["item1", "item2"], - "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", + "type": "array", + "title": "Data2", }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } + }, + "type": "object", + "required": ["data1", "data2"], + "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", + }, + "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { + "properties": { + "item1": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" + }, + "item2": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" + }, + }, + "type": "object", + "required": ["item1", "item2"], + "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - {"type": "string"}, - {"type": "integer"}, - ] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + {"type": "string"}, + {"type": "integer"}, + ] }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", + "type": "array", + "title": "Location", }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { - "properties": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": { - "type": "string", - "title": "Description", - }, - "sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" - }, - "multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" - }, - "type": "array", - "title": "Multi", - "default": [], - }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { + "properties": { + "title": {"type": "string", "title": "Title"}, + "size": {"type": "integer", "title": "Size"}, + "description": { + "type": "string", + "title": "Description", + }, + "sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" + }, + "multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", + "type": "array", + "title": "Multi", + "default": [], }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { - "properties": { - "name1": {"type": "string", "title": "Name1"} + }, + "type": "object", + "required": ["title", "size", "sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { + "properties": {"name1": {"type": "string", "title": "Name1"}}, + "type": "object", + "required": ["name1"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { + "properties": {"name": {"type": "string", "title": "Name"}}, + "type": "object", + "required": ["name"], + "title": "SubItem", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { + "properties": { + "new_title": { + "type": "string", + "title": "New Title", + }, + "new_size": { + "type": "integer", + "title": "New Size", + }, + "new_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + }, + "new_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" + }, + "new_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" }, - "type": "object", - "required": ["name1"], - "title": "ItemInList", + "type": "array", + "title": "New Multi", + "default": [], }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { - "properties": { - "name": {"type": "string", "title": "Name"} + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": { + "properties": { + "new_title": { + "type": "string", + "title": "New Title", + }, + "new_size": { + "type": "integer", + "title": "New Size", + }, + "new_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + }, + "new_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" + }, + "new_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" }, - "type": "object", - "required": ["name"], - "title": "SubItem", + "type": "array", + "title": "New Multi", + "default": [], }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { - "properties": { - "new_title": { - "type": "string", - "title": "New Title", - }, - "new_size": { - "type": "integer", - "title": "New Size", - }, - "new_description": { - "type": "string", - "title": "New Description", - }, - "new_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "new_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "type": "array", - "title": "New Multi", - "default": [], - }, + }, + "type": "object", + "required": ["new_title", "new_size", "new_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { + "properties": {"name2": {"type": "string", "title": "Name2"}}, + "type": "object", + "required": ["name2"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { + "properties": { + "new_sub_name": { + "type": "string", + "title": "New Sub Name", + } + }, + "type": "object", + "required": ["new_sub_name"], + "title": "SubItem", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { + "properties": { + "dup_title": { + "type": "string", + "title": "Dup Title", + }, + "dup_size": { + "type": "integer", + "title": "Dup Size", + }, + "dup_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Dup Description", + }, + "dup_sub": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" + }, + "dup_multi": { + "items": { + "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "Item", + "type": "array", + "title": "Dup Multi", + "default": [], }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { - "properties": { - "name2": {"type": "string", "title": "Name2"} - }, - "type": "object", - "required": ["name2"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { - "properties": { - "new_sub_name": { - "type": "string", - "title": "New Sub Name", - } - }, - "type": "object", - "required": ["new_sub_name"], - "title": "SubItem", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { - "properties": { - "dup_title": { - "type": "string", - "title": "Dup Title", - }, - "dup_size": { - "type": "integer", - "title": "Dup Size", - }, - "dup_description": { - "type": "string", - "title": "Dup Description", - }, - "dup_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" - }, - "dup_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" - }, - "type": "array", - "title": "Dup Multi", - "default": [], - }, - }, - "type": "object", - "required": ["dup_title", "dup_size", "dup_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { - "properties": { - "dup_name2": { - "type": "string", - "title": "Dup Name2", - } - }, - "type": "object", - "required": ["dup_name2"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { - "properties": { - "dup_sub_name": { - "type": "string", - "title": "Dup Sub Name", - } - }, - "type": "object", - "required": ["dup_sub_name"], - "title": "SubItem", - }, - } - ), - v2=snapshot( - { - "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": { - "properties": { - "data1": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList" - }, - "type": "array", - "title": "Data1", - }, - "data2": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList" - }, - "type": "array", - "title": "Data2", - }, - }, - "type": "object", - "required": ["data1", "data2"], - "title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post", - }, - "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": { - "properties": { - "item1": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input" - }, - "item2": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item" - }, - }, - "type": "object", - "required": ["item1", "item2"], - "title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post", - }, - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - {"type": "string"}, - {"type": "integer"}, - ] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__Item": { - "properties": { - "title": {"type": "string", "title": "Title"}, - "size": {"type": "integer", "title": "Size"}, - "description": { - "type": "string", - "title": "Description", - }, - "sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" - }, - "multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem" - }, - "type": "array", - "title": "Multi", - "default": [], - }, - }, - "type": "object", - "required": ["title", "size", "sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": { - "properties": { - "name1": {"type": "string", "title": "Name1"} - }, - "type": "object", - "required": ["name1"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": { - "properties": { - "name": {"type": "string", "title": "Name"} - }, - "type": "object", - "required": ["name"], - "title": "SubItem", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__Item": { - "properties": { - "new_title": { - "type": "string", - "title": "New Title", - }, - "new_size": { - "type": "integer", - "title": "New Size", - }, - "new_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - }, - "new_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "new_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "type": "array", - "title": "New Multi", - "default": [], - }, - }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": { - "properties": { - "new_title": { - "type": "string", - "title": "New Title", - }, - "new_size": { - "type": "integer", - "title": "New Size", - }, - "new_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - }, - "new_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "new_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem" - }, - "type": "array", - "title": "New Multi", - "default": [], - }, - }, - "type": "object", - "required": ["new_title", "new_size", "new_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": { - "properties": { - "name2": {"type": "string", "title": "Name2"} - }, - "type": "object", - "required": ["name2"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": { - "properties": { - "new_sub_name": { - "type": "string", - "title": "New Sub Name", - } - }, - "type": "object", - "required": ["new_sub_name"], - "title": "SubItem", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": { - "properties": { - "dup_title": { - "type": "string", - "title": "Dup Title", - }, - "dup_size": { - "type": "integer", - "title": "Dup Size", - }, - "dup_description": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "Dup Description", - }, - "dup_sub": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" - }, - "dup_multi": { - "items": { - "$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem" - }, - "type": "array", - "title": "Dup Multi", - "default": [], - }, - }, - "type": "object", - "required": ["dup_title", "dup_size", "dup_sub"], - "title": "Item", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { - "properties": { - "dup_name2": { - "type": "string", - "title": "Dup Name2", - } - }, - "type": "object", - "required": ["dup_name2"], - "title": "ItemInList", - }, - "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { - "properties": { - "dup_sub_name": { - "type": "string", - "title": "Dup Sub Name", - } - }, - "type": "object", - "required": ["dup_sub_name"], - "title": "SubItem", - }, - } - ), - ), + }, + "type": "object", + "required": ["dup_title", "dup_size", "dup_sub"], + "title": "Item", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": { + "properties": { + "dup_name2": { + "type": "string", + "title": "Dup Name2", + } + }, + "type": "object", + "required": ["dup_name2"], + "title": "ItemInList", + }, + "tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": { + "properties": { + "dup_sub_name": { + "type": "string", + "title": "Dup Sub Name", + } + }, + "type": "object", + "required": ["dup_sub_name"], + "title": "SubItem", + }, + }, }, } ) diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py index 3e86469908..2cb6c3d6b4 100644 --- a/tests/test_pydantic_v1_v2_noneable.py +++ b/tests/test_pydantic_v1_v2_noneable.py @@ -1,7 +1,7 @@ import sys from typing import Any, Union -from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 +from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() @@ -312,38 +312,20 @@ def test_v2_to_v1_validation_error(): assert response.status_code == 422, response.text assert response.json() == snapshot( { - "detail": pydantic_snapshot( - v2=snapshot( - [ - { - "type": "missing", - "loc": ["body", "new_size"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - { - "type": "missing", - "loc": ["body", "new_sub"], - "msg": "Field required", - "input": {"new_title": "Missing fields"}, - }, - ] - ), - v1=snapshot( - [ - { - "loc": ["body", "new_size"], - "msg": "field required", - "type": "value_error.missing", - }, - { - "loc": ["body", "new_sub"], - "msg": "field required", - "type": "value_error.missing", - }, - ] - ), - ) + "detail": [ + { + "type": "missing", + "loc": ["body", "new_size"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + { + "type": "missing", + "loc": ["body", "new_sub"], + "msg": "Field required", + "input": {"new_title": "Missing fields"}, + }, + ] } ) @@ -361,23 +343,12 @@ def test_v2_to_v1_nested_validation_error(): assert response.json() == snapshot( { "detail": [ - pydantic_snapshot( - v2=snapshot( - { - "type": "missing", - "loc": ["body", "new_sub", "new_sub_name"], - "msg": "Field required", - "input": {"wrong_field": "value"}, - } - ), - v1=snapshot( - { - "loc": ["body", "new_sub", "new_sub_name"], - "msg": "field required", - "type": "value_error.missing", - } - ), - ) + { + "type": "missing", + "loc": ["body", "new_sub", "new_sub_name"], + "msg": "Field required", + "input": {"wrong_field": "value"}, + } ] } ) @@ -396,23 +367,12 @@ def test_v2_to_v1_type_validation_error(): assert response.json() == snapshot( { "detail": [ - pydantic_snapshot( - v2=snapshot( - { - "type": "int_parsing", - "loc": ["body", "new_size"], - "msg": "Input should be a valid integer, unable to parse string as an integer", - "input": "not_a_number", - } - ), - v1=snapshot( - { - "loc": ["body", "new_size"], - "msg": "value is not a valid integer", - "type": "type_error.integer", - } - ), - ) + { + "type": "int_parsing", + "loc": ["body", "new_size"], + "msg": "Input should be a valid integer, unable to parse string as an integer", + "input": "not_a_number", + } ] } ) @@ -483,21 +443,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -507,22 +458,15 @@ def test_openapi_schema(): "description": "Successful Response", "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( + "schema": { + "anyOf": [ { - "anyOf": [ - { - "$ref": "#/components/schemas/NewItem" - }, - {"type": "null"}, - ], - "title": "Response Handle V1 Item To V2 V1 To V2 Post", - } - ), - v1=snapshot( - {"$ref": "#/components/schemas/NewItem"} - ), - ) + "$ref": "#/components/schemas/NewItem" + }, + {"type": "null"}, + ], + "title": "Response Handle V1 Item To V2 V1 To V2 Post", + } } }, }, @@ -546,21 +490,12 @@ def test_openapi_schema(): "requestBody": { "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( - { - "allOf": [ - { - "$ref": "#/components/schemas/Item" - } - ], - "title": "Data", - } - ), - v1=snapshot( + "schema": { + "allOf": [ {"$ref": "#/components/schemas/Item"} - ), - ) + ], + "title": "Data", + } } }, "required": True, @@ -570,22 +505,15 @@ def test_openapi_schema(): "description": "Successful Response", "content": { "application/json": { - "schema": pydantic_snapshot( - v2=snapshot( + "schema": { + "anyOf": [ { - "anyOf": [ - { - "$ref": "#/components/schemas/NewItem" - }, - {"type": "null"}, - ], - "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post", - } - ), - v1=snapshot( - {"$ref": "#/components/schemas/NewItem"} - ), - ) + "$ref": "#/components/schemas/NewItem" + }, + {"type": "null"}, + ], + "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post", + } } }, }, @@ -707,17 +635,10 @@ def test_openapi_schema(): "properties": { "new_title": {"type": "string", "title": "New Title"}, "new_size": {"type": "integer", "title": "New Size"}, - "new_description": pydantic_snapshot( - v2=snapshot( - { - "anyOf": [{"type": "string"}, {"type": "null"}], - "title": "New Description", - } - ), - v1=snapshot( - {"type": "string", "title": "New Description"} - ), - ), + "new_description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "New Description", + }, "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, "new_multi": { "items": {"$ref": "#/components/schemas/NewSubItem"}, diff --git a/tests/test_query_cookie_header_model_extra_params.py b/tests/test_query_cookie_header_model_extra_params.py index f4ebefb3f3..d361e1e533 100644 --- a/tests/test_query_cookie_header_model_extra_params.py +++ b/tests/test_query_cookie_header_model_extra_params.py @@ -1,5 +1,4 @@ from fastapi import Cookie, FastAPI, Header, Query -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel @@ -9,12 +8,7 @@ app = FastAPI() class Model(BaseModel): param: str - if PYDANTIC_V2: - model_config = {"extra": "allow"} - else: - - class Config: - extra = "allow" + model_config = {"extra": "allow"} @app.get("/query") diff --git a/tests/test_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py index b359874439..5858f8e801 100644 --- a/tests/test_read_with_orm_mode.py +++ b/tests/test_read_with_orm_mode.py @@ -4,10 +4,9 @@ from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict -from .utils import needs_pydanticv1, needs_pydanticv2 +from .utils import needs_pydanticv1 -@needs_pydanticv2 def test_read_with_orm_mode() -> None: class PersonBase(BaseModel): name: str @@ -48,7 +47,9 @@ def test_read_with_orm_mode() -> None: @needs_pydanticv1 def test_read_with_orm_mode_pv1() -> None: - class PersonBase(BaseModel): + from pydantic import v1 + + class PersonBase(v1.BaseModel): name: str lastname: str diff --git a/tests/test_request_param_model_by_alias.py b/tests/test_request_param_model_by_alias.py index a6f759f23b..c29130d7a6 100644 --- a/tests/test_request_param_model_by_alias.py +++ b/tests/test_request_param_model_by_alias.py @@ -1,6 +1,5 @@ from dirty_equals import IsPartialDict from fastapi import Cookie, FastAPI, Header, Query -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, Field @@ -53,8 +52,7 @@ def test_query_model_with_alias_by_name(): response = client.get("/query", params={"param": "value"}) assert response.status_code == 422, response.text details = response.json() - if PYDANTIC_V2: - assert details["detail"][0]["input"] == {"param": "value"} + assert details["detail"][0]["input"] == {"param": "value"} def test_header_model_with_alias_by_name(): @@ -62,8 +60,7 @@ def test_header_model_with_alias_by_name(): response = client.get("/header", headers={"param": "value"}) assert response.status_code == 422, response.text details = response.json() - if PYDANTIC_V2: - assert details["detail"][0]["input"] == IsPartialDict({"param": "value"}) + assert details["detail"][0]["input"] == IsPartialDict({"param": "value"}) def test_cookie_model_with_alias_by_name(): @@ -72,5 +69,4 @@ def test_cookie_model_with_alias_by_name(): response = client.get("/cookie") assert response.status_code == 422, response.text details = response.json() - if PYDANTIC_V2: - assert details["detail"][0]["input"] == {"param": "value"} + assert details["detail"][0]["input"] == {"param": "value"} diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index b2503a1c44..0048da0f8b 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -6,8 +6,6 @@ from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -246,7 +244,6 @@ async def read_model_required_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -269,7 +266,6 @@ def test_required_list_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize("json", [None, {}]) @pytest.mark.parametrize( "path", @@ -294,7 +290,6 @@ def test_required_list_validation_alias_missing(path: str, json: Union[dict, Non } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -319,7 +314,6 @@ def test_required_list_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -364,7 +358,6 @@ def read_model_required_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -390,7 +383,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize("json", [None, {}]) @pytest.mark.parametrize( "path", @@ -415,7 +407,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str, json): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -442,7 +433,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -467,7 +457,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_body/test_optional_list.py b/tests/test_request_params/test_body/test_optional_list.py index ede635f840..960e8890f3 100644 --- a/tests/test_request_params/test_body/test_optional_list.py +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -6,8 +6,6 @@ from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -283,7 +281,6 @@ def read_model_optional_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -369,7 +366,6 @@ def test_optional_list_validation_alias_missing_empty_dict(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -384,7 +380,6 @@ def test_optional_list_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -432,7 +427,6 @@ def read_model_optional_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -524,7 +518,6 @@ def test_optional_list_alias_and_validation_alias_missing_empty_dict(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -539,7 +532,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -554,7 +546,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_body/test_optional_str.py b/tests/test_request_params/test_body/test_optional_str.py index 9e0c7217cc..59732688a2 100644 --- a/tests/test_request_params/test_body/test_optional_str.py +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -6,8 +6,6 @@ from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -268,7 +266,6 @@ def read_model_optional_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -300,7 +297,6 @@ def test_optional_validation_alias_schema(path: str): ) -@needs_pydanticv2 def test_optional_validation_alias_missing(): client = TestClient(app) response = client.post("/optional-validation-alias") @@ -308,7 +304,6 @@ def test_optional_validation_alias_missing(): assert response.json() == {"p": None} -@needs_pydanticv2 def test_model_optional_validation_alias_missing(): client = TestClient(app) response = client.post("/model-optional-validation-alias") @@ -338,7 +333,6 @@ def test_model_optional_validation_alias_missing(): ) -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -350,7 +344,6 @@ def test_model_optional_validation_alias_missing_empty_dict(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -365,7 +358,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -410,7 +402,6 @@ def read_model_optional_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -445,7 +436,6 @@ def test_optional_alias_and_validation_alias_schema(path: str): ) -@needs_pydanticv2 def test_optional_alias_and_validation_alias_missing(): client = TestClient(app) response = client.post("/optional-alias-and-validation-alias") @@ -453,7 +443,6 @@ def test_optional_alias_and_validation_alias_missing(): assert response.json() == {"p": None} -@needs_pydanticv2 def test_model_optional_alias_and_validation_alias_missing(): client = TestClient(app) response = client.post("/model-optional-alias-and-validation-alias") @@ -483,7 +472,6 @@ def test_model_optional_alias_and_validation_alias_missing(): ) -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -498,7 +486,6 @@ def test_model_optional_alias_and_validation_alias_missing_empty_dict(path: str) assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -513,7 +500,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -528,7 +514,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_body/test_required_str.py b/tests/test_request_params/test_body/test_required_str.py index aa47a4ede3..5571ba5d5a 100644 --- a/tests/test_request_params/test_body/test_required_str.py +++ b/tests/test_request_params/test_body/test_required_str.py @@ -6,8 +6,6 @@ from fastapi import Body, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -236,7 +234,6 @@ def read_model_required_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -255,7 +252,6 @@ def test_required_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize("json", [None, {}]) @pytest.mark.parametrize( "path", @@ -282,7 +278,6 @@ def test_required_validation_alias_missing( } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -307,7 +302,6 @@ def test_required_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -353,7 +347,6 @@ def read_model_required_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -375,7 +368,6 @@ def test_required_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize("json", [None, {}]) @pytest.mark.parametrize( "path", @@ -402,7 +394,6 @@ def test_required_alias_and_validation_alias_missing( } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -430,7 +421,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -455,7 +445,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_cookie/test_optional_str.py b/tests/test_request_params/test_cookie/test_optional_str.py index f2d02dae54..b2f7f9cef5 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -6,8 +6,6 @@ from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -189,7 +187,6 @@ def read_model_optional_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -208,7 +205,6 @@ def test_optional_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -220,7 +216,6 @@ def test_optional_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -236,7 +231,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -276,7 +270,6 @@ def read_model_optional_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -298,7 +291,6 @@ def test_optional_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -313,7 +305,6 @@ def test_optional_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -329,7 +320,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -345,7 +335,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_cookie/test_required_str.py b/tests/test_request_params/test_cookie/test_required_str.py index 3255857d44..58bb7af5b9 100644 --- a/tests/test_request_params/test_cookie/test_required_str.py +++ b/tests/test_request_params/test_cookie/test_required_str.py @@ -6,8 +6,6 @@ from fastapi import Cookie, FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -231,7 +229,6 @@ def read_model_required_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -247,7 +244,6 @@ def test_required_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -274,7 +270,6 @@ def test_required_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -300,7 +295,6 @@ def test_required_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -339,7 +333,6 @@ def read_model_required_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -358,7 +351,6 @@ def test_required_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -385,7 +377,6 @@ def test_required_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -417,7 +408,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -446,7 +436,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_file/test_list.py b/tests/test_request_params/test_file/test_list.py index 52b032e441..90f36e5f7c 100644 --- a/tests/test_request_params/test_file/test_list.py +++ b/tests/test_request_params/test_file/test_list.py @@ -5,8 +5,6 @@ from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -280,7 +278,6 @@ def read_list_uploadfile_validation_alias( return {"file_size": [file.size for file in p]} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -322,7 +319,6 @@ def test_list_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -349,7 +345,6 @@ def test_list_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -374,7 +369,6 @@ def test_list_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -417,7 +411,6 @@ def read_list_uploadfile_alias_and_validation_alias( return {"file_size": [file.size for file in p]} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -459,7 +452,6 @@ def test_list_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -486,7 +478,6 @@ def test_list_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -514,7 +505,6 @@ def test_list_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -539,7 +529,6 @@ def test_list_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index b7177e071d..4e9564873c 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -5,8 +5,6 @@ from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -204,7 +202,6 @@ def read_optional_uploadfile_validation_alias( return {"file_size": p.size if p else None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -239,7 +236,6 @@ def test_optional_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -254,7 +250,6 @@ def test_optional_validation_alias_missing(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -269,7 +264,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -312,7 +306,6 @@ def read_optional_uploadfile_alias_and_validation_alias( return {"file_size": p.size if p else None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -347,7 +340,6 @@ def test_optional_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -362,7 +354,6 @@ def test_optional_alias_and_validation_alias_missing(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -377,7 +368,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -392,7 +382,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_file/test_optional_list.py b/tests/test_request_params/test_file/test_optional_list.py index af9fe552c9..e18f36e152 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -5,8 +5,6 @@ from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -217,7 +215,6 @@ def read_optional_list_uploadfile_validation_alias( return {"file_size": [file.size for file in p] if p else None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -259,7 +256,6 @@ def test_optional_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -274,7 +270,6 @@ def test_optional_validation_alias_missing(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -289,7 +284,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -329,7 +323,6 @@ def read_optional_list_uploadfile_alias_and_validation_alias( return {"file_size": [file.size for file in p] if p else None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -371,7 +364,6 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -386,7 +378,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -401,7 +392,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -416,7 +406,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"file_size": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_file/test_required.py b/tests/test_request_params/test_file/test_required.py index 2979a1040a..9783f4bceb 100644 --- a/tests/test_request_params/test_file/test_required.py +++ b/tests/test_request_params/test_file/test_required.py @@ -5,8 +5,6 @@ from dirty_equals import IsDict from fastapi import FastAPI, File, UploadFile from fastapi.testclient import TestClient -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -242,7 +240,6 @@ def read_required_uploadfile_validation_alias( return {"file_size": p.size} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -268,7 +265,6 @@ def test_required_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -295,7 +291,6 @@ def test_required_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -320,7 +315,6 @@ def test_required_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -359,7 +353,6 @@ def read_required_uploadfile_alias_and_validation_alias( return {"file_size": p.size} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -385,7 +378,6 @@ def test_required_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -412,7 +404,6 @@ def test_required_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -440,7 +431,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -465,7 +455,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_form/test_list.py b/tests/test_request_params/test_form/test_list.py index 9f45aa755d..600dba4ae9 100644 --- a/tests/test_request_params/test_form/test_list.py +++ b/tests/test_request_params/test_form/test_list.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -247,7 +245,6 @@ async def read_model_required_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -270,7 +267,6 @@ def test_required_list_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -297,7 +293,6 @@ def test_required_list_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -322,7 +317,6 @@ def test_required_list_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -363,7 +357,6 @@ def read_model_required_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -389,7 +382,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -416,7 +408,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -446,7 +437,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -470,7 +460,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_form/test_optional_list.py b/tests/test_request_params/test_form/test_optional_list.py index 0af6d34777..4552623f51 100644 --- a/tests/test_request_params/test_form/test_optional_list.py +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -211,7 +209,6 @@ def read_model_optional_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -250,7 +247,6 @@ def test_optional_list_validation_alias_schema(path: str): ) -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -262,7 +258,6 @@ def test_optional_list_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -277,7 +272,6 @@ def test_optional_list_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -321,7 +315,6 @@ def read_model_optional_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -363,7 +356,6 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): ) -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -378,7 +370,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -393,7 +384,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -408,7 +398,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_form/test_optional_str.py b/tests/test_request_params/test_form/test_optional_str.py index 92329216ec..1b08299046 100644 --- a/tests/test_request_params/test_form/test_optional_str.py +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -194,7 +192,6 @@ def read_model_optional_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -226,7 +223,6 @@ def test_optional_validation_alias_schema(path: str): ) -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -238,7 +234,6 @@ def test_optional_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -253,7 +248,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -298,7 +292,6 @@ def read_model_optional_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -333,7 +326,6 @@ def test_optional_alias_and_validation_alias_schema(path: str): ) -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -348,7 +340,6 @@ def test_optional_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -363,7 +354,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -378,7 +368,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_form/test_required_str.py b/tests/test_request_params/test_form/test_required_str.py index 1e33038040..1d2431b333 100644 --- a/tests/test_request_params/test_form/test_required_str.py +++ b/tests/test_request_params/test_form/test_required_str.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Form from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - from .utils import get_body_model_name app = FastAPI() @@ -232,7 +230,6 @@ def read_model_required_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -251,7 +248,6 @@ def test_required_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -278,7 +274,6 @@ def test_required_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -303,7 +298,6 @@ def test_required_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -347,7 +341,6 @@ def read_model_required_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -369,7 +362,6 @@ def test_required_alias_and_validation_alias_schema(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -396,7 +388,6 @@ def test_required_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -424,7 +415,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -449,7 +439,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_header/test_list.py b/tests/test_request_params/test_header/test_list.py index 62f9f36ff6..2eba17559e 100644 --- a/tests/test_request_params/test_header/test_list.py +++ b/tests/test_request_params/test_header/test_list.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -234,7 +232,6 @@ async def read_model_required_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -254,7 +251,6 @@ def test_required_list_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -281,7 +277,6 @@ def test_required_list_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -306,7 +301,6 @@ def test_required_list_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -343,7 +337,6 @@ def read_model_required_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -366,7 +359,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -393,7 +385,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -423,7 +414,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -450,7 +440,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_header/test_optional_list.py b/tests/test_request_params/test_header/test_optional_list.py index 88243f05a9..cd6167a183 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -202,7 +200,6 @@ def read_model_optional_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -224,7 +221,6 @@ def test_optional_list_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -236,7 +232,6 @@ def test_optional_list_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -251,7 +246,6 @@ def test_optional_list_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -291,7 +285,6 @@ def read_model_optional_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -316,7 +309,6 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -331,7 +323,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -346,7 +337,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -361,7 +351,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_header/test_optional_str.py b/tests/test_request_params/test_header/test_optional_str.py index e40b1669ee..d4f25cc1e7 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -186,7 +184,6 @@ def read_model_optional_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -205,7 +202,6 @@ def test_optional_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -217,7 +213,6 @@ def test_optional_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -232,7 +227,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -271,7 +265,6 @@ def read_model_optional_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -293,7 +286,6 @@ def test_optional_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -308,7 +300,6 @@ def test_optional_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -323,7 +314,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -338,7 +328,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_header/test_required_str.py b/tests/test_request_params/test_header/test_required_str.py index 23554d3e4a..85bb43d5a8 100644 --- a/tests/test_request_params/test_header/test_required_str.py +++ b/tests/test_request_params/test_header/test_required_str.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Header from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -225,7 +223,6 @@ def read_model_required_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -241,7 +238,6 @@ def test_required_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -268,7 +264,6 @@ def test_required_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -293,7 +288,6 @@ def test_required_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -331,7 +325,6 @@ def read_model_required_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -350,7 +343,6 @@ def test_required_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -377,7 +369,6 @@ def test_required_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -408,7 +399,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -436,7 +426,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_path/test_required_str.py b/tests/test_request_params/test_path/test_required_str.py index ecd4eb61cd..b2d63667e2 100644 --- a/tests/test_request_params/test_path/test_required_str.py +++ b/tests/test_request_params/test_path/test_required_str.py @@ -4,8 +4,6 @@ import pytest from fastapi import FastAPI, Path from fastapi.testclient import TestClient -from tests.utils import needs_pydanticv2 - app = FastAPI() @@ -45,14 +43,12 @@ def read_required_alias_and_validation_alias( "p_val_alias", "P Val Alias", id="required-validation-alias", - marks=needs_pydanticv2, ), pytest.param( "/required-alias-and-validation-alias/{p_val_alias}", "p_val_alias", "P Val Alias", id="required-alias-and-validation-alias", - marks=needs_pydanticv2, ), ], ) @@ -75,12 +71,10 @@ def test_schema(path: str, expected_name: str, expected_title: str): pytest.param( "/required-validation-alias", id="required-validation-alias", - marks=needs_pydanticv2, ), pytest.param( "/required-alias-and-validation-alias", id="required-alias-and-validation-alias", - marks=needs_pydanticv2, ), ], ) diff --git a/tests/test_request_params/test_query/test_list.py b/tests/test_request_params/test_query/test_list.py index 6a3000fbf6..dc21a85006 100644 --- a/tests/test_request_params/test_query/test_list.py +++ b/tests/test_request_params/test_query/test_list.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -234,7 +232,6 @@ async def read_model_required_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -254,7 +251,6 @@ def test_required_list_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -281,7 +277,6 @@ def test_required_list_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -306,7 +301,6 @@ def test_required_list_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-list-validation-alias", "/model-required-list-validation-alias"], @@ -341,7 +335,6 @@ def read_model_required_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -364,7 +357,6 @@ def test_required_list_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -391,7 +383,6 @@ def test_required_list_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -426,7 +417,6 @@ def test_required_list_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -453,7 +443,6 @@ def test_required_list_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_query/test_optional_list.py b/tests/test_request_params/test_query/test_optional_list.py index f4b8ec6a82..2a8f63a36a 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -202,7 +200,6 @@ def read_model_optional_list_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -224,7 +221,6 @@ def test_optional_list_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -236,7 +232,6 @@ def test_optional_list_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -251,7 +246,6 @@ def test_optional_list_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], @@ -289,7 +283,6 @@ def read_model_optional_list_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -314,7 +307,6 @@ def test_optional_list_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -329,7 +321,6 @@ def test_optional_list_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -344,7 +335,6 @@ def test_optional_list_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -359,7 +349,6 @@ def test_optional_list_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_query/test_optional_str.py b/tests/test_request_params/test_query/test_optional_str.py index c7d20e37d1..c6a70bc283 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -186,7 +184,6 @@ def read_model_optional_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -205,7 +202,6 @@ def test_optional_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/optional-validation-alias", "/model-optional-validation-alias"], @@ -217,7 +213,6 @@ def test_optional_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -232,7 +227,6 @@ def test_optional_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -271,7 +265,6 @@ def read_model_optional_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -293,7 +286,6 @@ def test_optional_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -308,7 +300,6 @@ def test_optional_alias_and_validation_alias_missing(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -323,7 +314,6 @@ def test_optional_alias_and_validation_alias_by_name(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -338,7 +328,6 @@ def test_optional_alias_and_validation_alias_by_alias(path: str): assert response.json() == {"p": None} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_request_params/test_query/test_required_str.py b/tests/test_request_params/test_query/test_required_str.py index ce30f3b1f8..2ef1b0373d 100644 --- a/tests/test_request_params/test_query/test_required_str.py +++ b/tests/test_request_params/test_query/test_required_str.py @@ -6,8 +6,6 @@ from fastapi import FastAPI, Query from fastapi.testclient import TestClient from pydantic import BaseModel, Field -from tests.utils import needs_pydanticv2 - app = FastAPI() # ===================================================================================== @@ -228,7 +226,6 @@ def read_model_required_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", ["/required-validation-alias", "/model-required-validation-alias"], @@ -244,7 +241,6 @@ def test_required_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -271,7 +267,6 @@ def test_required_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -296,7 +291,6 @@ def test_required_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -334,7 +328,6 @@ def read_model_required_alias_and_validation_alias( return {"p": p.p} -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -353,7 +346,6 @@ def test_required_alias_and_validation_alias_schema(path: str): ] -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -380,7 +372,6 @@ def test_required_alias_and_validation_alias_missing(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -411,7 +402,6 @@ def test_required_alias_and_validation_alias_by_name(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ @@ -439,7 +429,6 @@ def test_required_alias_and_validation_alias_by_alias(path: str): } -@needs_pydanticv2 @pytest.mark.parametrize( "path", [ diff --git a/tests/test_response_by_alias.py b/tests/test_response_by_alias.py index 5b241c76b6..807d2600b9 100644 --- a/tests/test_response_by_alias.py +++ b/tests/test_response_by_alias.py @@ -1,5 +1,4 @@ from fastapi import FastAPI -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict, Field @@ -13,24 +12,14 @@ class Model(BaseModel): class ModelNoAlias(BaseModel): name: str - if PYDANTIC_V2: - model_config = ConfigDict( - json_schema_extra={ - "description": ( - "response_model_by_alias=False is basically a quick hack, to support " - "proper OpenAPI use another model with the correct field names" - ) - } - ) - else: - - class Config: - schema_extra = { - "description": ( - "response_model_by_alias=False is basically a quick hack, to support " - "proper OpenAPI use another model with the correct field names" - ) - } + model_config = ConfigDict( + json_schema_extra={ + "description": ( + "response_model_by_alias=False is basically a quick hack, to support " + "proper OpenAPI use another model with the correct field names" + ) + } + ) @app.get("/dict", response_model=Model, response_model_by_alias=False) diff --git a/tests/test_schema_compat_pydantic_v2.py b/tests/test_schema_compat_pydantic_v2.py index 39626c0eca..737687f256 100644 --- a/tests/test_schema_compat_pydantic_v2.py +++ b/tests/test_schema_compat_pydantic_v2.py @@ -4,7 +4,7 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel -from tests.utils import needs_py310, needs_pydanticv2 +from tests.utils import needs_py310 @pytest.fixture(name="client") @@ -32,14 +32,12 @@ def get_client(): @needs_py310 -@needs_pydanticv2 def test_get(client: TestClient): response = client.get("/users") assert response.json() == {"username": "alice", "role": "admin"} @needs_py310 -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("openapi.json") assert response.json() == snapshot( diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index b313f47e90..176b5588d7 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -3,7 +3,6 @@ from typing import Union import pytest from dirty_equals import IsDict from fastapi import Body, Cookie, FastAPI, Header, Path, Query -from fastapi._compat import PYDANTIC_V2 from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict @@ -14,14 +13,9 @@ def create_app(): class Item(BaseModel): data: str - if PYDANTIC_V2: - model_config = ConfigDict( - json_schema_extra={"example": {"data": "Data in schema_extra"}} - ) - else: - - class Config: - schema_extra = {"example": {"data": "Data in schema_extra"}} + model_config = ConfigDict( + json_schema_extra={"example": {"data": "Data in schema_extra"}} + ) @app.post("/schema_extra/") def schema_extra(item: Item): diff --git a/tests/test_schema_ref_pydantic_v2.py b/tests/test_schema_ref_pydantic_v2.py index 119b76a529..69cb82a356 100644 --- a/tests/test_schema_ref_pydantic_v2.py +++ b/tests/test_schema_ref_pydantic_v2.py @@ -6,8 +6,6 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel, ConfigDict, Field -from tests.utils import needs_pydanticv2 - @pytest.fixture(name="client") def get_client(): @@ -25,13 +23,11 @@ def get_client(): return client -@needs_pydanticv2 def test_get(client: TestClient): response = client.get("/") assert response.json() == {"$ref": "some-ref"} -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("openapi.json") assert response.json() == snapshot( diff --git a/tests/test_tutorial/test_body_updates/test_tutorial001.py b/tests/test_tutorial/test_body_updates/test_tutorial001.py index 8bbc4d6992..0401eb7d0d 100644 --- a/tests/test_tutorial/test_body_updates/test_tutorial001.py +++ b/tests/test_tutorial/test_body_updates/test_tutorial001.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -45,7 +45,6 @@ def test_put(client: TestClient): } -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text @@ -185,137 +184,3 @@ def test_openapi_schema(client: TestClient): } }, } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "get": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Read Item", - "operationId": "read_item_items__item_id__get", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - }, - "put": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"title": "Item Id", "type": "string"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - }, - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number", "default": 10.5}, - "tags": { - "title": "Tags", - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py index a425e893d8..ddc282d85e 100644 --- a/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py +++ b/tests/test_tutorial/test_conditional_openapi/test_tutorial001.py @@ -2,8 +2,6 @@ import importlib from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 - def get_client() -> TestClient: from docs_src.conditional_openapi import tutorial001_py39 @@ -14,7 +12,6 @@ def get_client() -> TestClient: return client -@needs_pydanticv2 def test_disable_openapi(monkeypatch): monkeypatch.setenv("OPENAPI_URL", "") # Load the client after setting the env var @@ -27,7 +24,6 @@ def test_disable_openapi(monkeypatch): assert response.status_code == 404, response.text -@needs_pydanticv2 def test_root(): client = get_client() response = client.get("/") @@ -35,7 +31,6 @@ def test_root(): assert response.json() == {"message": "Hello World"} -@needs_pydanticv2 def test_default_openapi(): client = get_client() response = client.get("/docs") diff --git a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py index 4a826a5374..0fbf141e05 100644 --- a/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_cookie_param_models/test_tutorial002.py @@ -5,25 +5,16 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import ( - needs_py310, - needs_pydanticv1, - needs_pydanticv2, - pydantic_snapshot, -) +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39", marks=needs_pydanticv2), - pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_an_py39", marks=needs_pydanticv2), - pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1), - pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), - pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1), - pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=[needs_py310]), + pytest.param("tutorial002_an_py39"), + pytest.param("tutorial002_an_py310", marks=[needs_py310]), ], ) def get_client(request: pytest.FixtureRequest): @@ -62,31 +53,16 @@ def test_cookie_param_model_defaults(client: TestClient): def test_cookie_param_model_invalid(client: TestClient): response = client.get("/items/") assert response.status_code == 422 - assert response.json() == pydantic_snapshot( - v2=snapshot( + assert response.json() == { + "detail": [ { - "detail": [ - { - "type": "missing", - "loc": ["cookie", "session_id"], - "msg": "Field required", - "input": {}, - } - ] + "type": "missing", + "loc": ["cookie", "session_id"], + "msg": "Field required", + "input": {}, } - ), - v1=snapshot( - { - "detail": [ - { - "type": "value_error.missing", - "loc": ["cookie", "session_id"], - "msg": "field required", - } - ] - } - ), - ) + ] + } def test_cookie_param_model_extra(client: TestClient): @@ -146,24 +122,13 @@ def test_openapi_schema(client: TestClient): "name": "fatebook_tracker", "in": "cookie", "required": False, - "schema": pydantic_snapshot( - v2=snapshot( - { - "anyOf": [ - {"type": "string"}, - {"type": "null"}, - ], - "title": "Fatebook Tracker", - } - ), - v1=snapshot( - # TODO: remove when deprecating Pydantic v1 - { - "type": "string", - "title": "Fatebook Tracker", - } - ), - ), + "schema": { + "anyOf": [ + {"type": "string"}, + {"type": "null"}, + ], + "title": "Fatebook Tracker", + }, }, { "name": "googall_tracker", diff --git a/tests/test_tutorial/test_dataclasses/test_tutorial003.py b/tests/test_tutorial/test_dataclasses/test_tutorial003.py index d8cc45dd69..cddf4a9be8 100644 --- a/tests/test_tutorial/test_dataclasses/test_tutorial003.py +++ b/tests/test_tutorial/test_dataclasses/test_tutorial003.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -67,7 +67,6 @@ def test_get_authors(client: TestClient): ] -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200 @@ -201,137 +200,3 @@ def test_openapi_schema(client: TestClient): } }, } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200 - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/authors/{author_id}/items/": { - "post": { - "summary": "Create Author Items", - "operationId": "create_author_items_authors__author_id__items__post", - "parameters": [ - { - "required": True, - "schema": {"title": "Author Id", "type": "string"}, - "name": "author_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - } - } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Author"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - } - }, - "/authors/": { - "get": { - "summary": "Get Authors", - "operationId": "get_authors_authors__get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": { - "title": "Response Get Authors Authors Get", - "type": "array", - "items": { - "$ref": "#/components/schemas/Author" - }, - } - } - }, - } - }, - } - }, - }, - "components": { - "schemas": { - "Author": { - "title": "Author", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "items": { - "title": "Items", - "type": "array", - "items": {"$ref": "#/components/schemas/Item"}, - }, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - "Item": { - "title": "Item", - "required": ["name"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_header_param_models/test_tutorial002.py b/tests/test_tutorial/test_header_param_models/test_tutorial002.py index a7a271ba41..ed4743ebf9 100644 --- a/tests/test_tutorial/test_header_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_header_param_models/test_tutorial002.py @@ -5,20 +5,16 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39", marks=needs_pydanticv2), - pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_an_py39", marks=needs_pydanticv2), - pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1), - pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), - pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1), - pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=[needs_py310]), + pytest.param("tutorial002_an_py39"), + pytest.param("tutorial002_an_py310", marks=[needs_py310]), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py index 3805536e76..a95540731d 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -35,7 +35,6 @@ def test_query_params_str_validations(client: TestClient): } -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text @@ -135,100 +134,3 @@ def test_openapi_schema(client: TestClient): } }, } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py index 204cca09ed..d5f284e3bd 100644 --- a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py +++ b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial007.py @@ -3,8 +3,6 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 - @pytest.fixture( name="client", @@ -21,7 +19,6 @@ def get_client(request: pytest.FixtureRequest): return client -@needs_pydanticv2 def test_post(client: TestClient): yaml_data = """ name: Deadpoolio @@ -38,7 +35,6 @@ def test_post(client: TestClient): } -@needs_pydanticv2 def test_post_broken_yaml(client: TestClient): yaml_data = """ name: Deadpoolio @@ -52,7 +48,6 @@ def test_post_broken_yaml(client: TestClient): assert response.json() == {"detail": "Invalid YAML"} -@needs_pydanticv2 def test_post_invalid(client: TestClient): yaml_data = """ name: Deadpoolio @@ -77,7 +72,6 @@ def test_post_invalid(client: TestClient): } -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py index 2a7a2b78b2..c5a3aec1d9 100644 --- a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py +++ b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -34,7 +34,6 @@ def test_query_params_str_validations(client: TestClient): } -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text @@ -134,100 +133,3 @@ def test_openapi_schema(client: TestClient): } }, } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/": { - "post": { - "responses": { - "200": { - "description": "The created item", - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Create an item", - "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item", - "operationId": "create_item_items__post", - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "Item": { - "title": "Item", - "required": ["name", "price"], - "type": "object", - "properties": { - "name": {"title": "Name", "type": "string"}, - "description": {"title": "Description", "type": "string"}, - "price": {"title": "Price", "type": "number"}, - "tax": {"title": "Tax", "type": "number"}, - "tags": { - "title": "Tags", - "uniqueItems": True, - "type": "array", - "items": {"type": "string"}, - "default": [], - }, - }, - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py index ce53a36eb8..4090eba012 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py @@ -2,7 +2,6 @@ import sys from typing import Any import pytest -from fastapi._compat import PYDANTIC_V2 from tests.utils import skip_module_if_py_gte_314 @@ -10,13 +9,8 @@ if sys.version_info >= (3, 14): skip_module_if_py_gte_314() -if not PYDANTIC_V2: - pytest.skip("This test is only for Pydantic v2", allow_module_level=True) - import importlib -import pytest - from ...utils import needs_py310 diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py index 57720bf489..266d25944d 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -1,7 +1,6 @@ import sys import pytest -from fastapi._compat import PYDANTIC_V2 from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -10,12 +9,8 @@ if sys.version_info >= (3, 14): skip_module_if_py_gte_314() -if not PYDANTIC_V2: - pytest.skip("This test is only for Pydantic v2", allow_module_level=True) - import importlib -import pytest from fastapi.testclient import TestClient from ...utils import needs_py310 diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py index f020d4a975..693c3ba290 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -1,7 +1,6 @@ import sys import pytest -from fastapi._compat import PYDANTIC_V2 from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -9,9 +8,6 @@ from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() -if not PYDANTIC_V2: - pytest.skip("This test is only for Pydantic v2", allow_module_level=True) - import importlib diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py index daa6f0b050..0fd084c84b 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -1,7 +1,6 @@ import sys import pytest -from fastapi._compat import PYDANTIC_V2 from inline_snapshot import snapshot from tests.utils import skip_module_if_py_gte_314 @@ -9,9 +8,6 @@ from tests.utils import skip_module_if_py_gte_314 if sys.version_info >= (3, 14): skip_module_if_py_gte_314() -if not PYDANTIC_V2: - pytest.skip("This test is only for Pydantic v2", allow_module_level=True) - import importlib diff --git a/tests/test_tutorial/test_query_param_models/test_tutorial002.py b/tests/test_tutorial/test_query_param_models/test_tutorial002.py index e7de73f804..0e9c3351a0 100644 --- a/tests/test_tutorial/test_query_param_models/test_tutorial002.py +++ b/tests/test_tutorial/test_query_param_models/test_tutorial002.py @@ -5,20 +5,16 @@ from dirty_equals import IsDict from fastapi.testclient import TestClient from inline_snapshot import snapshot -from tests.utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from tests.utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial002_py39", marks=needs_pydanticv2), - pytest.param("tutorial002_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_an_py39", marks=needs_pydanticv2), - pytest.param("tutorial002_an_py310", marks=[needs_py310, needs_pydanticv2]), - pytest.param("tutorial002_pv1_py39", marks=needs_pydanticv1), - pytest.param("tutorial002_pv1_py310", marks=[needs_py310, needs_pydanticv1]), - pytest.param("tutorial002_pv1_an_py39", marks=needs_pydanticv1), - pytest.param("tutorial002_pv1_an_py310", marks=[needs_py310, needs_pydanticv1]), + pytest.param("tutorial002_py39"), + pytest.param("tutorial002_py310", marks=[needs_py310]), + pytest.param("tutorial002_an_py39"), + pytest.param("tutorial002_an_py310", marks=[needs_py310]), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py index 50ebf711f5..82bb606a9b 100644 --- a/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py +++ b/tests/test_tutorial/test_query_params_str_validations/test_tutorial015.py @@ -5,14 +5,14 @@ from dirty_equals import IsStr from fastapi.testclient import TestClient from inline_snapshot import snapshot -from ...utils import needs_py310, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( name="client", params=[ - pytest.param("tutorial015_an_py39", marks=needs_pydanticv2), - pytest.param("tutorial015_an_py310", marks=(needs_py310, needs_pydanticv2)), + pytest.param("tutorial015_an_py39"), + pytest.param("tutorial015_an_py310", marks=[needs_py310]), ], ) def get_client(request: pytest.FixtureRequest): diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002.py b/tests/test_tutorial/test_request_form_models/test_tutorial002.py index 9bb90fa064..238f8fa2ef 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002.py @@ -3,8 +3,6 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_pydanticv2 - @pytest.fixture( name="client", @@ -20,14 +18,12 @@ def get_client(request: pytest.FixtureRequest): return client -@needs_pydanticv2 def test_post_body_form(client: TestClient): response = client.post("/login/", data={"username": "Foo", "password": "secret"}) assert response.status_code == 200 assert response.json() == {"username": "Foo", "password": "secret"} -@needs_pydanticv2 def test_post_body_extra_form(client: TestClient): response = client.post( "/login/", data={"username": "Foo", "password": "secret", "extra": "extra"} @@ -45,7 +41,6 @@ def test_post_body_extra_form(client: TestClient): } -@needs_pydanticv2 def test_post_body_form_no_password(client: TestClient): response = client.post("/login/", data={"username": "Foo"}) assert response.status_code == 422 @@ -61,7 +56,6 @@ def test_post_body_form_no_password(client: TestClient): } -@needs_pydanticv2 def test_post_body_form_no_username(client: TestClient): response = client.post("/login/", data={"password": "secret"}) assert response.status_code == 422 @@ -77,7 +71,6 @@ def test_post_body_form_no_username(client: TestClient): } -@needs_pydanticv2 def test_post_body_form_no_data(client: TestClient): response = client.post("/login/") assert response.status_code == 422 @@ -99,7 +92,6 @@ def test_post_body_form_no_data(client: TestClient): } -@needs_pydanticv2 def test_post_body_json(client: TestClient): response = client.post("/login/", json={"username": "Foo", "password": "secret"}) assert response.status_code == 422, response.text @@ -121,7 +113,6 @@ def test_post_body_json(client: TestClient): } -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py index 41c7833bef..9ab30086bb 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py @@ -118,87 +118,3 @@ def test_post_body_json(client: TestClient): }, ] } - - -# TODO: remove when deprecating Pydantic v1 -@needs_pydanticv1 -def test_openapi_schema(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/login/": { - "post": { - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError" - } - } - }, - }, - }, - "summary": "Login", - "operationId": "login_login__post", - "requestBody": { - "content": { - "application/x-www-form-urlencoded": { - "schema": {"$ref": "#/components/schemas/FormData"} - } - }, - "required": True, - }, - } - } - }, - "components": { - "schemas": { - "FormData": { - "properties": { - "username": {"type": "string", "title": "Username"}, - "password": {"type": "string", "title": "Password"}, - }, - "additionalProperties": False, - "type": "object", - "required": ["username", "password"], - "title": "FormData", - }, - "ValidationError": { - "title": "ValidationError", - "required": ["loc", "msg", "type"], - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - }, - }, - "HTTPValidationError": { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": "#/components/schemas/ValidationError"}, - } - }, - }, - } - }, - } diff --git a/tests/test_tutorial/test_response_directly/test_tutorial001.py b/tests/test_tutorial/test_response_directly/test_tutorial001.py index 127f0e4c1d..2d0c387195 100644 --- a/tests/test_tutorial/test_response_directly/test_tutorial001.py +++ b/tests/test_tutorial/test_response_directly/test_tutorial001.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -37,7 +37,6 @@ def test_path_operation(client: TestClient): } -@needs_pydanticv2 def test_openapi_schema_pv2(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text @@ -151,138 +150,3 @@ def test_openapi_schema_pv2(client: TestClient): }, }, } - - -@needs_pydanticv1 -def test_openapi_schema_pv1(client: TestClient): - response = client.get("/openapi.json") - assert response.status_code == 200, response.text - assert response.json() == { - "info": { - "title": "FastAPI", - "version": "0.1.0", - }, - "openapi": "3.1.0", - "paths": { - "/items/{id}": { - "put": { - "operationId": "update_item_items__id__put", - "parameters": [ - { - "in": "path", - "name": "id", - "required": True, - "schema": { - "title": "Id", - "type": "string", - }, - }, - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Item", - }, - }, - }, - "required": True, - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": {}, - }, - }, - "description": "Successful Response", - }, - "422": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/HTTPValidationError", - }, - }, - }, - "description": "Validation Error", - }, - }, - "summary": "Update Item", - }, - }, - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": { - "$ref": "#/components/schemas/ValidationError", - }, - "title": "Detail", - "type": "array", - }, - }, - "title": "HTTPValidationError", - "type": "object", - }, - "Item": { - "properties": { - "description": { - "title": "Description", - "type": "string", - }, - "timestamp": { - "format": "date-time", - "title": "Timestamp", - "type": "string", - }, - "title": { - "title": "Title", - "type": "string", - }, - }, - "required": [ - "title", - "timestamp", - ], - "title": "Item", - "type": "object", - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - { - "type": "string", - }, - { - "type": "integer", - }, - ], - }, - "title": "Location", - "type": "array", - }, - "msg": { - "title": "Message", - "type": "string", - }, - "type": { - "title": "Error Type", - "type": "string", - }, - }, - "required": [ - "loc", - "msg", - "type", - ], - "title": "ValidationError", - "type": "object", - }, - }, - }, - } diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py index 4d1808cf6a..82f69fd463 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -20,7 +20,6 @@ def get_client(request: pytest.FixtureRequest): return client -@needs_pydanticv2 def test_post_body_example(client: TestClient): response = client.put( "/items/5", @@ -34,7 +33,6 @@ def test_post_body_example(client: TestClient): assert response.status_code == 200 -@needs_pydanticv2 def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py index 552bb66970..6059962897 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py @@ -2,6 +2,7 @@ import importlib import pytest from fastapi.testclient import TestClient +from inline_snapshot import snapshot from ...utils import needs_py310, needs_pydanticv1 @@ -38,98 +39,106 @@ def test_post_body_example(client: TestClient): def test_openapi_schema(client: TestClient): response = client.get("/openapi.json") assert response.status_code == 200, response.text - # insert_assert(response.json()) - assert response.json() == { - "openapi": "3.1.0", - "info": {"title": "FastAPI", "version": "0.1.0"}, - "paths": { - "/items/{item_id}": { - "put": { - "summary": "Update Item", - "operationId": "update_item_items__item_id__put", - "parameters": [ - { - "required": True, - "schema": {"type": "integer", "title": "Item Id"}, - "name": "item_id", - "in": "path", - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": {"$ref": "#/components/schemas/Item"} + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/{item_id}": { + "put": { + "summary": "Update Item", + "operationId": "update_item_items__item_id__put", + "parameters": [ + { + "required": True, + "schema": {"type": "integer", "title": "Item Id"}, + "name": "item_id", + "in": "path", } - }, - "required": True, - }, - "responses": { - "200": { - "description": "Successful Response", - "content": {"application/json": {"schema": {}}}, - }, - "422": { - "description": "Validation Error", + ], + "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/HTTPValidationError" + "title": "Item", + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], } } }, + "required": True, }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "price": {"type": "number", "title": "Price"}, + "tax": {"type": "number", "title": "Tax"}, + }, + "type": "object", + "required": ["name", "price"], + "title": "Item", + "examples": [ + { + "name": "Foo", + "description": "A very nice Item", + "price": 35.4, + "tax": 3.2, + } + ], + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", }, } - } - }, - "components": { - "schemas": { - "HTTPValidationError": { - "properties": { - "detail": { - "items": {"$ref": "#/components/schemas/ValidationError"}, - "type": "array", - "title": "Detail", - } - }, - "type": "object", - "title": "HTTPValidationError", - }, - "Item": { - "properties": { - "name": {"type": "string", "title": "Name"}, - "description": {"type": "string", "title": "Description"}, - "price": {"type": "number", "title": "Price"}, - "tax": {"type": "number", "title": "Tax"}, - }, - "type": "object", - "required": ["name", "price"], - "title": "Item", - "examples": [ - { - "name": "Foo", - "description": "A very nice Item", - "price": 35.4, - "tax": 3.2, - } - ], - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [{"type": "string"}, {"type": "integer"}] - }, - "type": "array", - "title": "Location", - }, - "msg": {"type": "string", "title": "Message"}, - "type": {"type": "string", "title": "Error Type"}, - }, - "type": "object", - "required": ["loc", "msg", "type"], - "title": "ValidationError", - }, - } - }, - } + }, + } + ) diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py index b59d799a31..275b234877 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial001.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -38,7 +38,6 @@ def test_read_items(client: TestClient) -> None: ] -@needs_pydanticv2 def test_openapi_schema(client: TestClient) -> None: response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py index 61fbacfc34..8230e39226 100644 --- a/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py +++ b/tests/test_tutorial/test_separate_openapi_schemas/test_tutorial002.py @@ -3,7 +3,7 @@ import importlib import pytest from fastapi.testclient import TestClient -from ...utils import needs_py310, needs_pydanticv2 +from ...utils import needs_py310 @pytest.fixture( @@ -38,7 +38,6 @@ def test_read_items(client: TestClient) -> None: ] -@needs_pydanticv2 def test_openapi_schema(client: TestClient) -> None: response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_tutorial/test_settings/test_app02.py b/tests/test_tutorial/test_settings/test_app02.py index 9cedc5a526..9cbed4fd1b 100644 --- a/tests/test_tutorial/test_settings/test_app02.py +++ b/tests/test_tutorial/test_settings/test_app02.py @@ -4,8 +4,6 @@ from types import ModuleType import pytest from pytest import MonkeyPatch -from ...utils import needs_pydanticv2 - @pytest.fixture( name="mod_path", @@ -31,7 +29,6 @@ def get_test_main_mod(mod_path: str) -> ModuleType: return test_main_mod -@needs_pydanticv2 def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") settings = main_mod.get_settings() @@ -39,6 +36,5 @@ def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): assert settings.items_per_user == 50 -@needs_pydanticv2 def test_override_settings(test_main_mod: ModuleType): test_main_mod.test_app() diff --git a/tests/test_tutorial/test_settings/test_app03.py b/tests/test_tutorial/test_settings/test_app03.py index dbaf8f3f90..06e82398d1 100644 --- a/tests/test_tutorial/test_settings/test_app03.py +++ b/tests/test_tutorial/test_settings/test_app03.py @@ -5,7 +5,7 @@ import pytest from fastapi.testclient import TestClient from pytest import MonkeyPatch -from ...utils import needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_pydanticv1 @pytest.fixture( @@ -26,7 +26,6 @@ def get_main_mod(mod_path: str) -> ModuleType: return main_mod -@needs_pydanticv2 def test_settings(main_mod: ModuleType, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") settings = main_mod.get_settings() @@ -45,7 +44,6 @@ def test_settings_pv1(mod_path: str, monkeypatch: MonkeyPatch): assert settings.items_per_user == 50 -@needs_pydanticv2 def test_endpoint(main_mod: ModuleType, monkeypatch: MonkeyPatch): monkeypatch.setenv("ADMIN_EMAIL", "admin@example.com") client = TestClient(main_mod.app) diff --git a/tests/test_tutorial/test_settings/test_tutorial001.py b/tests/test_tutorial/test_settings/test_tutorial001.py index 2c6dce261f..6a08096989 100644 --- a/tests/test_tutorial/test_settings/test_tutorial001.py +++ b/tests/test_tutorial/test_settings/test_tutorial001.py @@ -4,13 +4,13 @@ import pytest from fastapi.testclient import TestClient from pytest import MonkeyPatch -from ...utils import needs_pydanticv1, needs_pydanticv2 +from ...utils import needs_pydanticv1 @pytest.fixture( name="app", params=[ - pytest.param("tutorial001_py39", marks=needs_pydanticv2), + pytest.param("tutorial001_py39"), pytest.param("tutorial001_pv1_py39", marks=needs_pydanticv1), ], ) diff --git a/tests/test_union_body_discriminator.py b/tests/test_union_body_discriminator.py index bf41a72915..40fd0065a9 100644 --- a/tests/test_union_body_discriminator.py +++ b/tests/test_union_body_discriminator.py @@ -7,10 +7,7 @@ from inline_snapshot import snapshot from pydantic import BaseModel, Field from typing_extensions import Literal -from .utils import needs_pydanticv2 - -@needs_pydanticv2 def test_discriminator_pydantic_v2() -> None: from pydantic import Tag diff --git a/tests/test_union_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py index f8108c8df4..42a6aed24c 100644 --- a/tests/test_union_body_discriminator_annotated.py +++ b/tests/test_union_body_discriminator_annotated.py @@ -8,8 +8,6 @@ from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel -from .utils import needs_pydanticv2 - @pytest.fixture(name="client") def client_fixture() -> TestClient: @@ -47,21 +45,18 @@ def client_fixture() -> TestClient: return client -@needs_pydanticv2 def test_union_body_discriminator_assignment(client: TestClient) -> None: response = client.post("/pet/assignment", json={"pet_type": "cat", "meows": 5}) assert response.status_code == 200, response.text assert response.json() == {"pet_type": "cat", "meows": 5} -@needs_pydanticv2 def test_union_body_discriminator_annotated(client: TestClient) -> None: response = client.post("/pet/annotated", json={"pet_type": "dog", "barks": 3.5}) assert response.status_code == 200, response.text assert response.json() == {"pet_type": "dog", "barks": 3.5} -@needs_pydanticv2 def test_openapi_schema(client: TestClient) -> None: response = client.get("/openapi.json") assert response.status_code == 200, response.text diff --git a/tests/test_validate_response_recursive/app.py b/tests/test_validate_response_recursive/app.py index 8f76572ba6..d51aa848d0 100644 --- a/tests/test_validate_response_recursive/app.py +++ b/tests/test_validate_response_recursive/app.py @@ -1,5 +1,4 @@ from fastapi import FastAPI -from fastapi._compat import PYDANTIC_V2 from pydantic import BaseModel app = FastAPI() @@ -20,13 +19,9 @@ class RecursiveItemViaSubmodel(BaseModel): name: str -if PYDANTIC_V2: - RecursiveItem.model_rebuild() - RecursiveSubitemInSubmodel.model_rebuild() - RecursiveItemViaSubmodel.model_rebuild() -else: - RecursiveItem.update_forward_refs() - RecursiveSubitemInSubmodel.update_forward_refs() +RecursiveItem.model_rebuild() +RecursiveSubitemInSubmodel.model_rebuild() +RecursiveItemViaSubmodel.model_rebuild() @app.get("/items/recursive", response_model=RecursiveItem) diff --git a/tests/utils.py b/tests/utils.py index 691e92bbfb..b896d4527f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,43 +1,19 @@ import sys import pytest -from fastapi._compat import PYDANTIC_V2 -from inline_snapshot import Snapshot needs_py39 = pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python3.9+") needs_py310 = pytest.mark.skipif( sys.version_info < (3, 10), reason="requires python3.10+" ) needs_py_lt_314 = pytest.mark.skipif( - sys.version_info > (3, 13), reason="requires python3.13-" + sys.version_info >= (3, 14), reason="requires python3.13-" ) -needs_pydanticv2 = pytest.mark.skipif(not PYDANTIC_V2, reason="requires Pydantic v2") -needs_pydanticv1 = pytest.mark.skipif(PYDANTIC_V2, reason="requires Pydantic v1") + +needs_pydanticv1 = needs_py_lt_314 def skip_module_if_py_gte_314(): """Skip entire module on Python 3.14+ at import time.""" if sys.version_info >= (3, 14): pytest.skip("requires python3.13-", allow_module_level=True) - - -def pydantic_snapshot( - *, - v2: Snapshot, - v1: Snapshot, # TODO: remove v1 argument when deprecating Pydantic v1 -): - """ - This function should be used like this: - - >>> assert value == pydantic_snapshot(v2=snapshot(),v1=snapshot()) - - inline-snapshot will create the snapshots when pytest is executed for each versions of pydantic. - - It is also possible to use the function inside snapshots for version-specific values. - - >>> assert value == snapshot({ - "data": "some data", - "version_specific": pydantic_snapshot(v2=snapshot(),v1=snapshot()), - }) - """ - return v2 if PYDANTIC_V2 else v1 From eac57f6908dc7f9e9d65b1ddf638a496696a9818 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Dec 2025 15:56:04 +0000 Subject: [PATCH 123/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 259546d7ed..be48d562cc 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Upgrades + +* ➖ Drop support for Pydantic v1, keeping short temporary support for Pydantic v2's `pydantic.v1`. PR [#14575](https://github.com/fastapi/fastapi/pull/14575) by [@tiangolo](https://github.com/tiangolo). + ### Docs * 📝 Fix duplicated variable in `docs_src/python_types/tutorial005_py39.py`. PR [#14565](https://github.com/fastapi/fastapi/pull/14565) by [@paras-verma7454](https://github.com/paras-verma7454). From 1cb4e25651d7cb6690d658ff60289a5fb9d65167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 20 Dec 2025 08:08:57 -0800 Subject: [PATCH 124/140] =?UTF-8?q?=F0=9F=94=A7=20Tweak=20pre-commit=20to?= =?UTF-8?q?=20allow=20committing=20release-notes=20(#14577)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e6d93fb7d..a65d97dad3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,7 @@ repos: rev: v6.0.0 hooks: - id: check-added-large-files + args: ['--maxkb=750'] - id: check-toml - id: check-yaml args: From 55ec28b81b5857294a5871a937045483596a54bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Dec 2025 16:09:21 +0000 Subject: [PATCH 125/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index be48d562cc..35a097a7a0 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -21,6 +21,7 @@ hide: ### Internal +* 🔧 Tweak pre-commit to allow committing release-notes. PR [#14577](https://github.com/fastapi/fastapi/pull/14577) by [@tiangolo](https://github.com/tiangolo). * ⬆️ Use prek as a pre-commit alternative. PR [#14572](https://github.com/fastapi/fastapi/pull/14572) by [@tiangolo](https://github.com/tiangolo). * 👷 Add performance tests with CodSpeed. PR [#14558](https://github.com/fastapi/fastapi/pull/14558) by [@tiangolo](https://github.com/tiangolo). From 10252b193716e8b5c64d5b6760fc9aac9e3787d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 20 Dec 2025 17:11:50 +0100 Subject: [PATCH 126/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?6.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 35a097a7a0..d2026b756f 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.126.0 + ### Upgrades * ➖ Drop support for Pydantic v1, keeping short temporary support for Pydantic v2's `pydantic.v1`. PR [#14575](https://github.com/fastapi/fastapi/pull/14575) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 2f13448467..7ed0fa95bb 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.125.0" +__version__ = "0.126.0" from starlette import status as status From 6b591ddd7e16727c57be1dad153b49ad9741a863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 20 Dec 2025 17:15:42 +0100 Subject: [PATCH 127/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index d2026b756f..3a7c2a6ce6 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,8 @@ hide: ### Upgrades * ➖ Drop support for Pydantic v1, keeping short temporary support for Pydantic v2's `pydantic.v1`. PR [#14575](https://github.com/fastapi/fastapi/pull/14575) by [@tiangolo](https://github.com/tiangolo). + * The minimum version of Pydantic installed is now `pydantic >=2.7.0`. + * The `standard` dependencies now include `pydantic-settings >=2.0.0` and `pydantic-extra-types >=2.0.0`. ### Docs From 026b43e5d3008c86cbf6f1a76ba471276418413c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 20 Dec 2025 09:30:52 -0800 Subject: [PATCH 128/140] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20Japanese,=20generated=20from=20the=20existing=20trans?= =?UTF-8?q?lations=20(#14545)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maruo.S --- docs/ja/llm-prompt.md | 47 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 docs/ja/llm-prompt.md diff --git a/docs/ja/llm-prompt.md b/docs/ja/llm-prompt.md new file mode 100644 index 0000000000..c47cc36df7 --- /dev/null +++ b/docs/ja/llm-prompt.md @@ -0,0 +1,47 @@ +### Target language + +Translate to Japanese (日本語). + +Language code: ja. + +### Grammar and tone + +1) Use polite, instructional Japanese (です/ます調). +2) Keep the tone concise and technical (match existing Japanese FastAPI docs). + +### Headings + +1) Follow the existing Japanese style: short, descriptive headings (often noun phrases), e.g. 「チェック」. +2) Do not add a trailing period at the end of headings. + +### Quotes + +1) Prefer Japanese corner brackets 「」 in normal prose when quoting a term. +2) Do not change quotes inside inline code, code blocks, URLs, or file paths. + +### Ellipsis + +1) Keep ellipsis style consistent with existing Japanese docs (commonly `...`). +2) Never change `...` in code, URLs, or CLI examples. + +### Preferred translations / glossary + +Use the following preferred translations when they apply in documentation prose: + +- request (HTTP): リクエスト +- response (HTTP): レスポンス +- path operation: パスオペレーション +- path operation function: パスオペレーション関数 + +### `///` admonitions + +1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +2) If a title is present, prefer these canonical titles: + +- `/// note | 備考` +- `/// note | 技術詳細` +- `/// tip | 豆知識` +- `/// warning | 注意` +- `/// info | 情報` +- `/// check | 確認` +- `/// danger | 警告` From 5783910d0c0fc8a3da8fff793b681121c61efb4a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Dec 2025 17:31:11 +0000 Subject: [PATCH 129/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 3a7c2a6ce6..174af0cb42 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Translations + +* 🔧 Add LLM prompt file for Japanese, generated from the existing translations. PR [#14545](https://github.com/fastapi/fastapi/pull/14545) by [@tiangolo](https://github.com/tiangolo). + ## 0.126.0 ### Upgrades From 52892592753333c89076616824e3fe23181d65b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 20 Dec 2025 09:32:05 -0800 Subject: [PATCH 130/140] =?UTF-8?q?=F0=9F=94=A7=20Add=20LLM=20prompt=20fil?= =?UTF-8?q?e=20for=20Korean,=20generated=20from=20the=20existing=20transla?= =?UTF-8?q?tions=20(#14546)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hy.lee --- docs/ko/llm-prompt.md | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 docs/ko/llm-prompt.md diff --git a/docs/ko/llm-prompt.md b/docs/ko/llm-prompt.md new file mode 100644 index 0000000000..008511a5b7 --- /dev/null +++ b/docs/ko/llm-prompt.md @@ -0,0 +1,51 @@ +### Target language + +Translate to Korean (한국어). + +Language code: ko. + +### Grammar and tone + +1) Use polite, instructional Korean (e.g. 합니다/하세요 style). +2) Keep the tone consistent with the existing Korean FastAPI docs. + +### Headings + +1) Follow existing Korean heading style (short, action-oriented headings like “확인하기”). +2) Do not add trailing punctuation to headings. + +### Quotes + +1) Keep quote style consistent with the existing Korean docs. +2) Never change quotes inside inline code, code blocks, URLs, or file paths. + +### Ellipsis + +1) Keep ellipsis style consistent with existing Korean docs (often `...`). +2) Never change `...` in code, URLs, or CLI examples. + +### Preferred translations / glossary + +Use the following preferred translations when they apply in documentation prose: + +- request (HTTP): 요청 +- response (HTTP): 응답 +- path operation: 경로 처리 +- path operation function: 경로 처리 함수 + +### `///` admonitions + +1) Keep the admonition keyword in English (do not translate `note`, `tip`, etc.). +2) If a title is present, prefer these canonical titles: + +- `/// note | 참고` +- `/// tip | 팁` +- `/// warning | 경고` +- `/// info | 정보` +- `/// danger | 위험` +- `/// note Technical Details | 기술 세부사항` +- `/// check | 확인` +Notes: + +- `details` blocks exist in Korean docs; keep `/// details` as-is and translate only the title after `|`. +- Example canonical title used: `/// details | 상세 설명` From c2c1cc8aec83e713326842dffacd0e38a6ff24c2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Dec 2025 17:32:31 +0000 Subject: [PATCH 131/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 174af0cb42..7e3ef48ece 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Translations +* 🔧 Add LLM prompt file for Korean, generated from the existing translations. PR [#14546](https://github.com/fastapi/fastapi/pull/14546) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add LLM prompt file for Japanese, generated from the existing translations. PR [#14545](https://github.com/fastapi/fastapi/pull/14545) by [@tiangolo](https://github.com/tiangolo). ## 0.126.0 From 1d93d531bc950ae880679e2e365fb6d7e028d106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 21 Dec 2025 00:06:22 -0800 Subject: [PATCH 132/140] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20OpenAI?= =?UTF-8?q?=20model=20for=20translations=20to=20gpt-5.2=20(#14579)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/translate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/translate.py b/scripts/translate.py index 764bc704ea..6ebd24f547 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -727,7 +727,7 @@ def translate_page( print(f"Found existing translation: {out_path}") old_translation = out_path.read_text(encoding="utf-8") print(f"Translating {en_path} to {language} ({language_name})") - agent = Agent("openai:gpt-5") + agent = Agent("openai:gpt-5.2") prompt_segments = [ general_prompt, From 6513d4daa16a536d17743de6a292f49bd06388a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Dec 2025 08:06:42 +0000 Subject: [PATCH 133/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 7e3ef48ece..12267e3b10 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -12,6 +12,10 @@ hide: * 🔧 Add LLM prompt file for Korean, generated from the existing translations. PR [#14546](https://github.com/fastapi/fastapi/pull/14546) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add LLM prompt file for Japanese, generated from the existing translations. PR [#14545](https://github.com/fastapi/fastapi/pull/14545) by [@tiangolo](https://github.com/tiangolo). +### Internal + +* ⬆️ Upgrade OpenAI model for translations to gpt-5.2. PR [#14579](https://github.com/fastapi/fastapi/pull/14579) by [@tiangolo](https://github.com/tiangolo). + ## 0.126.0 ### Upgrades From 6e42bcd8ce2ade33d94f478310dad20ee84f8f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 21 Dec 2025 08:44:10 -0800 Subject: [PATCH 134/140] =?UTF-8?q?=F0=9F=94=8A=20Add=20deprecation=20warn?= =?UTF-8?q?ings=20when=20using=20`pydantic.v1`=20(#14583)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastapi/dependencies/utils.py | 8 + fastapi/routing.py | 16 + tests/benchmarks/test_general_performance.py | 150 ++++----- tests/test_compat_params_v1.py | 167 +++++----- tests/test_datetime_custom_encoder.py | 10 +- .../test_filter_pydantic_sub_model/app_pv1.py | 20 +- ...t_get_model_definitions_formfeed_escape.py | 30 +- .../test_pydantic_v1_deprecation_warnings.py | 98 ++++++ tests/test_pydantic_v1_v2_01.py | 40 +-- tests/test_pydantic_v1_v2_list.py | 75 +++-- tests/test_pydantic_v1_v2_mixed.py | 301 +++++++++--------- tests/test_pydantic_v1_v2_multifile/main.py | 213 ++++++------- tests/test_pydantic_v1_v2_noneable.py | 115 +++---- tests/test_read_with_orm_mode.py | 12 +- ...est_response_model_as_return_annotation.py | 12 +- .../test_tutorial002.py | 9 +- .../test_tutorial003.py | 9 +- .../test_tutorial004.py | 9 +- .../test_tutorial002_pv1.py | 9 +- .../test_tutorial001_pv1.py | 9 +- 20 files changed, 756 insertions(+), 556 deletions(-) create mode 100644 tests/test_pydantic_v1_deprecation_warnings.py diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index 0ba93524c4..39d0bd89cd 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,6 +1,7 @@ import dataclasses import inspect import sys +import warnings from collections.abc import Coroutine, Mapping, Sequence from contextlib import AsyncExitStack, contextmanager from copy import copy, deepcopy @@ -322,6 +323,13 @@ def get_dependant( ) continue assert param_details.field is not None + if isinstance(param_details.field, may_v1.ModelField): + warnings.warn( + "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." + f" Please update the param {param_name}: {param_details.type_annotation!r}.", + category=DeprecationWarning, + stacklevel=5, + ) if isinstance( param_details.field.field_info, (params.Body, temp_pydantic_v1_params.Body) ): diff --git a/fastapi/routing.py b/fastapi/routing.py index a1f2e44bb4..2770e3253d 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2,6 +2,7 @@ import email.message import functools import inspect import json +import warnings from collections.abc import ( AsyncIterator, Awaitable, @@ -28,6 +29,7 @@ from fastapi._compat import ( _get_model_config, _model_dump, _normalize_errors, + annotation_is_pydantic_v1, lenient_issubclass, may_v1, ) @@ -634,6 +636,13 @@ class APIRoute(routing.Route): f"Status code {status_code} must not have a response body" ) response_name = "Response_" + self.unique_id + if annotation_is_pydantic_v1(self.response_model): + warnings.warn( + "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." + f" Please update the response model {self.response_model!r}.", + category=DeprecationWarning, + stacklevel=4, + ) self.response_field = create_model_field( name=response_name, type_=self.response_model, @@ -667,6 +676,13 @@ class APIRoute(routing.Route): f"Status code {additional_status_code} must not have a response body" ) response_name = f"Response_{additional_status_code}_{self.unique_id}" + if annotation_is_pydantic_v1(model): + warnings.warn( + "pydantic.v1 is deprecated and will soon stop being supported by FastAPI." + f" In responses={{}}, please update {model}.", + category=DeprecationWarning, + stacklevel=4, + ) response_field = create_model_field( name=response_name, type_=model, mode="serialization" ) diff --git a/tests/benchmarks/test_general_performance.py b/tests/benchmarks/test_general_performance.py index dca3613d00..2da74b95c5 100644 --- a/tests/benchmarks/test_general_performance.py +++ b/tests/benchmarks/test_general_performance.py @@ -1,5 +1,6 @@ import json import sys +import warnings from collections.abc import Iterator from typing import Annotated, Any @@ -84,96 +85,103 @@ def app(basemodel_class: type[Any]) -> FastAPI: app = FastAPI() - @app.post("/sync/validated", response_model=ItemOut) - def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): - return ItemOut(name=item.name, value=item.value, dep=dep) + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + "ignore", + message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", + category=DeprecationWarning, + ) - @app.get("/sync/dict-no-response-model") - def sync_dict_no_response_model(): - return {"name": "foo", "value": 123} + @app.post("/sync/validated", response_model=ItemOut) + def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name=item.name, value=item.value, dep=dep) - @app.get("/sync/dict-with-response-model", response_model=ItemOut) - def sync_dict_with_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return {"name": "foo", "value": 123, "dep": dep} + @app.get("/sync/dict-no-response-model") + def sync_dict_no_response_model(): + return {"name": "foo", "value": 123} - @app.get("/sync/model-no-response-model") - def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): - return ItemOut(name="foo", value=123, dep=dep) + @app.get("/sync/dict-with-response-model", response_model=ItemOut) + def sync_dict_with_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return {"name": "foo", "value": 123, "dep": dep} - @app.get("/sync/model-with-response-model", response_model=ItemOut) - def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): - return ItemOut(name="foo", value=123, dep=dep) + @app.get("/sync/model-no-response-model") + def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name="foo", value=123, dep=dep) - @app.post("/async/validated", response_model=ItemOut) - async def async_validated( - item: ItemIn, - dep: Annotated[int, Depends(dep_b)], - ): - return ItemOut(name=item.name, value=item.value, dep=dep) + @app.get("/sync/model-with-response-model", response_model=ItemOut) + def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): + return ItemOut(name="foo", value=123, dep=dep) - @app.post("/sync/large-receive") - def sync_large_receive(payload: LargeIn): - return {"received": len(payload.items)} + @app.post("/async/validated", response_model=ItemOut) + async def async_validated( + item: ItemIn, + dep: Annotated[int, Depends(dep_b)], + ): + return ItemOut(name=item.name, value=item.value, dep=dep) - @app.post("/async/large-receive") - async def async_large_receive(payload: LargeIn): - return {"received": len(payload.items)} + @app.post("/sync/large-receive") + def sync_large_receive(payload: LargeIn): + return {"received": len(payload.items)} - @app.get("/sync/large-dict-no-response-model") - def sync_large_dict_no_response_model(): - return LARGE_PAYLOAD + @app.post("/async/large-receive") + async def async_large_receive(payload: LargeIn): + return {"received": len(payload.items)} - @app.get("/sync/large-dict-with-response-model", response_model=LargeOut) - def sync_large_dict_with_response_model(): - return LARGE_PAYLOAD + @app.get("/sync/large-dict-no-response-model") + def sync_large_dict_no_response_model(): + return LARGE_PAYLOAD - @app.get("/sync/large-model-no-response-model") - def sync_large_model_no_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + @app.get("/sync/large-dict-with-response-model", response_model=LargeOut) + def sync_large_dict_with_response_model(): + return LARGE_PAYLOAD - @app.get("/sync/large-model-with-response-model", response_model=LargeOut) - def sync_large_model_with_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + @app.get("/sync/large-model-no-response-model") + def sync_large_model_no_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - @app.get("/async/large-dict-no-response-model") - async def async_large_dict_no_response_model(): - return LARGE_PAYLOAD + @app.get("/sync/large-model-with-response-model", response_model=LargeOut) + def sync_large_model_with_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - @app.get("/async/large-dict-with-response-model", response_model=LargeOut) - async def async_large_dict_with_response_model(): - return LARGE_PAYLOAD + @app.get("/async/large-dict-no-response-model") + async def async_large_dict_no_response_model(): + return LARGE_PAYLOAD - @app.get("/async/large-model-no-response-model") - async def async_large_model_no_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + @app.get("/async/large-dict-with-response-model", response_model=LargeOut) + async def async_large_dict_with_response_model(): + return LARGE_PAYLOAD - @app.get("/async/large-model-with-response-model", response_model=LargeOut) - async def async_large_model_with_response_model(): - return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) + @app.get("/async/large-model-no-response-model") + async def async_large_model_no_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - @app.get("/async/dict-no-response-model") - async def async_dict_no_response_model(): - return {"name": "foo", "value": 123} + @app.get("/async/large-model-with-response-model", response_model=LargeOut) + async def async_large_model_with_response_model(): + return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) - @app.get("/async/dict-with-response-model", response_model=ItemOut) - async def async_dict_with_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return {"name": "foo", "value": 123, "dep": dep} + @app.get("/async/dict-no-response-model") + async def async_dict_no_response_model(): + return {"name": "foo", "value": 123} - @app.get("/async/model-no-response-model") - async def async_model_no_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return ItemOut(name="foo", value=123, dep=dep) + @app.get("/async/dict-with-response-model", response_model=ItemOut) + async def async_dict_with_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return {"name": "foo", "value": 123, "dep": dep} - @app.get("/async/model-with-response-model", response_model=ItemOut) - async def async_model_with_response_model( - dep: Annotated[int, Depends(dep_b)], - ): - return ItemOut(name="foo", value=123, dep=dep) + @app.get("/async/model-no-response-model") + async def async_model_no_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return ItemOut(name="foo", value=123, dep=dep) + + @app.get("/async/model-with-response-model", response_model=ItemOut) + async def async_model_with_response_model( + dep: Annotated[int, Depends(dep_b)], + ): + return ItemOut(name="foo", value=123, dep=dep) return app diff --git a/tests/test_compat_params_v1.py b/tests/test_compat_params_v1.py index b4ca861be3..2ac96993a8 100644 --- a/tests/test_compat_params_v1.py +++ b/tests/test_compat_params_v1.py @@ -1,4 +1,5 @@ import sys +import warnings from typing import Optional import pytest @@ -33,94 +34,90 @@ class Item(BaseModel): app = FastAPI() +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") -@app.get("/items/{item_id}") -def get_item_with_path( - item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)], -): - return {"item_id": item_id} + @app.get("/items/{item_id}") + def get_item_with_path( + item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=1000)], + ): + return {"item_id": item_id} + @app.get("/items/") + def get_items_with_query( + q: Annotated[ + Optional[str], + Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$"), + ] = None, + skip: Annotated[int, Query(ge=0)] = 0, + limit: Annotated[int, Query(ge=1, le=100, examples=[5])] = 10, + ): + return {"q": q, "skip": skip, "limit": limit} -@app.get("/items/") -def get_items_with_query( - q: Annotated[ - Optional[str], Query(min_length=3, max_length=50, pattern="^[a-zA-Z0-9 ]+$") - ] = None, - skip: Annotated[int, Query(ge=0)] = 0, - limit: Annotated[int, Query(ge=1, le=100, examples=[5])] = 10, -): - return {"q": q, "skip": skip, "limit": limit} + @app.get("/users/") + def get_user_with_header( + x_custom: Annotated[Optional[str], Header()] = None, + x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None, + ): + return {"x_custom": x_custom, "x_token": x_token} + @app.get("/cookies/") + def get_cookies( + session_id: Annotated[Optional[str], Cookie()] = None, + tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None, + ): + return {"session_id": session_id, "tracking_id": tracking_id} -@app.get("/users/") -def get_user_with_header( - x_custom: Annotated[Optional[str], Header()] = None, - x_token: Annotated[Optional[str], Header(convert_underscores=True)] = None, -): - return {"x_custom": x_custom, "x_token": x_token} + @app.post("/items/") + def create_item( + item: Annotated[ + Item, + Body( + examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}] + ), + ], + ): + return {"item": item} + @app.post("/items-embed/") + def create_item_embed( + item: Annotated[Item, Body(embed=True)], + ): + return {"item": item} -@app.get("/cookies/") -def get_cookies( - session_id: Annotated[Optional[str], Cookie()] = None, - tracking_id: Annotated[Optional[str], Cookie(min_length=10)] = None, -): - return {"session_id": session_id, "tracking_id": tracking_id} + @app.put("/items/{item_id}") + def update_item( + item_id: Annotated[int, Path(ge=1)], + item: Annotated[Item, Body()], + importance: Annotated[int, Body(gt=0, le=10)], + ): + return {"item": item, "importance": importance} + @app.post("/form-data/") + def submit_form( + username: Annotated[str, Form(min_length=3, max_length=50)], + password: Annotated[str, Form(min_length=8)], + email: Annotated[Optional[str], Form()] = None, + ): + return {"username": username, "password": password, "email": email} -@app.post("/items/") -def create_item( - item: Annotated[ - Item, - Body(examples=[{"name": "Foo", "price": 35.4, "description": "The Foo item"}]), - ], -): - return {"item": item} + @app.post("/upload/") + def upload_file( + file: Annotated[bytes, File()], + description: Annotated[Optional[str], Form()] = None, + ): + return {"file_size": len(file), "description": description} - -@app.post("/items-embed/") -def create_item_embed( - item: Annotated[Item, Body(embed=True)], -): - return {"item": item} - - -@app.put("/items/{item_id}") -def update_item( - item_id: Annotated[int, Path(ge=1)], - item: Annotated[Item, Body()], - importance: Annotated[int, Body(gt=0, le=10)], -): - return {"item": item, "importance": importance} - - -@app.post("/form-data/") -def submit_form( - username: Annotated[str, Form(min_length=3, max_length=50)], - password: Annotated[str, Form(min_length=8)], - email: Annotated[Optional[str], Form()] = None, -): - return {"username": username, "password": password, "email": email} - - -@app.post("/upload/") -def upload_file( - file: Annotated[bytes, File()], - description: Annotated[Optional[str], Form()] = None, -): - return {"file_size": len(file), "description": description} - - -@app.post("/upload-multiple/") -def upload_multiple_files( - files: Annotated[list[bytes], File()], - note: Annotated[str, Form()] = "", -): - return { - "file_count": len(files), - "total_size": sum(len(f) for f in files), - "note": note, - } + @app.post("/upload-multiple/") + def upload_multiple_files( + files: Annotated[list[bytes], File()], + note: Annotated[str, Form()] = "", + ): + return { + "file_count": len(files), + "total_size": sum(len(f) for f in files), + "note": note, + } client = TestClient(app) @@ -211,10 +208,10 @@ def test_header_params_none(): # Cookie parameter tests def test_cookie_params(): - with TestClient(app) as client: - client.cookies.set("session_id", "abc123") - client.cookies.set("tracking_id", "1234567890abcdef") - response = client.get("/cookies/") + with TestClient(app) as test_client: + test_client.cookies.set("session_id", "abc123") + test_client.cookies.set("tracking_id", "1234567890abcdef") + response = test_client.get("/cookies/") assert response.status_code == 200 assert response.json() == { "session_id": "abc123", @@ -223,9 +220,9 @@ def test_cookie_params(): def test_cookie_tracking_id_too_short(): - with TestClient(app) as client: - client.cookies.set("tracking_id", "short") - response = client.get("/cookies/") + with TestClient(app) as test_client: + test_client.cookies.set("tracking_id", "short") + response = test_client.get("/cookies/") assert response.status_code == 422 assert response.json() == snapshot( { diff --git a/tests/test_datetime_custom_encoder.py b/tests/test_datetime_custom_encoder.py index 822651f4f1..56b6780f04 100644 --- a/tests/test_datetime_custom_encoder.py +++ b/tests/test_datetime_custom_encoder.py @@ -1,3 +1,4 @@ +import warnings from datetime import datetime, timezone from fastapi import FastAPI @@ -48,9 +49,12 @@ def test_pydanticv1(): app = FastAPI() model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8)) - @app.get("/model", response_model=ModelWithDatetimeField) - def get_model(): - return model + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + + @app.get("/model", response_model=ModelWithDatetimeField) + def get_model(): + return model client = TestClient(app) with client: diff --git a/tests/test_filter_pydantic_sub_model/app_pv1.py b/tests/test_filter_pydantic_sub_model/app_pv1.py index 0b6ab53e04..d6f2ce7d2d 100644 --- a/tests/test_filter_pydantic_sub_model/app_pv1.py +++ b/tests/test_filter_pydantic_sub_model/app_pv1.py @@ -1,3 +1,4 @@ +import warnings from typing import Optional from fastapi import Depends, FastAPI @@ -31,11 +32,14 @@ async def get_model_c() -> ModelC: return ModelC(username="test-user", password="test-password") -@app.get("/model/{name}", response_model=ModelA) -async def get_model_a(name: str, model_c=Depends(get_model_c)): - return { - "name": name, - "description": "model-a-desc", - "model_b": model_c, - "tags": {"key1": "value1", "key2": "value2"}, - } +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + + @app.get("/model/{name}", response_model=ModelA) + async def get_model_a(name: str, model_c=Depends(get_model_c)): + return { + "name": name, + "description": "model-a-desc", + "model_b": model_c, + "tags": {"key1": "value1", "key2": "value2"}, + } diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py index 50d799a571..dee5955544 100644 --- a/tests/test_get_model_definitions_formfeed_escape.py +++ b/tests/test_get_model_definitions_formfeed_escape.py @@ -1,3 +1,5 @@ +import warnings + import pytest from fastapi import FastAPI from fastapi.testclient import TestClient @@ -36,12 +38,28 @@ def client_fixture(request: pytest.FixtureRequest) -> TestClient: app = FastAPI() - @app.get("/facilities/{facility_id}") - def get_facility(facility_id: str) -> Facility: - return Facility( - id=facility_id, - address=Address(line_1="123 Main St", city="Anytown", state_province="CA"), - ) + if request.param == "pydantic-v1": + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + + @app.get("/facilities/{facility_id}") + def get_facility(facility_id: str) -> Facility: + return Facility( + id=facility_id, + address=Address( + line_1="123 Main St", city="Anytown", state_province="CA" + ), + ) + else: + + @app.get("/facilities/{facility_id}") + def get_facility(facility_id: str) -> Facility: + return Facility( + id=facility_id, + address=Address( + line_1="123 Main St", city="Anytown", state_province="CA" + ), + ) client = TestClient(app) return client diff --git a/tests/test_pydantic_v1_deprecation_warnings.py b/tests/test_pydantic_v1_deprecation_warnings.py new file mode 100644 index 0000000000..e0008e2183 --- /dev/null +++ b/tests/test_pydantic_v1_deprecation_warnings.py @@ -0,0 +1,98 @@ +import sys + +import pytest + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +from fastapi import FastAPI +from fastapi._compat.v1 import BaseModel +from fastapi.testclient import TestClient + + +def test_warns_pydantic_v1_model_in_endpoint_param() -> None: + class ParamModelV1(BaseModel): + name: str + + app = FastAPI() + + with pytest.warns( + DeprecationWarning, + match=r"pydantic\.v1 is deprecated.*Please update the param data:", + ): + + @app.post("/param") + def endpoint(data: ParamModelV1): + return data + + client = TestClient(app) + response = client.post("/param", json={"name": "test"}) + assert response.status_code == 200, response.text + assert response.json() == {"name": "test"} + + +def test_warns_pydantic_v1_model_in_return_type() -> None: + class ReturnModelV1(BaseModel): + name: str + + app = FastAPI() + + with pytest.warns( + DeprecationWarning, + match=r"pydantic\.v1 is deprecated.*Please update the response model", + ): + + @app.get("/return") + def endpoint() -> ReturnModelV1: + return ReturnModelV1(name="test") + + client = TestClient(app) + response = client.get("/return") + assert response.status_code == 200, response.text + assert response.json() == {"name": "test"} + + +def test_warns_pydantic_v1_model_in_response_model() -> None: + class ResponseModelV1(BaseModel): + name: str + + app = FastAPI() + + with pytest.warns( + DeprecationWarning, + match=r"pydantic\.v1 is deprecated.*Please update the response model", + ): + + @app.get("/response-model", response_model=ResponseModelV1) + def endpoint(): + return {"name": "test"} + + client = TestClient(app) + response = client.get("/response-model") + assert response.status_code == 200, response.text + assert response.json() == {"name": "test"} + + +def test_warns_pydantic_v1_model_in_additional_responses_model() -> None: + class ErrorModelV1(BaseModel): + detail: str + + app = FastAPI() + + with pytest.warns( + DeprecationWarning, + match=r"pydantic\.v1 is deprecated.*In responses=\{\}, please update", + ): + + @app.get( + "/responses", response_model=None, responses={400: {"model": ErrorModelV1}} + ) + def endpoint(): + return {"ok": True} + + client = TestClient(app) + response = client.get("/responses") + assert response.status_code == 200, response.text + assert response.json() == {"ok": True} diff --git a/tests/test_pydantic_v1_v2_01.py b/tests/test_pydantic_v1_v2_01.py index 83536cafa2..4868e5d223 100644 --- a/tests/test_pydantic_v1_v2_01.py +++ b/tests/test_pydantic_v1_v2_01.py @@ -1,4 +1,5 @@ import sys +import warnings from typing import Any, Union from tests.utils import skip_module_if_py_gte_314 @@ -26,30 +27,29 @@ class Item(BaseModel): app = FastAPI() +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") -@app.post("/simple-model") -def handle_simple_model(data: SubItem) -> SubItem: - return data + @app.post("/simple-model") + def handle_simple_model(data: SubItem) -> SubItem: + return data + @app.post("/simple-model-filter", response_model=SubItem) + def handle_simple_model_filter(data: SubItem) -> Any: + extended_data = data.dict() + extended_data.update({"secret_price": 42}) + return extended_data -@app.post("/simple-model-filter", response_model=SubItem) -def handle_simple_model_filter(data: SubItem) -> Any: - extended_data = data.dict() - extended_data.update({"secret_price": 42}) - return extended_data + @app.post("/item") + def handle_item(data: Item) -> Item: + return data - -@app.post("/item") -def handle_item(data: Item) -> Item: - return data - - -@app.post("/item-filter", response_model=Item) -def handle_item_filter(data: Item) -> Any: - extended_data = data.dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return extended_data + @app.post("/item-filter", response_model=Item) + def handle_item_filter(data: Item) -> Any: + extended_data = data.dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return extended_data client = TestClient(app) diff --git a/tests/test_pydantic_v1_v2_list.py b/tests/test_pydantic_v1_v2_list.py index 4ddcbf240d..108f231faa 100644 --- a/tests/test_pydantic_v1_v2_list.py +++ b/tests/test_pydantic_v1_v2_list.py @@ -1,4 +1,5 @@ import sys +import warnings from typing import Any, Union from tests.utils import skip_module_if_py_gte_314 @@ -27,49 +28,47 @@ class Item(BaseModel): app = FastAPI() -@app.post("/item") -def handle_item(data: Item) -> list[Item]: - return [data, data] +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + @app.post("/item") + def handle_item(data: Item) -> list[Item]: + return [data, data] -@app.post("/item-filter", response_model=list[Item]) -def handle_item_filter(data: Item) -> Any: - extended_data = data.dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return [extended_data, extended_data] - - -@app.post("/item-list") -def handle_item_list(data: list[Item]) -> Item: - if data: - return data[0] - return Item(title="", size=0, sub=SubItem(name="")) - - -@app.post("/item-list-filter", response_model=Item) -def handle_item_list_filter(data: list[Item]) -> Any: - if data: - extended_data = data[0].dict() - extended_data.update({"secret_data": "classified", "internal_id": 12345}) - extended_data["sub"].update({"internal_id": 67890}) - return extended_data - return Item(title="", size=0, sub=SubItem(name="")) - - -@app.post("/item-list-to-list") -def handle_item_list_to_list(data: list[Item]) -> list[Item]: - return data - - -@app.post("/item-list-to-list-filter", response_model=list[Item]) -def handle_item_list_to_list_filter(data: list[Item]) -> Any: - if data: - extended_data = data[0].dict() + @app.post("/item-filter", response_model=list[Item]) + def handle_item_filter(data: Item) -> Any: + extended_data = data.dict() extended_data.update({"secret_data": "classified", "internal_id": 12345}) extended_data["sub"].update({"internal_id": 67890}) return [extended_data, extended_data] - return [] + + @app.post("/item-list") + def handle_item_list(data: list[Item]) -> Item: + if data: + return data[0] + return Item(title="", size=0, sub=SubItem(name="")) + + @app.post("/item-list-filter", response_model=Item) + def handle_item_list_filter(data: list[Item]) -> Any: + if data: + extended_data = data[0].dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return extended_data + return Item(title="", size=0, sub=SubItem(name="")) + + @app.post("/item-list-to-list") + def handle_item_list_to_list(data: list[Item]) -> list[Item]: + return data + + @app.post("/item-list-to-list-filter", response_model=list[Item]) + def handle_item_list_to_list_filter(data: list[Item]) -> Any: + if data: + extended_data = data[0].dict() + extended_data.update({"secret_data": "classified", "internal_id": 12345}) + extended_data["sub"].update({"internal_id": 67890}) + return [extended_data, extended_data] + return [] client = TestClient(app) diff --git a/tests/test_pydantic_v1_v2_mixed.py b/tests/test_pydantic_v1_v2_mixed.py index 61e5bb5827..895835a4c0 100644 --- a/tests/test_pydantic_v1_v2_mixed.py +++ b/tests/test_pydantic_v1_v2_mixed.py @@ -1,4 +1,5 @@ import sys +import warnings from typing import Any, Union from tests.utils import skip_module_if_py_gte_314 @@ -39,179 +40,181 @@ class NewItem(NewBaseModel): app = FastAPI() +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") -@app.post("/v1-to-v2/item") -def handle_v1_item_to_v2(data: Item) -> NewItem: - return NewItem( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=NewSubItem(new_sub_name=data.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], - ) + @app.post("/v1-to-v2/item") + def handle_v1_item_to_v2(data: Item) -> NewItem: + return NewItem( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=NewSubItem(new_sub_name=data.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], + ) + @app.post("/v1-to-v2/item-filter", response_model=NewItem) + def handle_v1_item_to_v2_filter(data: Item) -> Any: + result = { + "new_title": data.title, + "new_size": data.size, + "new_description": data.description, + "new_sub": { + "new_sub_name": data.sub.name, + "new_sub_secret": "sub_hidden", + }, + "new_multi": [ + {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} + for s in data.multi + ], + "secret": "hidden_v1_to_v2", + } + return result -@app.post("/v1-to-v2/item-filter", response_model=NewItem) -def handle_v1_item_to_v2_filter(data: Item) -> Any: - result = { - "new_title": data.title, - "new_size": data.size, - "new_description": data.description, - "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"}, - "new_multi": [ - {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi - ], - "secret": "hidden_v1_to_v2", - } - return result + @app.post("/v2-to-v1/item") + def handle_v2_item_to_v1(data: NewItem) -> Item: + return Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=SubItem(name=data.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + @app.post("/v2-to-v1/item-filter", response_model=Item) + def handle_v2_item_to_v1_filter(data: NewItem) -> Any: + result = { + "title": data.new_title, + "size": data.new_size, + "description": data.new_description, + "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, + "multi": [ + {"name": s.new_sub_name, "sub_secret": "sub_hidden"} + for s in data.new_multi + ], + "secret": "hidden_v2_to_v1", + } + return result -@app.post("/v2-to-v1/item") -def handle_v2_item_to_v1(data: NewItem) -> Item: - return Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=SubItem(name=data.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], - ) + @app.post("/v1-to-v2/item-to-list") + def handle_v1_item_to_v2_list(data: Item) -> list[NewItem]: + converted = NewItem( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=NewSubItem(new_sub_name=data.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], + ) + return [converted, converted] + @app.post("/v1-to-v2/list-to-list") + def handle_v1_list_to_v2_list(data: list[Item]) -> list[NewItem]: + result = [] + for item in data: + result.append( + NewItem( + new_title=item.title, + new_size=item.size, + new_description=item.description, + new_sub=NewSubItem(new_sub_name=item.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], + ) + ) + return result -@app.post("/v2-to-v1/item-filter", response_model=Item) -def handle_v2_item_to_v1_filter(data: NewItem) -> Any: - result = { - "title": data.new_title, - "size": data.new_size, - "description": data.new_description, - "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, - "multi": [ - {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi - ], - "secret": "hidden_v2_to_v1", - } - return result + @app.post("/v1-to-v2/list-to-list-filter", response_model=list[NewItem]) + def handle_v1_list_to_v2_list_filter(data: list[Item]) -> Any: + result = [] + for item in data: + converted = { + "new_title": item.title, + "new_size": item.size, + "new_description": item.description, + "new_sub": { + "new_sub_name": item.sub.name, + "new_sub_secret": "sub_hidden", + }, + "new_multi": [ + {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} + for s in item.multi + ], + "secret": "hidden_v2_to_v1", + } + result.append(converted) + return result - -@app.post("/v1-to-v2/item-to-list") -def handle_v1_item_to_v2_list(data: Item) -> list[NewItem]: - converted = NewItem( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=NewSubItem(new_sub_name=data.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], - ) - return [converted, converted] - - -@app.post("/v1-to-v2/list-to-list") -def handle_v1_list_to_v2_list(data: list[Item]) -> list[NewItem]: - result = [] - for item in data: - result.append( - NewItem( + @app.post("/v1-to-v2/list-to-item") + def handle_v1_list_to_v2_item(data: list[Item]) -> NewItem: + if data: + item = data[0] + return NewItem( new_title=item.title, new_size=item.size, new_description=item.description, new_sub=NewSubItem(new_sub_name=item.sub.name), new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], ) + return NewItem(new_title="", new_size=0, new_sub=NewSubItem(new_sub_name="")) + + @app.post("/v2-to-v1/item-to-list") + def handle_v2_item_to_v1_list(data: NewItem) -> list[Item]: + converted = Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=SubItem(name=data.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], ) - return result + return [converted, converted] + @app.post("/v2-to-v1/list-to-list") + def handle_v2_list_to_v1_list(data: list[NewItem]) -> list[Item]: + result = [] + for item in data: + result.append( + Item( + title=item.new_title, + size=item.new_size, + description=item.new_description, + sub=SubItem(name=item.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], + ) + ) + return result -@app.post("/v1-to-v2/list-to-list-filter", response_model=list[NewItem]) -def handle_v1_list_to_v2_list_filter(data: list[Item]) -> Any: - result = [] - for item in data: - converted = { - "new_title": item.title, - "new_size": item.size, - "new_description": item.description, - "new_sub": {"new_sub_name": item.sub.name, "new_sub_secret": "sub_hidden"}, - "new_multi": [ - {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} - for s in item.multi - ], - "secret": "hidden_v2_to_v1", - } - result.append(converted) - return result + @app.post("/v2-to-v1/list-to-list-filter", response_model=list[Item]) + def handle_v2_list_to_v1_list_filter(data: list[NewItem]) -> Any: + result = [] + for item in data: + converted = { + "title": item.new_title, + "size": item.new_size, + "description": item.new_description, + "sub": { + "name": item.new_sub.new_sub_name, + "sub_secret": "sub_hidden", + }, + "multi": [ + {"name": s.new_sub_name, "sub_secret": "sub_hidden"} + for s in item.new_multi + ], + "secret": "hidden_v2_to_v1", + } + result.append(converted) + return result - -@app.post("/v1-to-v2/list-to-item") -def handle_v1_list_to_v2_item(data: list[Item]) -> NewItem: - if data: - item = data[0] - return NewItem( - new_title=item.title, - new_size=item.size, - new_description=item.description, - new_sub=NewSubItem(new_sub_name=item.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in item.multi], - ) - return NewItem(new_title="", new_size=0, new_sub=NewSubItem(new_sub_name="")) - - -@app.post("/v2-to-v1/item-to-list") -def handle_v2_item_to_v1_list(data: NewItem) -> list[Item]: - converted = Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=SubItem(name=data.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - return [converted, converted] - - -@app.post("/v2-to-v1/list-to-list") -def handle_v2_list_to_v1_list(data: list[NewItem]) -> list[Item]: - result = [] - for item in data: - result.append( - Item( + @app.post("/v2-to-v1/list-to-item") + def handle_v2_list_to_v1_item(data: list[NewItem]) -> Item: + if data: + item = data[0] + return Item( title=item.new_title, size=item.new_size, description=item.new_description, sub=SubItem(name=item.new_sub.new_sub_name), multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], ) - ) - return result - - -@app.post("/v2-to-v1/list-to-list-filter", response_model=list[Item]) -def handle_v2_list_to_v1_list_filter(data: list[NewItem]) -> Any: - result = [] - for item in data: - converted = { - "title": item.new_title, - "size": item.new_size, - "description": item.new_description, - "sub": {"name": item.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, - "multi": [ - {"name": s.new_sub_name, "sub_secret": "sub_hidden"} - for s in item.new_multi - ], - "secret": "hidden_v2_to_v1", - } - result.append(converted) - return result - - -@app.post("/v2-to-v1/list-to-item") -def handle_v2_list_to_v1_item(data: list[NewItem]) -> Item: - if data: - item = data[0] - return Item( - title=item.new_title, - size=item.new_size, - description=item.new_description, - sub=SubItem(name=item.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in item.new_multi], - ) - return Item(title="", size=0, sub=SubItem(name="")) + return Item(title="", size=0, sub=SubItem(name="")) client = TestClient(app) diff --git a/tests/test_pydantic_v1_v2_multifile/main.py b/tests/test_pydantic_v1_v2_multifile/main.py index 9397368abf..4180ec3bf5 100644 --- a/tests/test_pydantic_v1_v2_multifile/main.py +++ b/tests/test_pydantic_v1_v2_multifile/main.py @@ -1,140 +1,137 @@ +import warnings + from fastapi import FastAPI from . import modelsv1, modelsv2, modelsv2b app = FastAPI() +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") -@app.post("/v1-to-v2/item") -def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item: - return modelsv2.Item( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), - new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], - ) + @app.post("/v1-to-v2/item") + def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item: + return modelsv2.Item( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), + new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], + ) + @app.post("/v2-to-v1/item") + def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item: + return modelsv1.Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], + ) -@app.post("/v2-to-v1/item") -def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item: - return modelsv1.Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], - ) + @app.post("/v1-to-v2/item-to-list") + def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]: + converted = modelsv2.Item( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), + new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], + ) + return [converted, converted] + @app.post("/v1-to-v2/list-to-list") + def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]: + result = [] + for item in data: + result.append( + modelsv2.Item( + new_title=item.title, + new_size=item.size, + new_description=item.description, + new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), + new_multi=[ + modelsv2.SubItem(new_sub_name=s.name) for s in item.multi + ], + ) + ) + return result -@app.post("/v1-to-v2/item-to-list") -def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]: - converted = modelsv2.Item( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=modelsv2.SubItem(new_sub_name=data.sub.name), - new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi], - ) - return [converted, converted] - - -@app.post("/v1-to-v2/list-to-list") -def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]: - result = [] - for item in data: - result.append( - modelsv2.Item( + @app.post("/v1-to-v2/list-to-item") + def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item: + if data: + item = data[0] + return modelsv2.Item( new_title=item.title, new_size=item.size, new_description=item.description, new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi], ) - ) - return result - - -@app.post("/v1-to-v2/list-to-item") -def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item: - if data: - item = data[0] return modelsv2.Item( - new_title=item.title, - new_size=item.size, - new_description=item.description, - new_sub=modelsv2.SubItem(new_sub_name=item.sub.name), - new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi], + new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="") ) - return modelsv2.Item( - new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="") - ) + @app.post("/v2-to-v1/item-to-list") + def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]: + converted = modelsv1.Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], + ) + return [converted, converted] -@app.post("/v2-to-v1/item-to-list") -def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]: - converted = modelsv1.Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=modelsv1.SubItem(name=data.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - return [converted, converted] + @app.post("/v2-to-v1/list-to-list") + def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]: + result = [] + for item in data: + result.append( + modelsv1.Item( + title=item.new_title, + size=item.new_size, + description=item.new_description, + sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), + multi=[ + modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi + ], + ) + ) + return result - -@app.post("/v2-to-v1/list-to-list") -def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]: - result = [] - for item in data: - result.append( - modelsv1.Item( + @app.post("/v2-to-v1/list-to-item") + def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item: + if data: + item = data[0] + return modelsv1.Item( title=item.new_title, size=item.new_size, description=item.new_description, sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi], ) - ) - return result + return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name="")) - -@app.post("/v2-to-v1/list-to-item") -def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item: - if data: - item = data[0] + @app.post("/v2-to-v1/same-name") + def handle_v2_same_name_to_v1( + item1: modelsv2.Item, item2: modelsv2b.Item + ) -> modelsv1.Item: return modelsv1.Item( - title=item.new_title, - size=item.new_size, - description=item.new_description, - sub=modelsv1.SubItem(name=item.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi], + title=item1.new_title, + size=item2.dup_size, + description=item1.new_description, + sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name), + multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi], ) - return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name="")) - -@app.post("/v2-to-v1/same-name") -def handle_v2_same_name_to_v1( - item1: modelsv2.Item, item2: modelsv2b.Item -) -> modelsv1.Item: - return modelsv1.Item( - title=item1.new_title, - size=item2.dup_size, - description=item1.new_description, - sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name), - multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi], - ) - - -@app.post("/v2-to-v1/list-of-items-to-list-of-items") -def handle_v2_items_in_list_to_v1_item_in_list( - data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList] -) -> list[modelsv1.ItemInList]: - result = [] - item1 = data1[0] - item2 = data2[0] - result = [ - modelsv1.ItemInList(name1=item1.name2), - modelsv1.ItemInList(name1=item2.dup_name2), - ] - return result + @app.post("/v2-to-v1/list-of-items-to-list-of-items") + def handle_v2_items_in_list_to_v1_item_in_list( + data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList] + ) -> list[modelsv1.ItemInList]: + item1 = data1[0] + item2 = data2[0] + return [ + modelsv1.ItemInList(name1=item1.name2), + modelsv1.ItemInList(name1=item2.dup_name2), + ] diff --git a/tests/test_pydantic_v1_v2_noneable.py b/tests/test_pydantic_v1_v2_noneable.py index 2cb6c3d6b4..ba98b5653c 100644 --- a/tests/test_pydantic_v1_v2_noneable.py +++ b/tests/test_pydantic_v1_v2_noneable.py @@ -1,4 +1,5 @@ import sys +import warnings from typing import Any, Union from tests.utils import skip_module_if_py_gte_314 @@ -39,65 +40,69 @@ class NewItem(NewBaseModel): app = FastAPI() +with warnings.catch_warnings(record=True): + warnings.simplefilter("always") -@app.post("/v1-to-v2/") -def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]: - if data.size < 0: - return None - return NewItem( - new_title=data.title, - new_size=data.size, - new_description=data.description, - new_sub=NewSubItem(new_sub_name=data.sub.name), - new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], - ) + @app.post("/v1-to-v2/") + def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]: + if data.size < 0: + return None + return NewItem( + new_title=data.title, + new_size=data.size, + new_description=data.description, + new_sub=NewSubItem(new_sub_name=data.sub.name), + new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], + ) + @app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None]) + def handle_v1_item_to_v2_filter(data: Item) -> Any: + if data.size < 0: + return None + result = { + "new_title": data.title, + "new_size": data.size, + "new_description": data.description, + "new_sub": { + "new_sub_name": data.sub.name, + "new_sub_secret": "sub_hidden", + }, + "new_multi": [ + {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} + for s in data.multi + ], + "secret": "hidden_v1_to_v2", + } + return result -@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None]) -def handle_v1_item_to_v2_filter(data: Item) -> Any: - if data.size < 0: - return None - result = { - "new_title": data.title, - "new_size": data.size, - "new_description": data.description, - "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"}, - "new_multi": [ - {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi - ], - "secret": "hidden_v1_to_v2", - } - return result + @app.post("/v2-to-v1/item") + def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]: + if data.new_size < 0: + return None + return Item( + title=data.new_title, + size=data.new_size, + description=data.new_description, + sub=SubItem(name=data.new_sub.new_sub_name), + multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], + ) - -@app.post("/v2-to-v1/item") -def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]: - if data.new_size < 0: - return None - return Item( - title=data.new_title, - size=data.new_size, - description=data.new_description, - sub=SubItem(name=data.new_sub.new_sub_name), - multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], - ) - - -@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None]) -def handle_v2_item_to_v1_filter(data: NewItem) -> Any: - if data.new_size < 0: - return None - result = { - "title": data.new_title, - "size": data.new_size, - "description": data.new_description, - "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, - "multi": [ - {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi - ], - "secret": "hidden_v2_to_v1", - } - return result + @app.post("/v2-to-v1/item-filter", response_model=Union[Item, None]) + def handle_v2_item_to_v1_filter(data: NewItem) -> Any: + if data.new_size < 0: + return None + result = { + "title": data.new_title, + "size": data.new_size, + "description": data.new_description, + "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, + "multi": [ + {"name": s.new_sub_name, "sub_secret": "sub_hidden"} + for s in data.new_multi + ], + "secret": "hidden_v2_to_v1", + } + return result client = TestClient(app) diff --git a/tests/test_read_with_orm_mode.py b/tests/test_read_with_orm_mode.py index 5858f8e801..a195634b8a 100644 --- a/tests/test_read_with_orm_mode.py +++ b/tests/test_read_with_orm_mode.py @@ -1,3 +1,4 @@ +import warnings from typing import Any from fastapi import FastAPI @@ -73,10 +74,13 @@ def test_read_with_orm_mode_pv1() -> None: app = FastAPI() - @app.post("/people/", response_model=PersonRead) - def create_person(person: PersonCreate) -> Any: - db_person = Person.from_orm(person) - return db_person + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + + @app.post("/people/", response_model=PersonRead) + def create_person(person: PersonCreate) -> Any: + db_person = Person.from_orm(person) + return db_person client = TestClient(app) diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index 44e882a76e..9e527d6a01 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -1,3 +1,4 @@ +import warnings from typing import Union import pytest @@ -521,11 +522,14 @@ def test_invalid_response_model_field_pv1(): class Model(v1.BaseModel): foo: str - with pytest.raises(FastAPIError) as e: + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") - @app.get("/") - def read_root() -> Union[Response, Model, None]: - return Response(content="Foo") # pragma: no cover + with pytest.raises(FastAPIError) as e: + + @app.get("/") + def read_root() -> Union[Response, Model, None]: + return Response(content="Foo") # pragma: no cover assert "valid Pydantic field type" in e.value.args[0] assert "parameter response_model=None" in e.value.args[0] diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py index 266d25944d..ab7e1d8a77 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -1,4 +1,5 @@ import sys +import warnings import pytest from inline_snapshot import snapshot @@ -24,7 +25,13 @@ from ...utils import needs_py310 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + "ignore", + message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", + category=DeprecationWarning, + ) + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") c = TestClient(mod.app) return c diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py index 693c3ba290..c45e042484 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -1,4 +1,5 @@ import sys +import warnings import pytest from inline_snapshot import snapshot @@ -24,7 +25,13 @@ from ...utils import needs_py310 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + "ignore", + message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", + category=DeprecationWarning, + ) + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") c = TestClient(mod.app) return c diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py index 0fd084c84b..f3da849e04 100644 --- a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -1,4 +1,5 @@ import sys +import warnings import pytest from inline_snapshot import snapshot @@ -24,7 +25,13 @@ from ...utils import needs_py310 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + "ignore", + message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", + category=DeprecationWarning, + ) + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") c = TestClient(mod.app) return c diff --git a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py index 9ab30086bb..515a5a8d78 100644 --- a/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py +++ b/tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py @@ -1,4 +1,5 @@ import importlib +import warnings import pytest from fastapi.testclient import TestClient @@ -14,7 +15,13 @@ from ...utils import needs_pydanticv1 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.request_form_models.{request.param}") + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + "ignore", + message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", + category=DeprecationWarning, + ) + mod = importlib.import_module(f"docs_src.request_form_models.{request.param}") client = TestClient(mod.app) return client diff --git a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py index 6059962897..c5526b19cd 100644 --- a/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py +++ b/tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py @@ -1,4 +1,5 @@ import importlib +import warnings import pytest from fastapi.testclient import TestClient @@ -15,7 +16,13 @@ from ...utils import needs_py310, needs_pydanticv1 ], ) def get_client(request: pytest.FixtureRequest): - mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + "ignore", + message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*", + category=DeprecationWarning, + ) + mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}") client = TestClient(mod.app) return client From 22c7200ebb9a7cfb3f938985fba55bd557faa4ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Dec 2025 16:44:32 +0000 Subject: [PATCH 135/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 12267e3b10..ed0592468c 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Breaking Changes + +* 🔊 Add deprecation warnings when using `pydantic.v1`. PR [#14583](https://github.com/fastapi/fastapi/pull/14583) by [@tiangolo](https://github.com/tiangolo). + ### Translations * 🔧 Add LLM prompt file for Korean, generated from the existing translations. PR [#14546](https://github.com/fastapi/fastapi/pull/14546) by [@tiangolo](https://github.com/tiangolo). From c4a1ab503635918938e3741d1fb6f2fff73dc1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 21 Dec 2025 17:45:43 +0100 Subject: [PATCH 136/140] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.12?= =?UTF-8?q?7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/en/docs/release-notes.md | 2 ++ fastapi/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index ed0592468c..f22b5dc95b 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,8 @@ hide: ## Latest Changes +## 0.127.0 + ### Breaking Changes * 🔊 Add deprecation warnings when using `pydantic.v1`. PR [#14583](https://github.com/fastapi/fastapi/pull/14583) by [@tiangolo](https://github.com/tiangolo). diff --git a/fastapi/__init__.py b/fastapi/__init__.py index 7ed0fa95bb..73df6dc6c9 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """FastAPI framework, high performance, easy to learn, fast to code, ready for production""" -__version__ = "0.126.0" +__version__ = "0.127.0" from starlette import status as status From b9b2793bda6898295593302b1213b4a61b91dd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 21 Dec 2025 09:40:17 -0800 Subject: [PATCH 137/140] =?UTF-8?q?=F0=9F=94=A8=20Update=20scripts=20and?= =?UTF-8?q?=20pre-commit=20to=20autofix=20files=20(#14585)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-docs.yml | 2 - .pre-commit-config.yaml | 22 +++++++- scripts/docs.py | 94 ++++++++++++-------------------- 3 files changed, 54 insertions(+), 64 deletions(-) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 73e1c6b67a..cd27179f57 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -60,8 +60,6 @@ jobs: pyproject.toml - name: Install docs extras run: uv pip install -r requirements-docs.txt - - name: Verify Docs - run: python ./scripts/docs.py verify-docs - name: Export Language Codes id: show-langs run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a65d97dad3..77e06bd96c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,10 +21,28 @@ repos: - id: ruff-format - repo: local hooks: - - id: local-script + - id: add-permalinks-pages language: unsupported - name: local script + name: add-permalinks-pages entry: uv run ./scripts/docs.py add-permalinks-pages args: - --update-existing files: ^docs/en/docs/.*\.md$ + - id: generate-readme + language: unsupported + name: generate README.md from index.md + entry: uv run ./scripts/docs.py generate-readme + files: ^docs/en/docs/index\.md|docs/en/data/sponsors\.yml|scripts/docs\.py$ + pass_filenames: false + - id: update-languages + language: unsupported + name: update languages + entry: uv run ./scripts/docs.py update-languages + files: ^docs/.*|scripts/docs\.py$ + pass_filenames: false + - id: ensure-non-translated + language: unsupported + name: ensure non-translated files are not modified + entry: uv run ./scripts/docs.py ensure-non-translated + files: ^docs/(?!en/).*|^scripts/docs\.py$ + pass_filenames: false diff --git a/scripts/docs.py b/scripts/docs.py index bf7d9de395..fbde1eca4f 100644 --- a/scripts/docs.py +++ b/scripts/docs.py @@ -19,7 +19,13 @@ from slugify import slugify as py_slugify logging.basicConfig(level=logging.INFO) -SUPPORTED_LANGS = {"en", "de", "es", "pt", "ru"} +SUPPORTED_LANGS = { + "en", + "de", + "es", + "pt", + "ru", +} app = typer.Typer() @@ -232,27 +238,15 @@ def generate_readme() -> None: """ Generate README.md content from main index.md """ - typer.echo("Generating README") readme_path = Path("README.md") + old_content = readme_path.read_text() new_content = generate_readme_content() - readme_path.write_text(new_content, encoding="utf-8") - - -@app.command() -def verify_readme() -> None: - """ - Verify README.md content from main index.md - """ - typer.echo("Verifying README") - readme_path = Path("README.md") - generated_content = generate_readme_content() - readme_content = readme_path.read_text("utf-8") - if generated_content != readme_content: - typer.secho( - "README.md outdated from the latest index.md", color=typer.colors.RED - ) - raise typer.Abort() - typer.echo("Valid README ✅") + if new_content != old_content: + print("README.md outdated from the latest index.md") + print("Updating README.md") + readme_path.write_text(new_content, encoding="utf-8") + raise typer.Exit(1) + print("README.md is up to date ✅") @app.command() @@ -280,7 +274,17 @@ def update_languages() -> None: """ Update the mkdocs.yml file Languages section including all the available languages. """ - update_config() + old_config = get_en_config() + updated_config = get_updated_config_content() + if old_config != updated_config: + print("docs/en/mkdocs.yml outdated") + print("Updating docs/en/mkdocs.yml") + en_config_path.write_text( + yaml.dump(updated_config, sort_keys=False, width=200, allow_unicode=True), + encoding="utf-8", + ) + raise typer.Exit(1) + print("docs/en/mkdocs.yml is up to date ✅") @app.command() @@ -367,39 +371,12 @@ def get_updated_config_content() -> dict[str, Any]: return config -def update_config() -> None: - config = get_updated_config_content() - en_config_path.write_text( - yaml.dump(config, sort_keys=False, width=200, allow_unicode=True), - encoding="utf-8", - ) - - @app.command() -def verify_config() -> None: +def ensure_non_translated() -> None: """ - Verify main mkdocs.yml content to make sure it uses the latest language names. + Ensure there are no files in the non translatable pages. """ - typer.echo("Verifying mkdocs.yml") - config = get_en_config() - updated_config = get_updated_config_content() - if config != updated_config: - typer.secho( - "docs/en/mkdocs.yml outdated from docs/language_names.yml, " - "update language_names.yml and run " - "python ./scripts/docs.py update-languages", - color=typer.colors.RED, - ) - raise typer.Abort() - typer.echo("Valid mkdocs.yml ✅") - - -@app.command() -def verify_non_translated() -> None: - """ - Verify there are no files in the non translatable pages. - """ - print("Verifying non translated pages") + print("Ensuring no non translated pages") lang_paths = get_lang_paths() error_paths = [] for lang in lang_paths: @@ -410,20 +387,17 @@ def verify_non_translated() -> None: if non_translatable_path.exists(): error_paths.append(non_translatable_path) if error_paths: - print("Non-translated pages found, remove them:") + print("Non-translated pages found, removing them:") for error_path in error_paths: print(error_path) - raise typer.Abort() + if error_path.is_file(): + error_path.unlink() + else: + shutil.rmtree(error_path) + raise typer.Exit(1) print("No non-translated pages found ✅") -@app.command() -def verify_docs(): - verify_readme() - verify_config() - verify_non_translated() - - @app.command() def langs_json(): langs = [] From e1bd9f3e33073f8b07bf69bf89f34b230130447a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Dec 2025 17:40:41 +0000 Subject: [PATCH 138/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index f22b5dc95b..4316e53bf1 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,6 +7,10 @@ hide: ## Latest Changes +### Internal + +* 🔨 Update scripts and pre-commit to autofix files. PR [#14585](https://github.com/fastapi/fastapi/pull/14585) by [@tiangolo](https://github.com/tiangolo). + ## 0.127.0 ### Breaking Changes From 6539b80d9f798af4143d772b7962218861e4d1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 21 Dec 2025 09:51:45 -0800 Subject: [PATCH 139/140] =?UTF-8?q?=F0=9F=91=B7=20Run=20CodSpeed=20tests?= =?UTF-8?q?=20in=20parallel=20to=20other=20tests=20to=20speed=20up=20CI=20?= =?UTF-8?q?(#14586)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb2b6b64ee..5a12d69c8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,10 +54,14 @@ jobs: - os: windows-latest python-version: "3.12" coverage: coverage - # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks - os: ubuntu-latest python-version: "3.13" coverage: coverage + # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks + - os: ubuntu-latest + python-version: "3.13" + coverage: coverage + codspeed: codspeed - os: ubuntu-latest python-version: "3.14" coverage: coverage @@ -85,12 +89,13 @@ jobs: run: uv pip install -r requirements-tests.txt - run: mkdir coverage - name: Test + if: matrix.codspeed != 'codspeed' run: bash scripts/test.sh env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }} - name: CodSpeed benchmarks - if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' + if: matrix.codspeed == 'codspeed' uses: CodSpeedHQ/action@v4 env: COVERAGE_FILE: coverage/.coverage.${{ runner.os }}-py${{ matrix.python-version }} From a7a0aee984fb36f6f16978147fb6a87115f0f4f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Dec 2025 17:52:08 +0000 Subject: [PATCH 140/140] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] --- docs/en/docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 4316e53bf1..a08ac1cd83 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -9,6 +9,7 @@ hide: ### Internal +* 👷 Run CodSpeed tests in parallel to other tests to speed up CI. PR [#14586](https://github.com/fastapi/fastapi/pull/14586) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update scripts and pre-commit to autofix files. PR [#14585](https://github.com/fastapi/fastapi/pull/14585) by [@tiangolo](https://github.com/tiangolo). ## 0.127.0