Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
- "3.10"
- "3.9"
- "3.8"
pydantic-version: ["pydantic-v1", "pydantic-v2"]
pydantic-version: ["pydantic-v1", "pydantic-v2.7", "pydantic-v2.8+"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need this.
There are other places where the code may fail with older versions of Pydantic and if we try to cover all such cases we will end up having huge amount of runs

fail-fast: false
steps:
- name: Dump GitHub context
Expand All @@ -80,8 +80,11 @@ jobs:
if: matrix.pydantic-version == 'pydantic-v1'
run: uv pip install "pydantic>=1.10.0,<2.0.0"
- name: Install Pydantic v2
if: matrix.pydantic-version == 'pydantic-v2'
run: uv pip install --upgrade "pydantic>=2.0.2,<3.0.0"
if: matrix.pydantic-version == 'pydantic-v2.7'
run: uv pip install --upgrade "pydantic>=2.0.2,<2.8"
- name: Install Pydantic v2.8+
if: matrix.pydantic-version == 'pydantic-v2.8+'
run: uv pip install --upgrade "pydantic>=2.8,<3.0"
# TODO: Remove this once Python 3.8 is no longer supported
- name: Install older AnyIO in Python 3.8
if: matrix.python-version == '3.8'
Expand Down
41 changes: 29 additions & 12 deletions fastapi/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
FrozenSet,
List,
Mapping,
Optional,
Sequence,
Set,
Tuple,
Expand Down Expand Up @@ -69,8 +70,8 @@
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,
)

RequiredParam = PydanticUndefined
Expand Down Expand Up @@ -146,19 +147,35 @@ def serialize(
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
context: Optional[Dict[str, Any]] = None,
) -> Any:
# What calls this code passes a value that already called
# self._type_adapter.validate_python(value)
return self._type_adapter.dump_python(
value,
mode=mode,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
#
# context argument was introduced in pydantic 2.8
if PYDANTIC_VERSION >= "2.8":
return self._type_adapter.dump_python(
value,
mode=mode,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
context=context,
)
else:
return self._type_adapter.dump_python(
value,
mode=mode,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There were linting issues when changing this to use dict approach from #11670 so leaving as is.

Comment on lines +154 to +178
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#
# context argument was introduced in pydantic 2.8
if PYDANTIC_VERSION >= "2.8":
return self._type_adapter.dump_python(
value,
mode=mode,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
context=context,
)
else:
return self._type_adapter.dump_python(
value,
mode=mode,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
kwargs: dict[str, Any] = (
{"context": context} if PYDANTIC_VERSION >= "2.8" else {}
)
return self._type_adapter.dump_python(
value,
mode=mode,
include=include,
exclude=exclude,
by_alias=by_alias,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
**kwargs,
)

This doesn't give any linting issues


def __hash__(self) -> int:
# Each ModelField is unique for our purposes, to allow making a dict from
Expand Down
132 changes: 132 additions & 0 deletions fastapi/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,7 @@ def add_api_route(
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
response_model_context: Optional[Dict[str, Any]] = None,
include_in_schema: bool = True,
response_class: Union[Type[Response], DefaultPlaceholder] = Default(
JSONResponse
Expand Down Expand Up @@ -1105,6 +1106,7 @@ def add_api_route(
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -1133,6 +1135,7 @@ def api_route(
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
response_model_context: Optional[Dict[str, Any]] = None,
include_in_schema: bool = True,
response_class: Type[Response] = Default(JSONResponse),
name: Optional[str] = None,
Expand Down Expand Up @@ -1162,6 +1165,7 @@ def decorator(func: DecoratedCallable) -> DecoratedCallable:
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -1711,6 +1715,21 @@ def get(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

Note: This feature is a noop on pydantic < 2.8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note: This feature is a noop on pydantic < 2.8
Note: This feature requires Pydantic 2.8 or higher


Read more about serialization context in the
[Pydantic documentation](https://docs.pydantic.dev/latest/concepts/serialization/#serialization-context)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -1822,6 +1841,7 @@ def read_items():
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -2084,6 +2104,21 @@ def put(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -2200,6 +2235,7 @@ def replace_item(item_id: str, item: Item):
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -2462,6 +2498,21 @@ def post(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -2578,6 +2629,7 @@ def create_item(item: Item):
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -2840,6 +2892,21 @@ def delete(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -2951,6 +3018,7 @@ def delete_item(item_id: str):
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -3213,6 +3281,21 @@ def options(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -3324,6 +3407,7 @@ def get_item_options():
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -3586,6 +3670,21 @@ def head(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -3697,6 +3796,7 @@ def get_items_headers(response: Response):
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -3959,6 +4059,21 @@ def patch(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -4075,6 +4190,7 @@ def update_item(item: Item):
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down Expand Up @@ -4337,6 +4453,21 @@ def trace(
"""
),
] = False,
response_model_context: Annotated[
Optional[Dict[str, Any]],
Doc(
"""
Additional context to pass to Pydantic when creating the response.

This will be passed in as serialization context to the response model.

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)
"""
),
] = None,
include_in_schema: Annotated[
bool,
Doc(
Expand Down Expand Up @@ -4448,6 +4579,7 @@ def trace_item(item_id: str):
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
response_model_context=response_model_context,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
Expand Down
Loading
Loading