diff --git a/fastapi/_compat.py b/fastapi/_compat.py index b37338766..3e0261ae8 100644 --- a/fastapi/_compat.py +++ b/fastapi/_compat.py @@ -18,13 +18,14 @@ from typing import ( Union, ) -from fastapi.exceptions import RequestErrorModel -from fastapi.types import IncEx, ModelNameMap, UnionType from pydantic import BaseModel, create_model from pydantic.version import VERSION as P_VERSION from starlette.datastructures import UploadFile from typing_extensions import Annotated, Literal, get_args, get_origin +from fastapi.exceptions import RequestErrorModel +from fastapi.types import IncEx, ModelNameMap, UnionType + # Reassign variable to make it reexported for mypy PYDANTIC_VERSION = P_VERSION PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.") @@ -50,8 +51,8 @@ if PYDANTIC_V2: from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError from pydantic import TypeAdapter from pydantic import ValidationError as ValidationError - from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] - GetJsonSchemaHandler as GetJsonSchemaHandler, + from pydantic._internal._schema_generation_shared import ( + GetJsonSchemaHandler as GetJsonSchemaHandler, # type: ignore[attr-defined] ) from pydantic._internal._typing_extra import eval_type_lenient from pydantic._internal._utils import lenient_issubclass as lenient_issubclass @@ -67,8 +68,8 @@ if PYDANTIC_V2: with_info_plain_validator_function as with_info_plain_validator_function, ) except ImportError: # pragma: no cover - from pydantic_core.core_schema import ( - general_plain_validator_function as with_info_plain_validator_function, # noqa: F401 + from pydantic_core.core_schema import ( # noqa: F401 + general_plain_validator_function as with_info_plain_validator_function, ) Required = PydanticUndefined @@ -149,8 +150,8 @@ if PYDANTIC_V2: # What calls this code passes a value that already called # self._type_adapter.validate_python(value) # - # context argument was introduced in pydantic 2.7.3 - kwargs = {"context": context} if PYDANTIC_VERSION >= "2.7.3" else {} + # context argument was introduced in pydantic 2.8 + kwargs = {"context": context} if PYDANTIC_VERSION >= "2.8" else {} return self._type_adapter.dump_python( value, @@ -287,17 +288,16 @@ if PYDANTIC_V2: return BodyModel else: - from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX from pydantic import AnyUrl as Url # noqa: F401 - from pydantic import ( # type: ignore[assignment] - BaseConfig as BaseConfig, # noqa: F401 + from pydantic import ( # type: ignore[assignment]; noqa: F401 + BaseConfig as BaseConfig, ) from pydantic import ValidationError as ValidationError # noqa: F401 - from pydantic.class_validators import ( # type: ignore[no-redef] - Validator as Validator, # noqa: F401 + from pydantic.class_validators import ( # type: ignore[no-redef]; noqa: F401 + Validator as Validator, ) - from pydantic.error_wrappers import ( # type: ignore[no-redef] - ErrorWrapper as ErrorWrapper, # noqa: F401 + from pydantic.error_wrappers import ( # type: ignore[no-redef]; noqa: F401 + ErrorWrapper as ErrorWrapper, ) from pydantic.errors import MissingError from pydantic.fields import ( # type: ignore[attr-defined] @@ -310,34 +310,36 @@ else: SHAPE_TUPLE_ELLIPSIS, ) from pydantic.fields import FieldInfo as FieldInfo - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - ModelField as ModelField, # noqa: F401 + from pydantic.fields import ( # type: ignore[no-redef,attr-defined]; noqa: F401 + ModelField as ModelField, ) - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - Required as Required, # noqa: F401 + from pydantic.fields import ( # type: ignore[no-redef,attr-defined]; noqa: F401 + Required as Required, ) - from pydantic.fields import ( # type: ignore[no-redef,attr-defined] - Undefined as Undefined, + from pydantic.fields import ( + Undefined as Undefined, # type: ignore[no-redef,attr-defined] ) - from pydantic.fields import ( # type: ignore[no-redef, attr-defined] - UndefinedType as UndefinedType, # noqa: F401 + from pydantic.fields import ( # type: ignore[no-redef, attr-defined]; noqa: F401 + UndefinedType as UndefinedType, + ) + from pydantic.schema import field_schema + from pydantic.schema import ( # type: ignore[no-redef] # noqa: F401 + get_annotation_from_field_info as get_annotation_from_field_info, ) from pydantic.schema import ( - field_schema, get_flat_models_from_fields, get_model_name_map, model_process_schema, ) - from pydantic.schema import ( # type: ignore[no-redef] # noqa: F401 - get_annotation_from_field_info as get_annotation_from_field_info, + from pydantic.typing import ( # type: ignore[no-redef]; noqa: F401 + evaluate_forwardref as evaluate_forwardref, ) - from pydantic.typing import ( # type: ignore[no-redef] - evaluate_forwardref as evaluate_forwardref, # noqa: F401 - ) - from pydantic.utils import ( # type: ignore[no-redef] - lenient_issubclass as lenient_issubclass, # noqa: F401 + from pydantic.utils import ( # type: ignore[no-redef]; noqa: F401 + lenient_issubclass as lenient_issubclass, ) + from fastapi.openapi.constants import REF_PREFIX as REF_PREFIX + GetJsonSchemaHandler = Any # type: ignore[assignment,misc] JsonSchemaValue = Dict[str, Any] # type: ignore[misc] CoreSchema = Any # type: ignore[assignment,misc] diff --git a/fastapi/applications.py b/fastapi/applications.py index 02a67c6e0..63c797f55 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -13,6 +13,17 @@ from typing import ( Union, ) +from starlette.applications import Starlette +from starlette.datastructures import State +from starlette.exceptions import HTTPException +from starlette.middleware import Middleware +from starlette.middleware.base import BaseHTTPMiddleware +from starlette.requests import Request +from starlette.responses import HTMLResponse, JSONResponse, Response +from starlette.routing import BaseRoute +from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send +from typing_extensions import Annotated, Doc, deprecated + from fastapi import routing from fastapi.datastructures import Default, DefaultPlaceholder from fastapi.exception_handlers import ( @@ -31,16 +42,6 @@ from fastapi.openapi.utils import get_openapi from fastapi.params import Depends from fastapi.types import DecoratedCallable, IncEx from fastapi.utils import generate_unique_id -from starlette.applications import Starlette -from starlette.datastructures import State -from starlette.exceptions import HTTPException -from starlette.middleware import Middleware -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.requests import Request -from starlette.responses import HTMLResponse, JSONResponse, Response -from starlette.routing import BaseRoute -from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send -from typing_extensions import Annotated, Doc, deprecated AppType = TypeVar("AppType", bound="FastAPI") @@ -1723,7 +1724,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -2112,7 +2113,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -2506,7 +2507,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -2900,7 +2901,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -3289,7 +3290,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -3678,7 +3679,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -4067,7 +4068,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -4461,7 +4462,7 @@ class FastAPI(Starlette): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) diff --git a/fastapi/routing.py b/fastapi/routing.py index 07c9c971c..116eaa88a 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -19,6 +19,24 @@ from typing import ( Union, ) +from pydantic import BaseModel +from starlette import routing +from starlette.concurrency import run_in_threadpool +from starlette.exceptions import HTTPException +from starlette.requests import Request +from starlette.responses import JSONResponse, Response +from starlette.routing import BaseRoute, Match +from starlette.routing import Mount as Mount # noqa +from starlette.routing import ( + compile_path, + get_name, + request_response, + websocket_session, +) +from starlette.types import ASGIApp, Lifespan, Scope +from starlette.websockets import WebSocket +from typing_extensions import Annotated, Doc, deprecated + from fastapi import params from fastapi._compat import ( ModelField, @@ -52,24 +70,6 @@ from fastapi.utils import ( get_value_or_default, is_body_allowed_for_status_code, ) -from pydantic import BaseModel -from starlette import routing -from starlette.concurrency import run_in_threadpool -from starlette.exceptions import HTTPException -from starlette.requests import Request -from starlette.responses import JSONResponse, Response -from starlette.routing import ( - BaseRoute, - Match, - compile_path, - get_name, - request_response, - websocket_session, -) -from starlette.routing import Mount as Mount # noqa -from starlette.types import ASGIApp, Lifespan, Scope -from starlette.websockets import WebSocket -from typing_extensions import Annotated, Doc, deprecated def _prepare_response_content( @@ -1583,7 +1583,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -1976,7 +1976,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -2374,7 +2374,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -2772,7 +2772,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -3165,7 +3165,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -3558,7 +3558,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -3956,7 +3956,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) @@ -4354,7 +4354,7 @@ class APIRouter(routing.Router): This will be passed in as serialization context to the response model. - Note: This feature is a noop on pydantic < 2.7.2 + Note: This feature is a noop on pydantic < 2.8 Read more about serialization context in the [Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context) diff --git a/tests/test_serialize_response_model.py b/tests/test_serialize_response_model.py index 470946357..ab9d4c6df 100644 --- a/tests/test_serialize_response_model.py +++ b/tests/test_serialize_response_model.py @@ -1,11 +1,12 @@ from typing import Dict, List, Optional import pytest -from fastapi import FastAPI -from fastapi._compat import PYDANTIC_V2, PYDANTIC_VERSION from pydantic import BaseModel, Field from starlette.testclient import TestClient +from fastapi import FastAPI +from fastapi._compat import PYDANTIC_V2, PYDANTIC_VERSION + app = FastAPI() @@ -188,7 +189,7 @@ if PYDANTIC_V2: client_v2 = TestClient(app_v2) - @pytest.mark.skipif(PYDANTIC_VERSION < "2.7.3", reason="requires Pydantic v2.7.3+") + @pytest.mark.skipif(PYDANTIC_VERSION < "2.8", reason="requires Pydantic v2.8+") def test_validdict_with_context__pydantic_supported(): response = client_v2.get("/items/validdict-with-context") response.raise_for_status() @@ -202,7 +203,7 @@ if PYDANTIC_V2: assert response.json() == expected_response @pytest.mark.skipif( - PYDANTIC_VERSION >= "2.7.3", + PYDANTIC_VERSION >= "2.8", reason="Pydantic supports the feature from this point on", ) def test_validdict_with_context__pre_pydantic_support():