From faee822574d3c202123f48eb09b9bd207385335b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 17 Feb 2026 01:59:14 -0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Upgrade=20typing=20syntax=20for?= =?UTF-8?q?=20Python=203.10=20(#14932)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: tiangolo <1326112+tiangolo@users.noreply.github.com> Co-authored-by: github-actions[bot] --- docs_src/extra_models/tutorial003_py310.py | 4 +--- scripts/doc_parsing_utils.py | 22 ++++++++--------- scripts/mkdocs_hooks.py | 8 +++---- scripts/notify_translations.py | 24 +++++++++---------- scripts/people.py | 12 +++++----- tests/main.py | 5 ++-- tests/test_additional_properties_bool.py | 4 +--- ...itional_responses_union_duplicate_anyof.py | 4 +--- tests/test_callable_endpoint.py | 3 +-- tests/test_compat.py | 24 +++++++++---------- tests/test_custom_middleware_exception.py | 3 +-- tests/test_custom_schema_fields.py | 8 +++---- tests/test_dependency_contextvars.py | 6 ++--- tests/test_dependency_overrides.py | 4 +--- tests/test_dependency_paramless.py | 4 ++-- tests/test_enforce_once_required_parameter.py | 6 ++--- tests/test_extra_routes.py | 4 +--- tests/test_filter_pydantic_sub_model_pv2.py | 4 +--- tests/test_form_default.py | 8 +++---- tests/test_forms_single_model.py | 4 ++-- tests/test_infer_param_optionality.py | 6 ++--- tests/test_invalid_sequence_param.py | 4 +--- tests/test_jsonable_encoder.py | 4 ++-- tests/test_openapi_examples.py | 8 +++---- .../test_openapi_query_parameter_extension.py | 4 +--- tests/test_openapi_schema_type.py | 4 +--- ...t_openapi_separate_input_output_schemas.py | 8 +++---- tests/test_optional_file_list.py | 4 +--- tests/test_param_class.py | 4 +--- tests/test_param_include_in_schema.py | 8 +++---- tests/test_pydantic_v1_error.py | 3 +-- ...ataclasses_uuid_stringified_annotations.py | 5 ++-- .../test_body/test_list.py | 8 +++---- .../test_body/test_optional_list.py | 20 +++++++--------- .../test_body/test_optional_str.py | 20 +++++++--------- .../test_body/test_required_str.py | 12 ++++------ .../test_cookie/test_optional_str.py | 18 +++++++------- .../test_file/test_optional.py | 18 +++++++------- .../test_file/test_optional_list.py | 20 +++++++--------- .../test_form/test_optional_list.py | 20 +++++++--------- .../test_form/test_optional_str.py | 18 +++++++------- .../test_header/test_optional_list.py | 20 +++++++--------- .../test_header/test_optional_str.py | 18 +++++++------- .../test_query/test_optional_list.py | 20 +++++++--------- .../test_query/test_optional_str.py | 18 +++++++------- tests/test_required_noneable.py | 8 +++---- ...est_response_model_as_return_annotation.py | 12 ++++------ tests/test_router_events.py | 5 ++-- tests/test_schema_extra_examples.py | 20 +++++++--------- .../test_security_api_key_cookie_optional.py | 4 +--- .../test_security_api_key_header_optional.py | 6 ++--- tests/test_security_api_key_query_optional.py | 6 ++--- tests/test_security_http_base_optional.py | 4 +--- tests/test_security_http_basic_optional.py | 3 +-- tests/test_security_http_bearer_optional.py | 4 +--- tests/test_security_http_digest_optional.py | 4 +--- ...curity_oauth2_authorization_code_bearer.py | 4 +--- ...2_authorization_code_bearer_description.py | 4 +--- ...uthorization_code_bearer_scopes_openapi.py | 6 ++--- tests/test_security_oauth2_optional.py | 6 ++--- ...st_security_oauth2_optional_description.py | 6 ++--- ...ecurity_oauth2_password_bearer_optional.py | 4 +--- ...h2_password_bearer_optional_description.py | 4 +--- .../test_security_openid_connect_optional.py | 6 ++--- tests/test_serialize_response.py | 6 ++--- tests/test_serialize_response_dataclass.py | 5 ++-- tests/test_serialize_response_model.py | 6 ++--- tests/test_skip_defaults.py | 12 ++++------ tests/test_sub_callbacks.py | 6 ++--- tests/test_union_body.py | 6 ++--- tests/test_union_body_discriminator.py | 5 ++-- ...test_union_body_discriminator_annotated.py | 4 ++-- tests/test_union_forms.py | 4 ++-- tests/test_union_inherited_body.py | 6 ++--- tests/test_validate_response.py | 8 +++---- tests/test_validate_response_dataclass.py | 6 ++--- tests/utils.py | 1 - 77 files changed, 269 insertions(+), 375 deletions(-) diff --git a/docs_src/extra_models/tutorial003_py310.py b/docs_src/extra_models/tutorial003_py310.py index 06675cbc0..8fe6f7136 100644 --- a/docs_src/extra_models/tutorial003_py310.py +++ b/docs_src/extra_models/tutorial003_py310.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI from pydantic import BaseModel @@ -30,6 +28,6 @@ items = { } -@app.get("/items/{item_id}", response_model=Union[PlaneItem, CarItem]) +@app.get("/items/{item_id}", response_model=PlaneItem | CarItem) async def read_item(item_id: str): return items[item_id] diff --git a/scripts/doc_parsing_utils.py b/scripts/doc_parsing_utils.py index 79f2e9ec0..1cd2299e6 100644 --- a/scripts/doc_parsing_utils.py +++ b/scripts/doc_parsing_utils.py @@ -1,5 +1,5 @@ import re -from typing import TypedDict, Union +from typing import TypedDict CODE_INCLUDE_RE = re.compile(r"^\{\*\s*(\S+)\s*(.*)\*\}$") CODE_INCLUDE_PLACEHOLDER = "" @@ -50,8 +50,8 @@ class MarkdownLinkInfo(TypedDict): line_no: int url: str text: str - title: Union[str, None] - attributes: Union[str, None] + title: str | None + attributes: str | None full_match: str @@ -287,8 +287,8 @@ def _add_lang_code_to_url(url: str, lang_code: str) -> str: def _construct_markdown_link( url: str, text: str, - title: Union[str, None], - attributes: Union[str, None], + title: str | None, + attributes: str | None, lang_code: str, ) -> str: """ @@ -549,7 +549,7 @@ def extract_multiline_code_blocks(text: list[str]) -> list[MultilineCodeBlockInf return blocks -def _split_hash_comment(line: str) -> tuple[str, Union[str, None]]: +def _split_hash_comment(line: str) -> tuple[str, str | None]: match = HASH_COMMENT_RE.match(line) if match: code = match.group("code").rstrip() @@ -558,7 +558,7 @@ def _split_hash_comment(line: str) -> tuple[str, Union[str, None]]: return line.rstrip(), None -def _split_slashes_comment(line: str) -> tuple[str, Union[str, None]]: +def _split_slashes_comment(line: str) -> tuple[str, str | None]: match = SLASHES_COMMENT_RE.match(line) if match: code = match.group("code").rstrip() @@ -603,9 +603,9 @@ def replace_multiline_code_block( return block_a["content"].copy() # We don't handle mermaid code blocks for now code_block: list[str] = [] - for line_a, line_b in zip(block_a["content"], block_b["content"]): - line_a_comment: Union[str, None] = None - line_b_comment: Union[str, None] = None + for line_a, line_b in zip(block_a["content"], block_b["content"], strict=False): + line_a_comment: str | None = None + line_b_comment: str | None = None # Handle comments based on language if block_language in { @@ -659,7 +659,7 @@ def replace_multiline_code_blocks_in_text( ) modified_text = text.copy() - for block, original_block in zip(code_blocks, original_code_blocks): + for block, original_block in zip(code_blocks, original_code_blocks, strict=True): updated_content = replace_multiline_code_block(block, original_block) start_line_index = block["start_line_no"] - 1 diff --git a/scripts/mkdocs_hooks.py b/scripts/mkdocs_hooks.py index 567c0111d..97006953d 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, Union +from typing import Any import material from mkdocs.config.defaults import MkDocsConfig @@ -105,9 +105,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[Page | Section | Link], *, config: MkDocsConfig +) -> list[Page | Section | Link]: + new_items: list[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 74cdf0dff..3484b69c7 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, Union, cast +from typing import Any, cast import httpx from github import Github @@ -181,9 +181,9 @@ class Settings(BaseSettings): github_repository: str github_token: SecretStr github_event_path: Path - github_event_name: Union[str, None] = None + github_event_name: str | None = None httpx_timeout: int = 30 - debug: Union[bool, None] = False + debug: bool | None = False number: int | None = None @@ -199,12 +199,12 @@ def get_graphql_response( *, settings: Settings, query: str, - after: Union[str, None] = None, - category_id: Union[str, None] = None, - discussion_number: Union[int, None] = None, - discussion_id: Union[str, None] = None, - comment_id: Union[str, None] = None, - body: Union[str, None] = None, + after: str | None = None, + category_id: str | None = None, + discussion_number: int | None = None, + discussion_id: str | None = None, + comment_id: str | None = None, + body: str | None = None, ) -> dict[str, Any]: headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = { @@ -249,7 +249,7 @@ def get_graphql_translation_discussions( def get_graphql_translation_discussion_comments_edges( - *, settings: Settings, discussion_number: int, after: Union[str, None] = None + *, settings: Settings, discussion_number: int, after: str | None = None ) -> list[CommentsEdge]: data = get_graphql_response( settings=settings, @@ -372,8 +372,8 @@ def main() -> None: f"Found a translation discussion for language: {lang} in discussion: #{discussion.number}" ) - already_notified_comment: Union[Comment, None] = None - already_done_comment: Union[Comment, None] = None + already_notified_comment: Comment | None = None + already_done_comment: Comment | None = None logging.info( f"Checking current comments in discussion: #{discussion.number} to see if already notified about this PR: #{pr.number}" diff --git a/scripts/people.py b/scripts/people.py index 207ab4649..f3254ab60 100644 --- a/scripts/people.py +++ b/scripts/people.py @@ -6,7 +6,7 @@ from collections import Counter from collections.abc import Container from datetime import datetime, timedelta, timezone from pathlib import Path -from typing import Any, Union +from typing import Any import httpx import yaml @@ -70,7 +70,7 @@ class Author(BaseModel): class CommentsNode(BaseModel): createdAt: datetime - author: Union[Author, None] = None + author: Author | None = None class Replies(BaseModel): @@ -89,7 +89,7 @@ class DiscussionsComments(BaseModel): class DiscussionsNode(BaseModel): number: int - author: Union[Author, None] = None + author: Author | None = None title: str | None = None createdAt: datetime comments: DiscussionsComments @@ -127,8 +127,8 @@ def get_graphql_response( *, settings: Settings, query: str, - after: Union[str, None] = None, - category_id: Union[str, None] = None, + after: str | None = None, + category_id: str | None = None, ) -> dict[str, Any]: headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"} variables = {"after": after, "category_id": category_id} @@ -156,7 +156,7 @@ def get_graphql_response( def get_graphql_question_discussion_edges( *, settings: Settings, - after: Union[str, None] = None, + after: str | None = None, ) -> list[DiscussionsEdge]: data = get_graphql_response( settings=settings, diff --git a/tests/main.py b/tests/main.py index 7edb16c61..d2fbbe615 100644 --- a/tests/main.py +++ b/tests/main.py @@ -1,5 +1,4 @@ import http -from typing import Optional from fastapi import FastAPI, Path, Query @@ -54,7 +53,7 @@ def get_bool_id(item_id: bool): @app.get("/path/param/{item_id}") -def get_path_param_id(item_id: Optional[str] = Path()): +def get_path_param_id(item_id: str | None = Path()): return item_id @@ -161,7 +160,7 @@ def get_query_type(query: int): @app.get("/query/int/optional") -def get_query_type_optional(query: Optional[int] = None): +def get_query_type_optional(query: int | None = None): if query is None: return "foo bar" return f"foo bar {query}" diff --git a/tests/test_additional_properties_bool.py b/tests/test_additional_properties_bool.py index c02841cde..9a1e139ea 100644 --- a/tests/test_additional_properties_bool.py +++ b/tests/test_additional_properties_bool.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -19,7 +17,7 @@ app = FastAPI() @app.post("/") async def post( - foo: Union[Foo, None] = None, + foo: Foo | None = None, ): return foo diff --git a/tests/test_additional_responses_union_duplicate_anyof.py b/tests/test_additional_responses_union_duplicate_anyof.py index 5d833fce4..401bc0a74 100644 --- a/tests/test_additional_responses_union_duplicate_anyof.py +++ b/tests/test_additional_responses_union_duplicate_anyof.py @@ -4,8 +4,6 @@ don't accumulate duplicate $ref entries in anyOf arrays. See https://github.com/fastapi/fastapi/pull/14463 """ -from typing import Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -23,7 +21,7 @@ class ModelB(BaseModel): app = FastAPI( responses={ 500: { - "model": Union[ModelA, ModelB], + "model": ModelA | ModelB, "content": {"application/json": {"examples": {"Case A": {"value": "a"}}}}, } } diff --git a/tests/test_callable_endpoint.py b/tests/test_callable_endpoint.py index 1882e9053..28999d383 100644 --- a/tests/test_callable_endpoint.py +++ b/tests/test_callable_endpoint.py @@ -1,11 +1,10 @@ from functools import partial -from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient -def main(some_arg, q: Optional[str] = None): +def main(some_arg, q: str | None = None): return {"some_arg": some_arg, "q": q} diff --git a/tests/test_compat.py b/tests/test_compat.py index 0b5600f8f..772bd305e 100644 --- a/tests/test_compat.py +++ b/tests/test_compat.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import FastAPI, UploadFile from fastapi._compat import ( Undefined, @@ -10,8 +8,6 @@ from fastapi.testclient import TestClient from pydantic import BaseModel, ConfigDict from pydantic.fields import FieldInfo -from .utils import needs_py310 - def test_model_field_default_required(): from fastapi._compat import v2 @@ -26,7 +22,7 @@ def test_complex(): app = FastAPI() @app.post("/") - def foo(foo: Union[str, list[int]]): + def foo(foo: str | list[int]): return foo client = TestClient(app) @@ -49,17 +45,17 @@ def test_propagates_pydantic2_model_config(): class EmbeddedModel(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - value: Union[str, Missing] = Missing() + value: str | Missing = Missing() class Model(BaseModel): model_config = ConfigDict( arbitrary_types_allowed=True, ) - value: Union[str, Missing] = Missing() + value: str | Missing = Missing() embedded_model: EmbeddedModel = EmbeddedModel() @app.post("/") - def foo(req: Model) -> dict[str, Union[str, None]]: + def foo(req: Model) -> dict[str, str | None]: return { "value": req.value or None, "embedded_value": req.embedded_model.value or None, @@ -89,7 +85,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(list[str] | list[bytes]) def test_is_uploadfile_sequence_annotation(): @@ -97,21 +93,20 @@ 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(list[str] | list[UploadFile]) 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=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"] assert isinstance(result, list) -@needs_py310 def test_serialize_sequence_value_with_optional_list_pipe_union(): """Test that serialize_sequence_value handles optional lists correctly (with new syntax).""" from fastapi._compat import v2 @@ -125,9 +120,12 @@ def test_serialize_sequence_value_with_optional_list_pipe_union(): def test_serialize_sequence_value_with_none_first_in_union(): """Test that serialize_sequence_value handles Union[None, List[...]] correctly.""" + from typing import Union + from fastapi._compat import v2 - field_info = FieldInfo(annotation=Union[None, list[str]]) + # Use Union[None, list[str]] to ensure None comes first in the union args + field_info = FieldInfo(annotation=Union[None, list[str]]) # noqa: UP007 field = v2.ModelField(name="items", field_info=field_info) result = v2.serialize_sequence_value(field=field, value=["x", "y"]) assert result == ["x", "y"] diff --git a/tests/test_custom_middleware_exception.py b/tests/test_custom_middleware_exception.py index d9b81e7c2..cf548f4ae 100644 --- a/tests/test_custom_middleware_exception.py +++ b/tests/test_custom_middleware_exception.py @@ -1,5 +1,4 @@ from pathlib import Path -from typing import Optional from fastapi import APIRouter, FastAPI, File, UploadFile from fastapi.exceptions import HTTPException @@ -17,7 +16,7 @@ class ContentSizeLimitMiddleware: max_content_size (optional): the maximum content size allowed in bytes, None for no limit """ - def __init__(self, app: APIRouter, max_content_size: Optional[int] = None): + def __init__(self, app: APIRouter, max_content_size: int | None = None): self.app = app self.max_content_size = max_content_size diff --git a/tests/test_custom_schema_fields.py b/tests/test_custom_schema_fields.py index 60b795e9b..c907c5424 100644 --- a/tests/test_custom_schema_fields.py +++ b/tests/test_custom_schema_fields.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from fastapi import FastAPI from fastapi.testclient import TestClient @@ -10,9 +10,9 @@ app = FastAPI() class Item(BaseModel): name: str - description: Annotated[ - Optional[str], WithJsonSchema({"type": ["string", "null"]}) - ] = None + description: Annotated[str | None, WithJsonSchema({"type": ["string", "null"]})] = ( + None + ) model_config = { "json_schema_extra": { diff --git a/tests/test_dependency_contextvars.py b/tests/test_dependency_contextvars.py index 0c2e5594b..eba135785 100644 --- a/tests/test_dependency_contextvars.py +++ b/tests/test_dependency_contextvars.py @@ -1,11 +1,11 @@ -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from contextvars import ContextVar -from typing import Any, Callable, Optional +from typing import Any 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[dict[str, Any] | None] = ContextVar( "legacy_request_state_context_var", default=None ) diff --git a/tests/test_dependency_overrides.py b/tests/test_dependency_overrides.py index e25db624d..7c99d9d9d 100644 --- a/tests/test_dependency_overrides.py +++ b/tests/test_dependency_overrides.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import APIRouter, Depends, FastAPI from fastapi.testclient import TestClient @@ -38,7 +36,7 @@ app.include_router(router) client = TestClient(app) -async def overrider_dependency_simple(q: Optional[str] = None): +async def overrider_dependency_simple(q: str | None = None): return {"q": q, "skip": 5, "limit": 10} diff --git a/tests/test_dependency_paramless.py b/tests/test_dependency_paramless.py index 1774196fe..62c977b82 100644 --- a/tests/test_dependency_paramless.py +++ b/tests/test_dependency_paramless.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated from fastapi import FastAPI, HTTPException, Security from fastapi.security import ( @@ -13,7 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def process_auth( - credentials: Annotated[Union[str, None], Security(oauth2_scheme)], + credentials: Annotated[str | None, Security(oauth2_scheme)], security_scopes: SecurityScopes, ): # This is an incorrect way of using it, this is not checking if the scopes are diff --git a/tests/test_enforce_once_required_parameter.py b/tests/test_enforce_once_required_parameter.py index 0dee15c25..9f8a96454 100644 --- a/tests/test_enforce_once_required_parameter.py +++ b/tests/test_enforce_once_required_parameter.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Query from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -11,7 +9,7 @@ def _get_client_key(client_id: str = Query(...)) -> str: return f"{client_id}_key" -def _get_client_tag(client_id: Optional[str] = Query(None)) -> Optional[str]: +def _get_client_tag(client_id: str | None = Query(None)) -> str | None: if client_id is None: return None return f"{client_id}_tag" @@ -20,7 +18,7 @@ def _get_client_tag(client_id: Optional[str] = Query(None)) -> Optional[str]: @app.get("/foo") def foo_handler( client_key: str = Depends(_get_client_key), - client_tag: Optional[str] = Depends(_get_client_tag), + client_tag: str | None = Depends(_get_client_tag), ): return {"client_id": client_key, "client_tag": client_tag} diff --git a/tests/test_extra_routes.py b/tests/test_extra_routes.py index 96f85b446..985adb943 100644 --- a/tests/test_extra_routes.py +++ b/tests/test_extra_routes.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.responses import JSONResponse from fastapi.testclient import TestClient @@ -11,7 +9,7 @@ app = FastAPI() class Item(BaseModel): name: str - price: Optional[float] = None + price: float | None = None @app.api_route("/items/{item_id}", methods=["GET"]) diff --git a/tests/test_filter_pydantic_sub_model_pv2.py b/tests/test_filter_pydantic_sub_model_pv2.py index 1de2b50f7..1f39581c2 100644 --- a/tests/test_filter_pydantic_sub_model_pv2.py +++ b/tests/test_filter_pydantic_sub_model_pv2.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from dirty_equals import HasRepr from fastapi import Depends, FastAPI @@ -22,7 +20,7 @@ def get_client(): class ModelA(BaseModel): name: str - description: Optional[str] = None + description: str | None = None foo: ModelB tags: dict[str, str] = {} diff --git a/tests/test_form_default.py b/tests/test_form_default.py index 0b3eb8f2e..c4d33e3fb 100644 --- a/tests/test_form_default.py +++ b/tests/test_form_default.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from fastapi import FastAPI, File, Form from starlette.testclient import TestClient @@ -7,14 +7,14 @@ app = FastAPI() @app.post("/urlencoded") -async def post_url_encoded(age: Annotated[Optional[int], Form()] = None): +async def post_url_encoded(age: Annotated[int | None, Form()] = None): return age @app.post("/multipart") async def post_multi_part( - age: Annotated[Optional[int], Form()] = None, - file: Annotated[Optional[bytes], File()] = None, + age: Annotated[int | None, Form()] = None, + file: Annotated[bytes | None, File()] = None, ): return {"file": file, "age": age} diff --git a/tests/test_forms_single_model.py b/tests/test_forms_single_model.py index 7d03d2957..4575e3335 100644 --- a/tests/test_forms_single_model.py +++ b/tests/test_forms_single_model.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated from fastapi import FastAPI, Form from fastapi.testclient import TestClient @@ -10,7 +10,7 @@ app = FastAPI() class FormModel(BaseModel): username: str lastname: str - age: Optional[int] = None + age: int | None = None tags: list[str] = ["foo", "bar"] alias_with: str = Field(alias="with", default="nothing") diff --git a/tests/test_infer_param_optionality.py b/tests/test_infer_param_optionality.py index bb20a4a1a..2cf74e187 100644 --- a/tests/test_infer_param_optionality.py +++ b/tests/test_infer_param_optionality.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -22,7 +20,7 @@ def get_user(user_id: str): @item_router.get("/") -def get_items(user_id: Optional[str] = None): +def get_items(user_id: str | None = None): if user_id is None: return [{"item_id": "i1", "user_id": "u1"}, {"item_id": "i2", "user_id": "u2"}] else: @@ -30,7 +28,7 @@ def get_items(user_id: Optional[str] = None): @item_router.get("/{item_id}") -def get_item(item_id: str, user_id: Optional[str] = None): +def get_item(item_id: str, user_id: str | None = None): if user_id is None: return {"item_id": item_id} else: diff --git a/tests/test_invalid_sequence_param.py b/tests/test_invalid_sequence_param.py index 3695344f7..d137f6805 100644 --- a/tests/test_invalid_sequence_param.py +++ b/tests/test_invalid_sequence_param.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import FastAPI, Query from pydantic import BaseModel @@ -61,5 +59,5 @@ def test_invalid_simple_dict(): title: str @app.get("/items/") - def read_items(q: Optional[dict] = Query(default=None)): + def read_items(q: dict | None = Query(default=None)): pass # pragma: no cover diff --git a/tests/test_jsonable_encoder.py b/tests/test_jsonable_encoder.py index 4528dff44..595202bea 100644 --- a/tests/test_jsonable_encoder.py +++ b/tests/test_jsonable_encoder.py @@ -6,7 +6,7 @@ from decimal import Decimal from enum import Enum from math import isinf, isnan from pathlib import PurePath, PurePosixPath, PureWindowsPath -from typing import Optional, TypedDict +from typing import TypedDict import pytest from fastapi._compat import Undefined @@ -57,7 +57,7 @@ class RoleEnum(Enum): class ModelWithConfig(BaseModel): - role: Optional[RoleEnum] = None + role: RoleEnum | None = None model_config = {"use_enum_values": True} diff --git a/tests/test_openapi_examples.py b/tests/test_openapi_examples.py index deb74d8a0..e27dd2be0 100644 --- a/tests/test_openapi_examples.py +++ b/tests/test_openapi_examples.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -57,7 +55,7 @@ def path_examples( @app.get("/query_examples/") def query_examples( - data: Union[str, None] = Query( + data: str | None = Query( default=None, examples=[ "json_schema_query1", @@ -80,7 +78,7 @@ def query_examples( @app.get("/header_examples/") def header_examples( - data: Union[str, None] = Header( + data: str | None = Header( default=None, examples=[ "json_schema_header1", @@ -103,7 +101,7 @@ def header_examples( @app.get("/cookie_examples/") def cookie_examples( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, examples=["json_schema_cookie1", "json_schema_cookie2"], openapi_examples={ diff --git a/tests/test_openapi_query_parameter_extension.py b/tests/test_openapi_query_parameter_extension.py index 836a0a7ee..118d51814 100644 --- a/tests/test_openapi_query_parameter_extension.py +++ b/tests/test_openapi_query_parameter_extension.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -26,7 +24,7 @@ app = FastAPI() ] }, ) -def route_with_extra_query_parameters(standard_query_param: Optional[int] = 50): +def route_with_extra_query_parameters(standard_query_param: int | None = 50): return {} diff --git a/tests/test_openapi_schema_type.py b/tests/test_openapi_schema_type.py index 98d978745..e8166d2fb 100644 --- a/tests/test_openapi_schema_type.py +++ b/tests/test_openapi_schema_type.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - import pytest from fastapi.openapi.models import Schema, SchemaType @@ -13,7 +11,7 @@ from fastapi.openapi.models import Schema, SchemaType ], ) def test_allowed_schema_type( - type_value: Optional[Union[SchemaType, list[SchemaType]]], + type_value: SchemaType | list[SchemaType] | None, ) -> 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 0efeece01..50255ed09 100644 --- a/tests/test_openapi_separate_input_output_schemas.py +++ b/tests/test_openapi_separate_input_output_schemas.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -8,15 +6,15 @@ from pydantic import BaseModel, computed_field class SubItem(BaseModel): subname: str - sub_description: Optional[str] = None + sub_description: str | None = None tags: list[str] = [] model_config = {"json_schema_serialization_defaults_required": True} class Item(BaseModel): name: str - description: Optional[str] = None - sub: Optional[SubItem] = None + description: str | None = None + sub: SubItem | None = None model_config = {"json_schema_serialization_defaults_required": True} diff --git a/tests/test_optional_file_list.py b/tests/test_optional_file_list.py index 686025864..a57e6358f 100644 --- a/tests/test_optional_file_list.py +++ b/tests/test_optional_file_list.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, File from fastapi.testclient import TestClient @@ -7,7 +5,7 @@ app = FastAPI() @app.post("/files") -async def upload_files(files: Optional[list[bytes]] = File(None)): +async def upload_files(files: list[bytes] | None = 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_param_class.py b/tests/test_param_class.py index 1fd40dcd2..e6642daea 100644 --- a/tests/test_param_class.py +++ b/tests/test_param_class.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.params import Param from fastapi.testclient import TestClient @@ -8,7 +6,7 @@ app = FastAPI() @app.get("/items/") -def read_items(q: Optional[str] = Param(default=None)): # type: ignore +def read_items(q: str | None = Param(default=None)): # type: ignore return {"q": q} diff --git a/tests/test_param_include_in_schema.py b/tests/test_param_include_in_schema.py index 463fb51b1..727552b46 100644 --- a/tests/test_param_include_in_schema.py +++ b/tests/test_param_include_in_schema.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import Cookie, FastAPI, Header, Path, Query from fastapi.testclient import TestClient @@ -10,14 +8,14 @@ app = FastAPI() @app.get("/hidden_cookie") async def hidden_cookie( - hidden_cookie: Optional[str] = Cookie(default=None, include_in_schema=False), + hidden_cookie: str | None = Cookie(default=None, include_in_schema=False), ): return {"hidden_cookie": hidden_cookie} @app.get("/hidden_header") async def hidden_header( - hidden_header: Optional[str] = Header(default=None, include_in_schema=False), + hidden_header: str | None = Header(default=None, include_in_schema=False), ): return {"hidden_header": hidden_header} @@ -29,7 +27,7 @@ async def hidden_path(hidden_path: str = Path(include_in_schema=False)): @app.get("/hidden_query") async def hidden_query( - hidden_query: Optional[str] = Query(default=None, include_in_schema=False), + hidden_query: str | None = Query(default=None, include_in_schema=False), ): return {"hidden_query": hidden_query} diff --git a/tests/test_pydantic_v1_error.py b/tests/test_pydantic_v1_error.py index 13229a313..044fdf0d6 100644 --- a/tests/test_pydantic_v1_error.py +++ b/tests/test_pydantic_v1_error.py @@ -1,6 +1,5 @@ import sys import warnings -from typing import Union import pytest @@ -80,7 +79,7 @@ def test_raises_pydantic_v1_model_in_union() -> None: with pytest.raises(PydanticV1NotSupportedError): @app.post("/union") - def endpoint(data: Union[dict, ModelV1A]): # pragma: no cover + def endpoint(data: dict | ModelV1A): # pragma: no cover return data diff --git a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py index b72b0518a..4f7b0b2a0 100644 --- a/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py +++ b/tests/test_pydanticv2_dataclasses_uuid_stringified_annotations.py @@ -2,7 +2,6 @@ from __future__ import annotations import uuid from dataclasses import dataclass, field -from typing import Union from dirty_equals import IsUUID from fastapi import FastAPI @@ -16,8 +15,8 @@ class Item: name: str price: float tags: list[str] = field(default_factory=list) - description: Union[str, None] = None - tax: Union[float, None] = None + description: str | None = None + tax: float | None = None app = FastAPI() diff --git a/tests/test_request_params/test_body/test_list.py b/tests/test_request_params/test_body/test_list.py index 970e6a660..aa9745f84 100644 --- a/tests/test_request_params/test_body/test_list.py +++ b/tests/test_request_params/test_body/test_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated import pytest from dirty_equals import IsOneOf, IsPartialDict @@ -55,7 +55,7 @@ def test_required_list_str_schema(path: str): "path", ["/required-list-str", "/model-required-list-str"], ) -def test_required_list_str_missing(path: str, json: Union[dict, None]): +def test_required_list_str_missing(path: str, json: dict | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -132,7 +132,7 @@ def test_required_list_str_alias_schema(path: str): "path", ["/required-list-alias", "/model-required-list-alias"], ) -def test_required_list_alias_missing(path: str, json: Union[dict, None]): +def test_required_list_alias_missing(path: str, json: dict | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -236,7 +236,7 @@ def test_required_list_validation_alias_schema(path: str): "/model-required-list-validation-alias", ], ) -def test_required_list_validation_alias_missing(path: str, json: Union[dict, None]): +def test_required_list_validation_alias_missing(path: str, json: dict | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 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 ba8ba9092..2c5c5d61b 100644 --- a/tests/test_request_params/test_body/test_optional_list.py +++ b/tests/test_request_params/test_body/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import Body, FastAPI @@ -15,13 +15,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[list[str] | None, Body(embed=True)] = None, ): return {"p": p} class BodyModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.post("/model-optional-list-str", operation_id="model_optional_list_str") @@ -103,13 +103,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[list[str] | None, Body(embed=True, alias="p_alias")] = None, ): return {"p": p} class BodyModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") @@ -208,14 +208,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") + list[str] | None, 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: list[str] | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -323,7 +323,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]], + list[str] | None, Body(embed=True, alias="p_alias", validation_alias="p_val_alias"), ] = None, ): @@ -331,9 +331,7 @@ def read_optional_list_alias_and_validation_alias( class BodyModelOptionalListAliasAndValidationAlias(BaseModel): - p: Optional[list[str]] = Field( - None, alias="p_alias", validation_alias="p_val_alias" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( 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 b9c18034d..184fc94cb 100644 --- a/tests/test_request_params/test_body/test_optional_str.py +++ b/tests/test_request_params/test_body/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import Body, FastAPI @@ -14,12 +14,12 @@ app = FastAPI() @app.post("/optional-str", operation_id="optional_str") -async def read_optional_str(p: Annotated[Optional[str], Body(embed=True)] = None): +async def read_optional_str(p: Annotated[str | None, Body(embed=True)] = None): return {"p": p} class BodyModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.post("/model-optional-str", operation_id="model_optional_str") @@ -98,13 +98,13 @@ def test_optional_str(path: str): @app.post("/optional-alias", operation_id="optional_alias") async def read_optional_alias( - p: Annotated[Optional[str], Body(embed=True, alias="p_alias")] = None, + p: Annotated[str | None, Body(embed=True, alias="p_alias")] = None, ): return {"p": p} class BodyModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.post("/model-optional-alias", operation_id="model_optional_alias") @@ -197,15 +197,13 @@ def test_optional_alias_by_alias(path: str): @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, + p: Annotated[str | None, 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") + p: str | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -309,14 +307,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): ) def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") + str | None, 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") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( 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 5b434fa1d..2e02f8d20 100644 --- a/tests/test_request_params/test_body/test_required_str.py +++ b/tests/test_request_params/test_body/test_required_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Any, Union +from typing import Annotated, Any import pytest from dirty_equals import IsOneOf @@ -51,7 +51,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: dict[str, Any] | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -124,7 +124,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: dict[str, Any] | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -221,9 +221,7 @@ def test_required_validation_alias_schema(path: str): "/model-required-validation-alias", ], ) -def test_required_validation_alias_missing( - path: str, json: Union[dict[str, Any], None] -): +def test_required_validation_alias_missing(path: str, json: dict[str, Any] | None): client = TestClient(app) response = client.post(path, json=json) assert response.status_code == 422 @@ -338,7 +336,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: dict[str, Any] | None ): client = TestClient(app) response = client.post(path, json=json) 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 1b2a18b07..227d2bccc 100644 --- a/tests/test_request_params/test_cookie/test_optional_str.py +++ b/tests/test_request_params/test_cookie/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import Cookie, FastAPI @@ -13,12 +13,12 @@ app = FastAPI() @app.get("/optional-str") -async def read_optional_str(p: Annotated[Optional[str], Cookie()] = None): +async def read_optional_str(p: Annotated[str | None, Cookie()] = None): return {"p": p} class CookieModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.get("/model-optional-str") @@ -75,13 +75,13 @@ def test_optional_str(path: str): @app.get("/optional-alias") async def read_optional_alias( - p: Annotated[Optional[str], Cookie(alias="p_alias")] = None, + p: Annotated[str | None, Cookie(alias="p_alias")] = None, ): return {"p": p} class CookieModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.get("/model-optional-alias") @@ -153,13 +153,13 @@ def test_optional_alias_by_alias(path: str): @app.get("/optional-validation-alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Cookie(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Cookie(validation_alias="p_val_alias")] = None, ): return {"p": p} class CookieModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-validation-alias") @@ -237,14 +237,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): @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") + str | None, 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") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-alias-and-validation-alias") diff --git a/tests/test_request_params/test_file/test_optional.py b/tests/test_request_params/test_file/test_optional.py index 45ef7bdec..b4dc11a06 100644 --- a/tests/test_request_params/test_file/test_optional.py +++ b/tests/test_request_params/test_file/test_optional.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile @@ -13,12 +13,12 @@ app = FastAPI() @app.post("/optional-bytes", operation_id="optional_bytes") -async def read_optional_bytes(p: Annotated[Optional[bytes], File()] = None): +async def read_optional_bytes(p: Annotated[bytes | None, 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): +async def read_optional_uploadfile(p: Annotated[UploadFile | None, File()] = None): return {"file_size": p.size if p else None} @@ -82,14 +82,14 @@ def test_optional(path: str): @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, + p: Annotated[bytes | None, 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, + p: Annotated[UploadFile | None, File(alias="p_alias")] = None, ): return {"file_size": p.size if p else None} @@ -170,7 +170,7 @@ def test_optional_alias_by_alias(path: str): "/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, + p: Annotated[bytes | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": len(p) if p else None} @@ -180,7 +180,7 @@ def read_optional_bytes_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, + p: Annotated[UploadFile | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": p.size if p else None} @@ -263,7 +263,7 @@ def test_optional_validation_alias_by_validation_alias(path: str): ) def read_optional_bytes_alias_and_validation_alias( p: Annotated[ - Optional[bytes], File(alias="p_alias", validation_alias="p_val_alias") + bytes | None, File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": len(p) if p else None} @@ -275,7 +275,7 @@ def read_optional_bytes_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") + UploadFile | None, File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": p.size if p else None} 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 162fbe08a..a506ec991 100644 --- a/tests/test_request_params/test_file/test_optional_list.py +++ b/tests/test_request_params/test_file/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, File, UploadFile @@ -13,13 +13,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[list[bytes] | None, 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[list[UploadFile] | None, File()] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -87,14 +87,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[list[bytes] | None, 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[list[UploadFile] | None, File(alias="p_alias")] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -176,16 +176,14 @@ 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[list[bytes] | None, 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, + p: Annotated[list[UploadFile] | None, File(validation_alias="p_val_alias")] = None, ): return {"file_size": [file.size for file in p] if p else None} @@ -270,7 +268,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") + list[bytes] | None, File(alias="p_alias", validation_alias="p_val_alias") ] = None, ): return {"file_size": [len(file) for file in p] if p else None} @@ -279,7 +277,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]], + list[UploadFile] | None, File(alias="p_alias", validation_alias="p_val_alias"), ] = None, ): 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 6d1957a18..7ecf9c9bf 100644 --- a/tests/test_request_params/test_form/test_optional_list.py +++ b/tests/test_request_params/test_form/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Form @@ -15,13 +15,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[list[str] | None, Form()] = None, ): return {"p": p} class FormModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.post("/model-optional-list-str", operation_id="model_optional_list_str") @@ -80,13 +80,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[list[str] | None, Form(alias="p_alias")] = None, ): return {"p": p} class FormModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") @@ -163,13 +163,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[list[str] | None, 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: list[str] | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -251,16 +251,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") + list[str] | None, 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" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( 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 810e83caa..4ef16756e 100644 --- a/tests/test_request_params/test_form/test_optional_str.py +++ b/tests/test_request_params/test_form/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Form @@ -14,12 +14,12 @@ app = FastAPI() @app.post("/optional-str", operation_id="optional_str") -async def read_optional_str(p: Annotated[Optional[str], Form()] = None): +async def read_optional_str(p: Annotated[str | None, Form()] = None): return {"p": p} class FormModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.post("/model-optional-str", operation_id="model_optional_str") @@ -75,13 +75,13 @@ def test_optional_str(path: str): @app.post("/optional-alias", operation_id="optional_alias") async def read_optional_alias( - p: Annotated[Optional[str], Form(alias="p_alias")] = None, + p: Annotated[str | None, Form(alias="p_alias")] = None, ): return {"p": p} class FormModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.post("/model-optional-alias", operation_id="model_optional_alias") @@ -151,13 +151,13 @@ def test_optional_alias_by_alias(path: str): @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, + p: Annotated[str | None, Form(validation_alias="p_val_alias")] = None, ): return {"p": p} class FormModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.post( @@ -238,14 +238,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): ) def read_optional_alias_and_validation_alias( p: Annotated[ - Optional[str], Form(alias="p_alias", validation_alias="p_val_alias") + str | None, 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") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.post( 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 3bbb73d54..9f4eacc23 100644 --- a/tests/test_request_params/test_header/test_optional_list.py +++ b/tests/test_request_params/test_header/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Header @@ -14,13 +14,13 @@ app = FastAPI() @app.get("/optional-list-str") async def read_optional_list_str( - p: Annotated[Optional[list[str]], Header()] = None, + p: Annotated[list[str] | None, Header()] = None, ): return {"p": p} class HeaderModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.get("/model-optional-list-str") @@ -81,13 +81,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[list[str] | None, Header(alias="p_alias")] = None, ): return {"p": p} class HeaderModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.get("/model-optional-list-alias") @@ -162,13 +162,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[list[str] | None, 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: list[str] | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-list-validation-alias") @@ -246,16 +246,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") + list[str] | None, 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" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-list-alias-and-validation-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 a5174e59a..04773c83f 100644 --- a/tests/test_request_params/test_header/test_optional_str.py +++ b/tests/test_request_params/test_header/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Header @@ -13,12 +13,12 @@ app = FastAPI() @app.get("/optional-str") -async def read_optional_str(p: Annotated[Optional[str], Header()] = None): +async def read_optional_str(p: Annotated[str | None, Header()] = None): return {"p": p} class HeaderModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.get("/model-optional-str") @@ -74,13 +74,13 @@ def test_optional_str(path: str): @app.get("/optional-alias") async def read_optional_alias( - p: Annotated[Optional[str], Header(alias="p_alias")] = None, + p: Annotated[str | None, Header(alias="p_alias")] = None, ): return {"p": p} class HeaderModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.get("/model-optional-alias") @@ -150,13 +150,13 @@ def test_optional_alias_by_alias(path: str): @app.get("/optional-validation-alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Header(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Header(validation_alias="p_val_alias")] = None, ): return {"p": p} class HeaderModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-validation-alias") @@ -232,14 +232,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): @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") + str | None, 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") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-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 5608c6499..6b70b75a4 100644 --- a/tests/test_request_params/test_query/test_optional_list.py +++ b/tests/test_request_params/test_query/test_optional_list.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Query @@ -14,13 +14,13 @@ app = FastAPI() @app.get("/optional-list-str") async def read_optional_list_str( - p: Annotated[Optional[list[str]], Query()] = None, + p: Annotated[list[str] | None, Query()] = None, ): return {"p": p} class QueryModelOptionalListStr(BaseModel): - p: Optional[list[str]] = None + p: list[str] | None = None @app.get("/model-optional-list-str") @@ -81,13 +81,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[list[str] | None, Query(alias="p_alias")] = None, ): return {"p": p} class QueryModelOptionalListAlias(BaseModel): - p: Optional[list[str]] = Field(None, alias="p_alias") + p: list[str] | None = Field(None, alias="p_alias") @app.get("/model-optional-list-alias") @@ -162,13 +162,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[list[str] | None, 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: list[str] | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-list-validation-alias") @@ -244,16 +244,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") + list[str] | None, 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" - ) + p: list[str] | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-list-alias-and-validation-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 b181686b0..f7f35860b 100644 --- a/tests/test_request_params/test_query/test_optional_str.py +++ b/tests/test_request_params/test_query/test_optional_str.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import pytest from fastapi import FastAPI, Query @@ -13,12 +13,12 @@ app = FastAPI() @app.get("/optional-str") -async def read_optional_str(p: Optional[str] = None): +async def read_optional_str(p: str | None = None): return {"p": p} class QueryModelOptionalStr(BaseModel): - p: Optional[str] = None + p: str | None = None @app.get("/model-optional-str") @@ -74,13 +74,13 @@ def test_optional_str(path: str): @app.get("/optional-alias") async def read_optional_alias( - p: Annotated[Optional[str], Query(alias="p_alias")] = None, + p: Annotated[str | None, Query(alias="p_alias")] = None, ): return {"p": p} class QueryModelOptionalAlias(BaseModel): - p: Optional[str] = Field(None, alias="p_alias") + p: str | None = Field(None, alias="p_alias") @app.get("/model-optional-alias") @@ -150,13 +150,13 @@ def test_optional_alias_by_alias(path: str): @app.get("/optional-validation-alias") def read_optional_validation_alias( - p: Annotated[Optional[str], Query(validation_alias="p_val_alias")] = None, + p: Annotated[str | None, Query(validation_alias="p_val_alias")] = None, ): return {"p": p} class QueryModelOptionalValidationAlias(BaseModel): - p: Optional[str] = Field(None, validation_alias="p_val_alias") + p: str | None = Field(None, validation_alias="p_val_alias") @app.get("/model-optional-validation-alias") @@ -232,14 +232,14 @@ def test_optional_validation_alias_by_validation_alias(path: str): @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") + str | None, 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") + p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") @app.get("/model-optional-alias-and-validation-alias") diff --git a/tests/test_required_noneable.py b/tests/test_required_noneable.py index 5da8cd4d0..c99f20212 100644 --- a/tests/test_required_noneable.py +++ b/tests/test_required_noneable.py @@ -1,5 +1,3 @@ -from typing import Union - from fastapi import Body, FastAPI, Query from fastapi.testclient import TestClient @@ -7,17 +5,17 @@ app = FastAPI() @app.get("/query") -def read_query(q: Union[str, None]): +def read_query(q: str | None): return q @app.get("/explicit-query") -def read_explicit_query(q: Union[str, None] = Query()): +def read_explicit_query(q: str | None = Query()): return q @app.post("/body-embed") -def send_body_embed(b: Union[str, None] = Body(embed=True)): +def send_body_embed(b: str | None = Body(embed=True)): return b diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py index ded597102..7be7902ad 100644 --- a/tests/test_response_model_as_return_annotation.py +++ b/tests/test_response_model_as_return_annotation.py @@ -1,5 +1,3 @@ -from typing import Union - import pytest from fastapi import FastAPI from fastapi.exceptions import FastAPIError, ResponseValidationError @@ -216,7 +214,7 @@ def no_response_model_annotation_forward_ref_list_of_model() -> "list[User]": @app.get( "/response_model_union-no_annotation-return_model1", - response_model=Union[User, Item], + response_model=User | Item, ) def response_model_union_no_annotation_return_model1(): return DBUser(name="John", surname="Doe", password_hash="secret") @@ -224,19 +222,19 @@ def response_model_union_no_annotation_return_model1(): @app.get( "/response_model_union-no_annotation-return_model2", - response_model=Union[User, Item], + response_model=User | Item, ) def response_model_union_no_annotation_return_model2(): return Item(name="Foo", price=42.0) @app.get("/no_response_model-annotation_union-return_model1") -def no_response_model_annotation_union_return_model1() -> Union[User, Item]: +def no_response_model_annotation_union_return_model1() -> User | Item: return DBUser(name="John", surname="Doe", password_hash="secret") @app.get("/no_response_model-annotation_union-return_model2") -def no_response_model_annotation_union_return_model2() -> Union[User, Item]: +def no_response_model_annotation_union_return_model2() -> User | Item: return Item(name="Foo", price=42.0) @@ -503,7 +501,7 @@ def test_invalid_response_model_field(): with pytest.raises(FastAPIError) as e: @app.get("/") - def read_root() -> Union[Response, None]: + def read_root() -> Response | None: return Response(content="Foo") # pragma: no cover assert "valid Pydantic field type" in e.value.args[0] diff --git a/tests/test_router_events.py b/tests/test_router_events.py index a47d11913..7869a7afc 100644 --- a/tests/test_router_events.py +++ b/tests/test_router_events.py @@ -1,6 +1,5 @@ from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from typing import Union import pytest from fastapi import APIRouter, FastAPI, Request @@ -176,7 +175,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, str | bool], None]: yield { "app_specific": True, "overridden": "app", @@ -185,7 +184,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, str | bool], None]: yield { "router_specific": True, "overridden": "router", # should override parent diff --git a/tests/test_schema_extra_examples.py b/tests/test_schema_extra_examples.py index 9ec41e7e8..32f5cea47 100644 --- a/tests/test_schema_extra_examples.py +++ b/tests/test_schema_extra_examples.py @@ -1,5 +1,3 @@ -from typing import Union - import pytest from fastapi import Body, Cookie, FastAPI, Header, Path, Query from fastapi.exceptions import FastAPIDeprecationWarning @@ -117,7 +115,7 @@ def create_app(): @app.get("/query_example/") def query_example( - data: Union[str, None] = Query( + data: str | None = Query( default=None, example="query1", ), @@ -126,7 +124,7 @@ def create_app(): @app.get("/query_examples/") def query_examples( - data: Union[str, None] = Query( + data: str | None = Query( default=None, examples=["query1", "query2"], ), @@ -137,7 +135,7 @@ def create_app(): @app.get("/query_example_examples/") def query_example_examples( - data: Union[str, None] = Query( + data: str | None = Query( default=None, example="query_overridden", examples=["query1", "query2"], @@ -149,7 +147,7 @@ def create_app(): @app.get("/header_example/") def header_example( - data: Union[str, None] = Header( + data: str | None = Header( default=None, example="header1", ), @@ -158,7 +156,7 @@ def create_app(): @app.get("/header_examples/") def header_examples( - data: Union[str, None] = Header( + data: str | None = Header( default=None, examples=[ "header1", @@ -172,7 +170,7 @@ def create_app(): @app.get("/header_example_examples/") def header_example_examples( - data: Union[str, None] = Header( + data: str | None = Header( default=None, example="header_overridden", examples=["header1", "header2"], @@ -184,7 +182,7 @@ def create_app(): @app.get("/cookie_example/") def cookie_example( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, example="cookie1", ), @@ -193,7 +191,7 @@ def create_app(): @app.get("/cookie_examples/") def cookie_examples( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, examples=["cookie1", "cookie2"], ), @@ -204,7 +202,7 @@ def create_app(): @app.get("/cookie_example_examples/") def cookie_example_examples( - data: Union[str, None] = Cookie( + data: str | None = Cookie( default=None, example="cookie_overridden", examples=["cookie1", "cookie2"], diff --git a/tests/test_security_api_key_cookie_optional.py b/tests/test_security_api_key_cookie_optional.py index 7988d8044..e911654fa 100644 --- a/tests/test_security_api_key_cookie_optional.py +++ b/tests/test_security_api_key_cookie_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyCookie from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(api_key)): +def get_current_user(oauth_header: str | None = Security(api_key)): if oauth_header is None: return None user = User(username=oauth_header) diff --git a/tests/test_security_api_key_header_optional.py b/tests/test_security_api_key_header_optional.py index 51abd0bb9..0a8cf420e 100644 --- a/tests/test_security_api_key_header_optional.py +++ b/tests/test_security_api_key_header_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyHeader from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(api_key)): +def get_current_user(oauth_header: str | None = Security(api_key)): if oauth_header is None: return None user = User(username=oauth_header) @@ -23,7 +21,7 @@ def get_current_user(oauth_header: Optional[str] = Security(api_key)): @app.get("/users/me") -def read_current_user(current_user: Optional[User] = Depends(get_current_user)): +def read_current_user(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_api_key_query_optional.py b/tests/test_security_api_key_query_optional.py index 26fbb9ee4..e9fba3043 100644 --- a/tests/test_security_api_key_query_optional.py +++ b/tests/test_security_api_key_query_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security import APIKeyQuery from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(api_key)): +def get_current_user(oauth_header: str | None = Security(api_key)): if oauth_header is None: return None user = User(username=oauth_header) @@ -23,7 +21,7 @@ def get_current_user(oauth_header: Optional[str] = Security(api_key)): @app.get("/users/me") -def read_current_user(current_user: Optional[User] = Depends(get_current_user)): +def read_current_user(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_http_base_optional.py b/tests/test_security_http_base_optional.py index 612a7909f..1d1944ab0 100644 --- a/tests/test_security_http_base_optional.py +++ b/tests/test_security_http_base_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBase from fastapi.testclient import TestClient @@ -12,7 +10,7 @@ security = HTTPBase(scheme="Other", auto_error=False) @app.get("/users/me") def read_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Security(security), + credentials: HTTPAuthorizationCredentials | None = Security(security), ): if credentials is None: return {"msg": "Create an account first"} diff --git a/tests/test_security_http_basic_optional.py b/tests/test_security_http_basic_optional.py index e94565c7b..78abf2b68 100644 --- a/tests/test_security_http_basic_optional.py +++ b/tests/test_security_http_basic_optional.py @@ -1,5 +1,4 @@ from base64 import b64encode -from typing import Optional from fastapi import FastAPI, Security from fastapi.security import HTTPBasic, HTTPBasicCredentials @@ -12,7 +11,7 @@ security = HTTPBasic(auto_error=False) @app.get("/users/me") -def read_current_user(credentials: Optional[HTTPBasicCredentials] = Security(security)): +def read_current_user(credentials: HTTPBasicCredentials | None = Security(security)): if credentials is None: return {"msg": "Create an account first"} return {"username": credentials.username, "password": credentials.password} diff --git a/tests/test_security_http_bearer_optional.py b/tests/test_security_http_bearer_optional.py index b49a6593e..06d9d03db 100644 --- a/tests/test_security_http_bearer_optional.py +++ b/tests/test_security_http_bearer_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from fastapi.testclient import TestClient @@ -12,7 +10,7 @@ security = HTTPBearer(auto_error=False) @app.get("/users/me") def read_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Security(security), + credentials: HTTPAuthorizationCredentials | None = Security(security), ): if credentials is None: return {"msg": "Create an account first"} diff --git a/tests/test_security_http_digest_optional.py b/tests/test_security_http_digest_optional.py index 97e62634d..d1056b191 100644 --- a/tests/test_security_http_digest_optional.py +++ b/tests/test_security_http_digest_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest from fastapi.testclient import TestClient @@ -12,7 +10,7 @@ security = HTTPDigest(auto_error=False) @app.get("/users/me") def read_current_user( - credentials: Optional[HTTPAuthorizationCredentials] = Security(security), + credentials: HTTPAuthorizationCredentials | None = Security(security), ): if credentials is None: return {"msg": "Create an account first"} diff --git a/tests/test_security_oauth2_authorization_code_bearer.py b/tests/test_security_oauth2_authorization_code_bearer.py index 1ba577e9f..587486c76 100644 --- a/tests/test_security_oauth2_authorization_code_bearer.py +++ b/tests/test_security_oauth2_authorization_code_bearer.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient @@ -13,7 +11,7 @@ oauth2_scheme = OAuth2AuthorizationCodeBearer( @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): return {"token": token} diff --git a/tests/test_security_oauth2_authorization_code_bearer_description.py b/tests/test_security_oauth2_authorization_code_bearer_description.py index 73807c31a..f878ede64 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_description.py +++ b/tests/test_security_oauth2_authorization_code_bearer_description.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.testclient import TestClient @@ -16,7 +14,7 @@ oauth2_scheme = OAuth2AuthorizationCodeBearer( @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): return {"token": token} 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 583007c8b..6fcce6fed 100644 --- a/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py +++ b/tests/test_security_oauth2_authorization_code_bearer_scopes_openapi.py @@ -1,6 +1,6 @@ # Ref: https://github.com/fastapi/fastapi/issues/14454 -from typing import Annotated, Optional +from typing import Annotated from fastapi import APIRouter, Depends, FastAPI, Security from fastapi.security import OAuth2AuthorizationCodeBearer @@ -46,13 +46,13 @@ router = APIRouter(dependencies=[Security(oauth2_scheme, scopes=["read"])]) @router.get("/items/") -async def read_items(token: Optional[str] = Depends(oauth2_scheme)): +async def read_items(token: str | None = Depends(oauth2_scheme)): return {"token": token} @router.post("/items/") async def create_item( - token: Optional[str] = Security(oauth2_scheme, scopes=["read", "write"]), + token: str | None = Security(oauth2_scheme, scopes=["read", "write"]), ): return {"token": token} diff --git a/tests/test_security_oauth2_optional.py b/tests/test_security_oauth2_optional.py index cb79afdb8..a7eaf5944 100644 --- a/tests/test_security_oauth2_optional.py +++ b/tests/test_security_oauth2_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -24,7 +22,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): +def get_current_user(oauth_header: str | None = Security(reusable_oauth2)): if oauth_header is None: return None user = User(username=oauth_header) @@ -37,7 +35,7 @@ def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): @app.get("/users/me") -def read_users_me(current_user: Optional[User] = Depends(get_current_user)): +def read_users_me(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_oauth2_optional_description.py b/tests/test_security_oauth2_optional_description.py index b3fae37a1..0918d352e 100644 --- a/tests/test_security_oauth2_optional_description.py +++ b/tests/test_security_oauth2_optional_description.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import Depends, FastAPI, Security from fastapi.security import OAuth2, OAuth2PasswordRequestFormStrict @@ -25,7 +23,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(reusable_oauth2)): +def get_current_user(oauth_header: str | None = Security(reusable_oauth2)): if oauth_header is None: return None user = User(username=oauth_header) @@ -38,7 +36,7 @@ def login(form_data: OAuth2PasswordRequestFormStrict = Depends()): @app.get("/users/me") -def read_users_me(current_user: Optional[User] = Depends(get_current_user)): +def read_users_me(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_security_oauth2_password_bearer_optional.py b/tests/test_security_oauth2_password_bearer_optional.py index 01e2f65ed..263359c95 100644 --- a/tests/test_security_oauth2_password_bearer_optional.py +++ b/tests/test_security_oauth2_password_bearer_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2PasswordBearer from fastapi.testclient import TestClient @@ -11,7 +9,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token", auto_error=False) @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): if token is None: return {"msg": "Create an account first"} return {"token": token} diff --git a/tests/test_security_oauth2_password_bearer_optional_description.py b/tests/test_security_oauth2_password_bearer_optional_description.py index fec8d03a7..0deb7e48f 100644 --- a/tests/test_security_oauth2_password_bearer_optional_description.py +++ b/tests/test_security_oauth2_password_bearer_optional_description.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI, Security from fastapi.security import OAuth2PasswordBearer from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ oauth2_scheme = OAuth2PasswordBearer( @app.get("/items/") -async def read_items(token: Optional[str] = Security(oauth2_scheme)): +async def read_items(token: str | None = Security(oauth2_scheme)): if token is None: return {"msg": "Create an account first"} return {"token": token} diff --git a/tests/test_security_openid_connect_optional.py b/tests/test_security_openid_connect_optional.py index ebaf394dc..44e1a4866 100644 --- a/tests/test_security_openid_connect_optional.py +++ b/tests/test_security_openid_connect_optional.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, FastAPI, Security from fastapi.security.open_id_connect_url import OpenIdConnect from fastapi.testclient import TestClient @@ -15,7 +13,7 @@ class User(BaseModel): username: str -def get_current_user(oauth_header: Optional[str] = Security(oid)): +def get_current_user(oauth_header: str | None = Security(oid)): if oauth_header is None: return None user = User(username=oauth_header) @@ -23,7 +21,7 @@ def get_current_user(oauth_header: Optional[str] = Security(oid)): @app.get("/users/me") -def read_current_user(current_user: Optional[User] = Depends(get_current_user)): +def read_current_user(current_user: User | None = Depends(get_current_user)): if current_user is None: return {"msg": "Create an account first"} return current_user diff --git a/tests/test_serialize_response.py b/tests/test_serialize_response.py index 14f88dd93..114c3c6cb 100644 --- a/tests/test_serialize_response.py +++ b/tests/test_serialize_response.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -9,8 +7,8 @@ app = FastAPI() class Item(BaseModel): name: str - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/valid", response_model=Item) diff --git a/tests/test_serialize_response_dataclass.py b/tests/test_serialize_response_dataclass.py index ee695368b..ae05f14d1 100644 --- a/tests/test_serialize_response_dataclass.py +++ b/tests/test_serialize_response_dataclass.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from datetime import datetime -from typing import Optional from fastapi import FastAPI from fastapi.testclient import TestClient @@ -12,8 +11,8 @@ app = FastAPI() class Item: name: str date: datetime - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/valid", response_model=Item) diff --git a/tests/test_serialize_response_model.py b/tests/test_serialize_response_model.py index 79c90c9c2..bb05f7bc4 100644 --- a/tests/test_serialize_response_model.py +++ b/tests/test_serialize_response_model.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from pydantic import BaseModel, Field from starlette.testclient import TestClient @@ -9,8 +7,8 @@ app = FastAPI() class Item(BaseModel): name: str = Field(alias="aliased_name") - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/valid", response_model=Item) diff --git a/tests/test_skip_defaults.py b/tests/test_skip_defaults.py index 02765291c..238da7392 100644 --- a/tests/test_skip_defaults.py +++ b/tests/test_skip_defaults.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import FastAPI from fastapi.testclient import TestClient from pydantic import BaseModel @@ -8,23 +6,23 @@ app = FastAPI() class SubModel(BaseModel): - a: Optional[str] = "foo" + a: str | None = "foo" class Model(BaseModel): - x: Optional[int] = None + x: int | None = None sub: SubModel class ModelSubclass(Model): y: int z: int = 0 - w: Optional[int] = None + w: int | None = None class ModelDefaults(BaseModel): - w: Optional[str] = None - x: Optional[str] = None + w: str | None = None + x: str | None = None y: str = "y" z: str = "z" diff --git a/tests/test_sub_callbacks.py b/tests/test_sub_callbacks.py index 86dc4d00e..b8a9dd292 100644 --- a/tests/test_sub_callbacks.py +++ b/tests/test_sub_callbacks.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import APIRouter, FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -10,7 +8,7 @@ app = FastAPI() class Invoice(BaseModel): id: str - title: Optional[str] = None + title: str | None = None customer: str total: float @@ -51,7 +49,7 @@ subrouter = APIRouter() @subrouter.post("/invoices/", callbacks=invoices_callback_router.routes) -def create_invoice(invoice: Invoice, callback_url: Optional[HttpUrl] = None): +def create_invoice(invoice: Invoice, callback_url: HttpUrl | None = None): """ Create an invoice. diff --git a/tests/test_union_body.py b/tests/test_union_body.py index e333e2499..88f9e06cc 100644 --- a/tests/test_union_body.py +++ b/tests/test_union_body.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -9,7 +7,7 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None + name: str | None = None class OtherItem(BaseModel): @@ -17,7 +15,7 @@ class OtherItem(BaseModel): @app.post("/items/") -def save_union_body(item: Union[OtherItem, Item]): +def save_union_body(item: OtherItem | Item): return {"item": item} diff --git a/tests/test_union_body_discriminator.py b/tests/test_union_body_discriminator.py index 4afe7be4b..1b682c775 100644 --- a/tests/test_union_body_discriminator.py +++ b/tests/test_union_body_discriminator.py @@ -1,10 +1,9 @@ -from typing import Annotated, Any, Union +from typing import Annotated, Any, Literal from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot from pydantic import BaseModel, Field -from typing_extensions import Literal def test_discriminator_pydantic_v2() -> None: @@ -21,7 +20,7 @@ def test_discriminator_pydantic_v2() -> None: price: float Item = Annotated[ - Union[Annotated[FirstItem, Tag("first")], Annotated[OtherItem, Tag("other")]], + Annotated[FirstItem, Tag("first")] | Annotated[OtherItem, Tag("other")], Field(discriminator="value"), ] diff --git a/tests/test_union_body_discriminator_annotated.py b/tests/test_union_body_discriminator_annotated.py index 6644d106c..7e64ea75b 100644 --- a/tests/test_union_body_discriminator_annotated.py +++ b/tests/test_union_body_discriminator_annotated.py @@ -1,6 +1,6 @@ # Ref: https://github.com/fastapi/fastapi/discussions/14495 -from typing import Annotated, Union +from typing import Annotated import pytest from fastapi import FastAPI @@ -27,7 +27,7 @@ def client_fixture() -> TestClient: return v.get("pet_type", "") Pet = Annotated[ - Union[Annotated[Cat, Tag("cat")], Annotated[Dog, Tag("dog")]], + Annotated[Cat, Tag("cat")] | Annotated[Dog, Tag("dog")], Discriminator(get_pet_type), ] diff --git a/tests/test_union_forms.py b/tests/test_union_forms.py index f6c2658f9..8cd7b4f01 100644 --- a/tests/test_union_forms.py +++ b/tests/test_union_forms.py @@ -1,4 +1,4 @@ -from typing import Annotated, Union +from typing import Annotated from fastapi import FastAPI, Form from fastapi.testclient import TestClient @@ -19,7 +19,7 @@ class CompanyForm(BaseModel): @app.post("/form-union/") -def post_union_form(data: Annotated[Union[UserForm, CompanyForm], Form()]): +def post_union_form(data: Annotated[UserForm | CompanyForm, Form()]): return {"received": data} diff --git a/tests/test_union_inherited_body.py b/tests/test_union_inherited_body.py index 5378880a4..c997a87a3 100644 --- a/tests/test_union_inherited_body.py +++ b/tests/test_union_inherited_body.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - from fastapi import FastAPI from fastapi.testclient import TestClient from inline_snapshot import snapshot @@ -9,7 +7,7 @@ app = FastAPI() class Item(BaseModel): - name: Optional[str] = None + name: str | None = None class ExtendedItem(Item): @@ -17,7 +15,7 @@ class ExtendedItem(Item): @app.post("/items/") -def save_union_different_body(item: Union[ExtendedItem, Item]): +def save_union_different_body(item: ExtendedItem | Item): return {"item": item} diff --git a/tests/test_validate_response.py b/tests/test_validate_response.py index 938d41956..7288220ea 100644 --- a/tests/test_validate_response.py +++ b/tests/test_validate_response.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - import pytest from fastapi import FastAPI from fastapi.exceptions import ResponseValidationError @@ -11,8 +9,8 @@ app = FastAPI() class Item(BaseModel): name: str - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/invalid", response_model=Item) @@ -25,7 +23,7 @@ def get_invalid_none(): return None -@app.get("/items/validnone", response_model=Union[Item, None]) +@app.get("/items/validnone", response_model=Item | None) def get_valid_none(send_none: bool = False): if send_none: return None diff --git a/tests/test_validate_response_dataclass.py b/tests/test_validate_response_dataclass.py index 67282bcde..03b7d5f33 100644 --- a/tests/test_validate_response_dataclass.py +++ b/tests/test_validate_response_dataclass.py @@ -1,5 +1,3 @@ -from typing import Optional - import pytest from fastapi import FastAPI from fastapi.exceptions import ResponseValidationError @@ -12,8 +10,8 @@ app = FastAPI() @dataclass class Item: name: str - price: Optional[float] = None - owner_ids: Optional[list[int]] = None + price: float | None = None + owner_ids: list[int] | None = None @app.get("/items/invalid", response_model=Item) diff --git a/tests/utils.py b/tests/utils.py index 4cbfee79f..09c4e13b0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,6 @@ import sys import pytest -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+" )