diff --git a/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
new file mode 100644
index 000000000..7f60492ee
--- /dev/null
+++ b/docs/de/docs/how-to/migrate-from-pydantic-v1-to-pydantic-v2.md
@@ -0,0 +1,133 @@
+# Von Pydantic v1 zu Pydantic v2 migrieren { #migrate-from-pydantic-v1-to-pydantic-v2 }
+
+Wenn Sie eine ältere FastAPI-App haben, nutzen Sie möglicherweise Pydantic Version 1.
+
+FastAPI unterstützt seit Version 0.100.0 sowohl Pydantic v1 als auch v2.
+
+Wenn Sie Pydantic v2 installiert hatten, wurde dieses verwendet. Wenn stattdessen Pydantic v1 installiert war, wurde jenes verwendet.
+
+Pydantic v1 ist jetzt deprecatet und die Unterstützung dafür wird in den nächsten Versionen von FastAPI entfernt, Sie sollten also zu **Pydantic v2 migrieren**. Auf diese Weise erhalten Sie die neuesten Features, Verbesserungen und Fixes.
+
+/// warning | Achtung
+
+Außerdem hat das Pydantic-Team die Unterstützung für Pydantic v1 in den neuesten Python-Versionen eingestellt, beginnend mit **Python 3.14**.
+
+Wenn Sie die neuesten Features von Python nutzen möchten, müssen Sie sicherstellen, dass Sie Pydantic v2 verwenden.
+
+///
+
+Wenn Sie eine ältere FastAPI-App mit Pydantic v1 haben, zeige ich Ihnen hier, wie Sie sie zu Pydantic v2 migrieren, und die **neuen Features in FastAPI 0.119.0**, die Ihnen bei einer schrittweisen Migration helfen.
+
+## Offizieller Leitfaden { #official-guide }
+
+Pydantic hat einen offiziellen Migrationsleitfaden von v1 zu v2.
+
+Er enthält auch, was sich geändert hat, wie Validierungen nun korrekter und strikter sind, mögliche Stolpersteine, usw.
+
+Sie können ihn lesen, um besser zu verstehen, was sich geändert hat.
+
+## Tests { #tests }
+
+Stellen Sie sicher, dass Sie [Tests](../tutorial/testing.md){.internal-link target=_blank} für Ihre App haben und diese in Continuous Integration (CI) ausführen.
+
+Auf diese Weise können Sie das Update durchführen und sicherstellen, dass weiterhin alles wie erwartet funktioniert.
+
+## `bump-pydantic` { #bump-pydantic }
+
+In vielen Fällen, wenn Sie reguläre Pydantic-Modelle ohne Anpassungen verwenden, können Sie den Großteil des Prozesses der Migration von Pydantic v1 auf Pydantic v2 automatisieren.
+
+Sie können `bump-pydantic` vom selben Pydantic-Team verwenden.
+
+Dieses Tool hilft Ihnen, den Großteil des zu ändernden Codes automatisch anzupassen.
+
+Danach können Sie die Tests ausführen und prüfen, ob alles funktioniert. Falls ja, sind Sie fertig. 😎
+
+## Pydantic v1 in v2 { #pydantic-v1-in-v2 }
+
+Pydantic v2 enthält alles aus Pydantic v1 als Untermodul `pydantic.v1`.
+
+Das bedeutet, Sie können die neueste Version von Pydantic v2 installieren und die alten Pydantic‑v1‑Komponenten aus diesem Untermodul importieren und verwenden, als hätten Sie das alte Pydantic v1 installiert.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial001_an_py310.py hl[1,4] *}
+
+### FastAPI-Unterstützung für Pydantic v1 in v2 { #fastapi-support-for-pydantic-v1-in-v2 }
+
+Seit FastAPI 0.119.0 gibt es außerdem eine teilweise Unterstützung für Pydantic v1 innerhalb von Pydantic v2, um die Migration auf v2 zu erleichtern.
+
+Sie könnten also Pydantic auf die neueste Version 2 aktualisieren und die Importe so ändern, dass das Untermodul `pydantic.v1` verwendet wird, und in vielen Fällen würde es einfach funktionieren.
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial002_an_py310.py hl[2,5,15] *}
+
+/// warning | Achtung
+
+Beachten Sie, dass, da das Pydantic‑Team Pydantic v1 in neueren Python‑Versionen nicht mehr unterstützt, beginnend mit Python 3.14, auch die Verwendung von `pydantic.v1` unter Python 3.14 und höher nicht unterstützt wird.
+
+///
+
+### Pydantic v1 und v2 in derselben App { #pydantic-v1-and-v2-on-the-same-app }
+
+Es wird von Pydantic **nicht unterstützt**, dass ein Pydantic‑v2‑Modell Felder hat, die als Pydantic‑v1‑Modelle definiert sind, und umgekehrt.
+
+```mermaid
+graph TB
+ subgraph "❌ Nicht unterstützt"
+ direction TB
+ subgraph V2["Pydantic-v2-Modell"]
+ V1Field["Pydantic-v1-Modell"]
+ end
+ subgraph V1["Pydantic-v1-Modell"]
+ V2Field["Pydantic-v2-Modell"]
+ end
+ end
+
+ style V2 fill:#f9fff3
+ style V1 fill:#fff6f0
+ style V1Field fill:#fff6f0
+ style V2Field fill:#f9fff3
+```
+
+... aber Sie können getrennte Modelle, die Pydantic v1 bzw. v2 nutzen, in derselben App verwenden.
+
+```mermaid
+graph TB
+ subgraph "✅ Unterstützt"
+ direction TB
+ subgraph V2["Pydantic-v2-Modell"]
+ V2Field["Pydantic-v2-Modell"]
+ end
+ subgraph V1["Pydantic-v1-Modell"]
+ V1Field["Pydantic-v1-Modell"]
+ end
+ end
+
+ style V2 fill:#f9fff3
+ style V1 fill:#fff6f0
+ style V1Field fill:#fff6f0
+ style V2Field fill:#f9fff3
+```
+
+In einigen Fällen ist es sogar möglich, sowohl Pydantic‑v1‑ als auch Pydantic‑v2‑Modelle in derselben **Pfadoperation** Ihrer FastAPI‑App zu verwenden:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial003_an_py310.py hl[2:3,6,12,21:22] *}
+
+Im obigen Beispiel ist das Eingabemodell ein Pydantic‑v1‑Modell, und das Ausgabemodell (definiert in `response_model=ItemV2`) ist ein Pydantic‑v2‑Modell.
+
+### Pydantic v1 Parameter { #pydantic-v1-parameters }
+
+Wenn Sie einige der FastAPI-spezifischen Tools für Parameter wie `Body`, `Query`, `Form`, usw. zusammen mit Pydantic‑v1‑Modellen verwenden müssen, können Sie die aus `fastapi.temp_pydantic_v1_params` importieren, während Sie die Migration zu Pydantic v2 abschließen:
+
+{* ../../docs_src/pydantic_v1_in_v2/tutorial004_an_py310.py hl[4,18] *}
+
+### In Schritten migrieren { #migrate-in-steps }
+
+/// tip | Tipp
+
+Probieren Sie zuerst `bump-pydantic` aus. Wenn Ihre Tests erfolgreich sind und das funktioniert, sind Sie mit einem einzigen Befehl fertig. ✨
+
+///
+
+Wenn `bump-pydantic` für Ihren Anwendungsfall nicht funktioniert, können Sie die Unterstützung für Pydantic‑v1‑ und Pydantic‑v2‑Modelle in derselben App nutzen, um die Migration zu Pydantic v2 schrittweise durchzuführen.
+
+Sie könnten zuerst Pydantic auf die neueste Version 2 aktualisieren und die Importe so ändern, dass für all Ihre Modelle `pydantic.v1` verwendet wird.
+
+Anschließend können Sie beginnen, Ihre Modelle gruppenweise von Pydantic v1 auf v2 zu migrieren – in kleinen, schrittweisen Etappen. 🚶
diff --git a/docs/en/data/sponsors.yml b/docs/en/data/sponsors.yml
index ae28410e7..7a015e404 100644
--- a/docs/en/data/sponsors.yml
+++ b/docs/en/data/sponsors.yml
@@ -58,3 +58,6 @@ bronze:
- url: https://lambdatest.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage
title: LambdaTest, AI-Powered Cloud-based Test Orchestration Platform
img: https://fastapi.tiangolo.com/img/sponsors/lambdatest.png
+ - url: https://requestly.com/fastapi
+ title: All-in-one platform to Test, Mock and Intercept APIs. Built for speed, privacy and offline support.
+ img: https://fastapi.tiangolo.com/img/sponsors/requestly.png
diff --git a/docs/en/data/sponsors_badge.yml b/docs/en/data/sponsors_badge.yml
index 62ba6a84c..14f55805c 100644
--- a/docs/en/data/sponsors_badge.yml
+++ b/docs/en/data/sponsors_badge.yml
@@ -46,3 +46,4 @@ logins:
- madisonredtfeldt
- railwayapp
- subtotal
+ - requestly
diff --git a/docs/en/docs/img/sponsors/requestly.png b/docs/en/docs/img/sponsors/requestly.png
new file mode 100644
index 000000000..a167aa017
Binary files /dev/null and b/docs/en/docs/img/sponsors/requestly.png differ
diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md
index 1853b54f9..03f8df30e 100644
--- a/docs/en/docs/release-notes.md
+++ b/docs/en/docs/release-notes.md
@@ -7,12 +7,23 @@ hide:
## Latest Changes
+### Translations
+
+* 🌐 Sync German docs. PR [#14188](https://github.com/fastapi/fastapi/pull/14188) by [@nilslindemann](https://github.com/nilslindemann).
+
+## 0.119.1
+
+### Fixes
+
+* 🐛 Fix internal Pydantic v1 compatibility (warnings) for Python 3.14 and Pydantic 2.12.1. PR [#14186](https://github.com/fastapi/fastapi/pull/14186) by [@svlandeg](https://github.com/svlandeg).
+
### Docs
* 📝 Replace `starlette.io` by `starlette.dev` and `uvicorn.org` by `uvicorn.dev`. PR [#14176](https://github.com/fastapi/fastapi/pull/14176) by [@Kludex](https://github.com/Kludex).
### Internal
+* 🔧 Add sponsor Requestly. PR [#14205](https://github.com/fastapi/fastapi/pull/14205) by [@tiangolo](https://github.com/tiangolo).
* 🔧 Configure reminder for `waiting` label in `issue-manager`. PR [#14156](https://github.com/fastapi/fastapi/pull/14156) by [@YuriiMotov](https://github.com/YuriiMotov).
## 0.119.0
diff --git a/fastapi/__init__.py b/fastapi/__init__.py
index 2091f0d1f..a7164d18f 100644
--- a/fastapi/__init__.py
+++ b/fastapi/__init__.py
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
-__version__ = "0.119.0"
+__version__ = "0.119.1"
from starlette import status as status
diff --git a/fastapi/_compat/__init__.py b/fastapi/_compat/__init__.py
index b2ae5adc7..0aadd68de 100644
--- a/fastapi/_compat/__init__.py
+++ b/fastapi/_compat/__init__.py
@@ -30,6 +30,10 @@ from .main import serialize_sequence_value as serialize_sequence_value
from .main import (
with_info_plain_validator_function as with_info_plain_validator_function,
)
+from .may_v1 import CoreSchema as CoreSchema
+from .may_v1 import GetJsonSchemaHandler as GetJsonSchemaHandler
+from .may_v1 import JsonSchemaValue as JsonSchemaValue
+from .may_v1 import _normalize_errors as _normalize_errors
from .model_field import ModelField as ModelField
from .shared import PYDANTIC_V2 as PYDANTIC_V2
from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE
@@ -44,7 +48,3 @@ from .shared import (
from .shared import lenient_issubclass as lenient_issubclass
from .shared import sequence_types as sequence_types
from .shared import value_is_sequence as value_is_sequence
-from .v1 import CoreSchema as CoreSchema
-from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler
-from .v1 import JsonSchemaValue as JsonSchemaValue
-from .v1 import _normalize_errors as _normalize_errors
diff --git a/fastapi/_compat/main.py b/fastapi/_compat/main.py
index 3f758f072..e5275950e 100644
--- a/fastapi/_compat/main.py
+++ b/fastapi/_compat/main.py
@@ -1,3 +1,4 @@
+import sys
from functools import lru_cache
from typing import (
Any,
@@ -8,7 +9,7 @@ from typing import (
Type,
)
-from fastapi._compat import v1
+from fastapi._compat import may_v1
from fastapi._compat.shared import PYDANTIC_V2, lenient_issubclass
from fastapi.types import ModelNameMap
from pydantic import BaseModel
@@ -50,7 +51,9 @@ else:
@lru_cache
def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:
- if lenient_issubclass(model, v1.BaseModel):
+ if lenient_issubclass(model, may_v1.BaseModel):
+ from fastapi._compat import v1
+
return v1.get_model_fields(model)
else:
from . import v2
@@ -59,7 +62,7 @@ def get_cached_model_fields(model: Type[BaseModel]) -> List[ModelField]:
def _is_undefined(value: object) -> bool:
- if isinstance(value, v1.UndefinedType):
+ if isinstance(value, may_v1.UndefinedType):
return True
elif PYDANTIC_V2:
from . import v2
@@ -69,7 +72,9 @@ def _is_undefined(value: object) -> bool:
def _get_model_config(model: BaseModel) -> Any:
- if isinstance(model, v1.BaseModel):
+ if isinstance(model, may_v1.BaseModel):
+ from fastapi._compat import v1
+
return v1._get_model_config(model)
elif PYDANTIC_V2:
from . import v2
@@ -80,7 +85,9 @@ def _get_model_config(model: BaseModel) -> Any:
def _model_dump(
model: BaseModel, mode: Literal["json", "python"] = "json", **kwargs: Any
) -> Any:
- if isinstance(model, v1.BaseModel):
+ if isinstance(model, may_v1.BaseModel):
+ from fastapi._compat import v1
+
return v1._model_dump(model, mode=mode, **kwargs)
elif PYDANTIC_V2:
from . import v2
@@ -89,7 +96,7 @@ def _model_dump(
def _is_error_wrapper(exc: Exception) -> bool:
- if isinstance(exc, v1.ErrorWrapper):
+ if isinstance(exc, may_v1.ErrorWrapper):
return True
elif PYDANTIC_V2:
from . import v2
@@ -99,7 +106,9 @@ def _is_error_wrapper(exc: Exception) -> bool:
def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
- if isinstance(field_info, v1.FieldInfo):
+ if isinstance(field_info, may_v1.FieldInfo):
+ from fastapi._compat import v1
+
return v1.copy_field_info(field_info=field_info, annotation=annotation)
else:
assert PYDANTIC_V2
@@ -111,7 +120,9 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
def create_body_model(
*, fields: Sequence[ModelField], model_name: str
) -> Type[BaseModel]:
- if fields and isinstance(fields[0], v1.ModelField):
+ if fields and isinstance(fields[0], may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.create_body_model(fields=fields, model_name=model_name)
else:
assert PYDANTIC_V2
@@ -123,7 +134,9 @@ def create_body_model(
def get_annotation_from_field_info(
annotation: Any, field_info: FieldInfo, field_name: str
) -> Any:
- if isinstance(field_info, v1.FieldInfo):
+ if isinstance(field_info, may_v1.FieldInfo):
+ from fastapi._compat import v1
+
return v1.get_annotation_from_field_info(
annotation=annotation, field_info=field_info, field_name=field_name
)
@@ -137,7 +150,9 @@ def get_annotation_from_field_info(
def is_bytes_field(field: ModelField) -> bool:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.is_bytes_field(field)
else:
assert PYDANTIC_V2
@@ -147,7 +162,9 @@ def is_bytes_field(field: ModelField) -> bool:
def is_bytes_sequence_field(field: ModelField) -> bool:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.is_bytes_sequence_field(field)
else:
assert PYDANTIC_V2
@@ -157,7 +174,9 @@ def is_bytes_sequence_field(field: ModelField) -> bool:
def is_scalar_field(field: ModelField) -> bool:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.is_scalar_field(field)
else:
assert PYDANTIC_V2
@@ -167,7 +186,9 @@ def is_scalar_field(field: ModelField) -> bool:
def is_scalar_sequence_field(field: ModelField) -> bool:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.is_scalar_sequence_field(field)
else:
assert PYDANTIC_V2
@@ -177,7 +198,9 @@ def is_scalar_sequence_field(field: ModelField) -> bool:
def is_sequence_field(field: ModelField) -> bool:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.is_sequence_field(field)
else:
assert PYDANTIC_V2
@@ -187,7 +210,9 @@ def is_sequence_field(field: ModelField) -> bool:
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.serialize_sequence_value(field=field, value=value)
else:
assert PYDANTIC_V2
@@ -197,7 +222,9 @@ def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
def _model_rebuild(model: Type[BaseModel]) -> None:
- if lenient_issubclass(model, v1.BaseModel):
+ if lenient_issubclass(model, may_v1.BaseModel):
+ from fastapi._compat import v1
+
v1._model_rebuild(model)
elif PYDANTIC_V2:
from . import v2
@@ -206,9 +233,18 @@ def _model_rebuild(model: Type[BaseModel]) -> None:
def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
- v1_model_fields = [field for field in fields if isinstance(field, v1.ModelField)]
- v1_flat_models = v1.get_flat_models_from_fields(v1_model_fields, known_models=set()) # type: ignore[attr-defined]
- all_flat_models = v1_flat_models
+ v1_model_fields = [
+ field for field in fields if isinstance(field, may_v1.ModelField)
+ ]
+ if v1_model_fields:
+ from fastapi._compat import v1
+
+ v1_flat_models = v1.get_flat_models_from_fields(
+ v1_model_fields, known_models=set()
+ )
+ all_flat_models = v1_flat_models
+ else:
+ all_flat_models = set()
if PYDANTIC_V2:
from . import v2
@@ -222,6 +258,8 @@ def get_compat_model_name_map(fields: List[ModelField]) -> ModelNameMap:
model_name_map = v2.get_model_name_map(all_flat_models)
return model_name_map
+ from fastapi._compat import v1
+
model_name_map = v1.get_model_name_map(all_flat_models)
return model_name_map
@@ -232,17 +270,35 @@ def get_definitions(
model_name_map: ModelNameMap,
separate_input_output_schemas: bool = True,
) -> Tuple[
- Dict[Tuple[ModelField, Literal["validation", "serialization"]], v1.JsonSchemaValue],
+ Dict[
+ Tuple[ModelField, Literal["validation", "serialization"]],
+ may_v1.JsonSchemaValue,
+ ],
Dict[str, Dict[str, Any]],
]:
- v1_fields = [field for field in fields if isinstance(field, v1.ModelField)]
- v1_field_maps, v1_definitions = v1.get_definitions(
- fields=v1_fields,
- model_name_map=model_name_map,
- separate_input_output_schemas=separate_input_output_schemas,
- )
- if not PYDANTIC_V2:
- return v1_field_maps, v1_definitions
+ if sys.version_info < (3, 14):
+ v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)]
+ v1_field_maps, v1_definitions = may_v1.get_definitions(
+ fields=v1_fields,
+ model_name_map=model_name_map,
+ separate_input_output_schemas=separate_input_output_schemas,
+ )
+ if not PYDANTIC_V2:
+ return v1_field_maps, v1_definitions
+ else:
+ from . import v2
+
+ v2_fields = [field for field in fields if isinstance(field, v2.ModelField)]
+ v2_field_maps, v2_definitions = v2.get_definitions(
+ fields=v2_fields,
+ model_name_map=model_name_map,
+ separate_input_output_schemas=separate_input_output_schemas,
+ )
+ all_definitions = {**v1_definitions, **v2_definitions}
+ all_field_maps = {**v1_field_maps, **v2_field_maps}
+ return all_field_maps, all_definitions
+
+ # Pydantic v1 is not supported since Python 3.14
else:
from . import v2
@@ -252,9 +308,7 @@ def get_definitions(
model_name_map=model_name_map,
separate_input_output_schemas=separate_input_output_schemas,
)
- all_definitions = {**v1_definitions, **v2_definitions}
- all_field_maps = {**v1_field_maps, **v2_field_maps}
- return all_field_maps, all_definitions
+ return v2_field_maps, v2_definitions
def get_schema_from_model_field(
@@ -262,11 +316,14 @@ def get_schema_from_model_field(
field: ModelField,
model_name_map: ModelNameMap,
field_mapping: Dict[
- Tuple[ModelField, Literal["validation", "serialization"]], v1.JsonSchemaValue
+ Tuple[ModelField, Literal["validation", "serialization"]],
+ may_v1.JsonSchemaValue,
],
separate_input_output_schemas: bool = True,
) -> Dict[str, Any]:
- if isinstance(field, v1.ModelField):
+ if isinstance(field, may_v1.ModelField):
+ from fastapi._compat import v1
+
return v1.get_schema_from_model_field(
field=field,
model_name_map=model_name_map,
@@ -286,7 +343,7 @@ def get_schema_from_model_field(
def _is_model_field(value: Any) -> bool:
- if isinstance(value, v1.ModelField):
+ if isinstance(value, may_v1.ModelField):
return True
elif PYDANTIC_V2:
from . import v2
@@ -296,7 +353,7 @@ def _is_model_field(value: Any) -> bool:
def _is_model_class(value: Any) -> bool:
- if lenient_issubclass(value, v1.BaseModel):
+ if lenient_issubclass(value, may_v1.BaseModel):
return True
elif PYDANTIC_V2:
from . import v2
diff --git a/fastapi/_compat/may_v1.py b/fastapi/_compat/may_v1.py
new file mode 100644
index 000000000..beea4d167
--- /dev/null
+++ b/fastapi/_compat/may_v1.py
@@ -0,0 +1,123 @@
+import sys
+from typing import Any, Dict, List, Literal, Sequence, Tuple, Type, Union
+
+from fastapi.types import ModelNameMap
+
+if sys.version_info >= (3, 14):
+
+ class AnyUrl:
+ pass
+
+ class BaseConfig:
+ pass
+
+ class BaseModel:
+ pass
+
+ class Color:
+ pass
+
+ class CoreSchema:
+ pass
+
+ class ErrorWrapper:
+ pass
+
+ class FieldInfo:
+ pass
+
+ class GetJsonSchemaHandler:
+ pass
+
+ class JsonSchemaValue:
+ pass
+
+ class ModelField:
+ pass
+
+ class NameEmail:
+ pass
+
+ class RequiredParam:
+ pass
+
+ class SecretBytes:
+ pass
+
+ class SecretStr:
+ pass
+
+ class Undefined:
+ pass
+
+ class UndefinedType:
+ pass
+
+ class Url:
+ pass
+
+ from .v2 import ValidationError, create_model
+
+ def get_definitions(
+ *,
+ fields: List[ModelField],
+ model_name_map: ModelNameMap,
+ separate_input_output_schemas: bool = True,
+ ) -> Tuple[
+ Dict[
+ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
+ ],
+ Dict[str, Dict[str, Any]],
+ ]:
+ return {}, {} # pragma: no cover
+
+
+else:
+ from .v1 import AnyUrl as AnyUrl
+ from .v1 import BaseConfig as BaseConfig
+ from .v1 import BaseModel as BaseModel
+ from .v1 import Color as Color
+ from .v1 import CoreSchema as CoreSchema
+ from .v1 import ErrorWrapper as ErrorWrapper
+ from .v1 import FieldInfo as FieldInfo
+ from .v1 import GetJsonSchemaHandler as GetJsonSchemaHandler
+ from .v1 import JsonSchemaValue as JsonSchemaValue
+ from .v1 import ModelField as ModelField
+ from .v1 import NameEmail as NameEmail
+ from .v1 import RequiredParam as RequiredParam
+ from .v1 import SecretBytes as SecretBytes
+ from .v1 import SecretStr as SecretStr
+ from .v1 import Undefined as Undefined
+ from .v1 import UndefinedType as UndefinedType
+ from .v1 import Url as Url
+ from .v1 import ValidationError, create_model
+ from .v1 import get_definitions as get_definitions
+
+
+RequestErrorModel: Type[BaseModel] = create_model("Request")
+
+
+def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]:
+ use_errors: List[Any] = []
+ for error in errors:
+ if isinstance(error, ErrorWrapper):
+ new_errors = ValidationError( # type: ignore[call-arg]
+ errors=[error], model=RequestErrorModel
+ ).errors()
+ use_errors.extend(new_errors)
+ elif isinstance(error, list):
+ use_errors.extend(_normalize_errors(error))
+ else:
+ use_errors.append(error)
+ return use_errors
+
+
+def _regenerate_error_with_loc(
+ *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...]
+) -> List[Dict[str, Any]]:
+ updated_loc_errors: List[Any] = [
+ {**err, "loc": loc_prefix + err.get("loc", ())}
+ for err in _normalize_errors(errors)
+ ]
+
+ return updated_loc_errors
diff --git a/fastapi/_compat/shared.py b/fastapi/_compat/shared.py
index 495d5c5f7..cabf48228 100644
--- a/fastapi/_compat/shared.py
+++ b/fastapi/_compat/shared.py
@@ -16,7 +16,7 @@ from typing import (
Union,
)
-from fastapi._compat import v1
+from fastapi._compat import may_v1
from fastapi.types import UnionType
from pydantic import BaseModel
from pydantic.version import VERSION as PYDANTIC_VERSION
@@ -98,7 +98,9 @@ def value_is_sequence(value: Any) -> bool:
def _annotation_is_complex(annotation: Union[Type[Any], None]) -> bool:
return (
- lenient_issubclass(annotation, (BaseModel, v1.BaseModel, Mapping, UploadFile))
+ lenient_issubclass(
+ annotation, (BaseModel, may_v1.BaseModel, Mapping, UploadFile)
+ )
or _annotation_is_sequence(annotation)
or is_dataclass(annotation)
)
@@ -195,12 +197,12 @@ def is_uploadfile_sequence_annotation(annotation: Any) -> bool:
def annotation_is_pydantic_v1(annotation: Any) -> bool:
- if lenient_issubclass(annotation, v1.BaseModel):
+ if lenient_issubclass(annotation, may_v1.BaseModel):
return True
origin = get_origin(annotation)
if origin is Union or origin is UnionType:
for arg in get_args(annotation):
- if lenient_issubclass(arg, v1.BaseModel):
+ if lenient_issubclass(arg, may_v1.BaseModel):
return True
if field_annotation_is_sequence(annotation):
for sub_annotation in get_args(annotation):
diff --git a/fastapi/_compat/v1.py b/fastapi/_compat/v1.py
index f0ac51634..e17ce8bea 100644
--- a/fastapi/_compat/v1.py
+++ b/fastapi/_compat/v1.py
@@ -54,13 +54,15 @@ if not PYDANTIC_V2:
from pydantic.schema import TypeModelSet as TypeModelSet
from pydantic.schema import (
field_schema,
- get_flat_models_from_fields,
model_process_schema,
)
from pydantic.schema import (
get_annotation_from_field_info as get_annotation_from_field_info,
)
from pydantic.schema import get_flat_models_from_field as get_flat_models_from_field
+ from pydantic.schema import (
+ get_flat_models_from_fields as get_flat_models_from_fields,
+ )
from pydantic.schema import get_model_name_map as get_model_name_map
from pydantic.types import SecretBytes as SecretBytes
from pydantic.types import SecretStr as SecretStr
@@ -99,7 +101,6 @@ else:
from pydantic.v1.schema import TypeModelSet as TypeModelSet
from pydantic.v1.schema import (
field_schema,
- get_flat_models_from_fields,
model_process_schema,
)
from pydantic.v1.schema import (
@@ -108,6 +109,9 @@ else:
from pydantic.v1.schema import (
get_flat_models_from_field as get_flat_models_from_field,
)
+ from pydantic.v1.schema import (
+ get_flat_models_from_fields as get_flat_models_from_fields,
+ )
from pydantic.v1.schema import get_model_name_map as get_model_name_map
from pydantic.v1.types import ( # type: ignore[assignment]
SecretBytes as SecretBytes,
@@ -215,32 +219,6 @@ def is_pv1_scalar_sequence_field(field: ModelField) -> bool:
return False
-def _normalize_errors(errors: Sequence[Any]) -> List[Dict[str, Any]]:
- use_errors: List[Any] = []
- for error in errors:
- if isinstance(error, ErrorWrapper):
- new_errors = ValidationError( # type: ignore[call-arg]
- errors=[error], model=RequestErrorModel
- ).errors()
- use_errors.extend(new_errors)
- elif isinstance(error, list):
- use_errors.extend(_normalize_errors(error))
- else:
- use_errors.append(error)
- return use_errors
-
-
-def _regenerate_error_with_loc(
- *, errors: Sequence[Any], loc_prefix: Tuple[Union[str, int], ...]
-) -> List[Dict[str, Any]]:
- updated_loc_errors: List[Any] = [
- {**err, "loc": loc_prefix + err.get("loc", ())}
- for err in _normalize_errors(errors)
- ]
-
- return updated_loc_errors
-
-
def _model_rebuild(model: Type[BaseModel]) -> None:
model.update_forward_refs()
diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py
index 29606b9f3..fb2c691d8 100644
--- a/fastapi/_compat/v2.py
+++ b/fastapi/_compat/v2.py
@@ -15,7 +15,7 @@ from typing import (
cast,
)
-from fastapi._compat import shared, v1
+from fastapi._compat import may_v1, shared
from fastapi.openapi.constants import REF_TEMPLATE
from fastapi.types import IncEx, ModelNameMap
from pydantic import BaseModel, TypeAdapter, create_model
@@ -116,7 +116,7 @@ class ModelField:
None,
)
except ValidationError as exc:
- return None, v1._regenerate_error_with_loc(
+ return None, may_v1._regenerate_error_with_loc(
errors=exc.errors(include_url=False), loc_prefix=loc
)
diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py
index 675ad6faf..aa06dd2a9 100644
--- a/fastapi/dependencies/utils.py
+++ b/fastapi/dependencies/utils.py
@@ -43,9 +43,9 @@ from fastapi._compat import (
is_uploadfile_or_nonable_uploadfile_annotation,
is_uploadfile_sequence_annotation,
lenient_issubclass,
+ may_v1,
sequence_types,
serialize_sequence_value,
- v1,
value_is_sequence,
)
from fastapi._compat.shared import annotation_is_pydantic_v1
@@ -380,7 +380,7 @@ def analyze_param(
fastapi_annotations = [
arg
for arg in annotated_args[1:]
- if isinstance(arg, (FieldInfo, v1.FieldInfo, params.Depends))
+ if isinstance(arg, (FieldInfo, may_v1.FieldInfo, params.Depends))
]
fastapi_specific_annotations = [
arg
@@ -397,21 +397,21 @@ def analyze_param(
)
]
if fastapi_specific_annotations:
- fastapi_annotation: Union[FieldInfo, v1.FieldInfo, params.Depends, None] = (
- fastapi_specific_annotations[-1]
- )
+ fastapi_annotation: Union[
+ FieldInfo, may_v1.FieldInfo, params.Depends, None
+ ] = fastapi_specific_annotations[-1]
else:
fastapi_annotation = None
# Set default for Annotated FieldInfo
- if isinstance(fastapi_annotation, (FieldInfo, v1.FieldInfo)):
+ if isinstance(fastapi_annotation, (FieldInfo, may_v1.FieldInfo)):
# Copy `field_info` because we mutate `field_info.default` below.
field_info = copy_field_info(
field_info=fastapi_annotation, annotation=use_annotation
)
assert field_info.default in {
Undefined,
- v1.Undefined,
- } or field_info.default in {RequiredParam, v1.RequiredParam}, (
+ may_v1.Undefined,
+ } or field_info.default in {RequiredParam, may_v1.RequiredParam}, (
f"`{field_info.__class__.__name__}` default value cannot be set in"
f" `Annotated` for {param_name!r}. Set the default value with `=` instead."
)
@@ -435,7 +435,7 @@ def analyze_param(
)
depends = value
# Get FieldInfo from default value
- elif isinstance(value, (FieldInfo, v1.FieldInfo)):
+ elif isinstance(value, (FieldInfo, may_v1.FieldInfo)):
assert field_info is None, (
"Cannot specify FastAPI annotations in `Annotated` and default value"
f" together for {param_name!r}"
@@ -524,7 +524,8 @@ def analyze_param(
type_=use_annotation_from_field_info,
default=field_info.default,
alias=alias,
- required=field_info.default in (RequiredParam, v1.RequiredParam, Undefined),
+ required=field_info.default
+ in (RequiredParam, may_v1.RequiredParam, Undefined),
field_info=field_info,
)
if is_path_param:
@@ -741,7 +742,7 @@ def _validate_value_with_model_field(
if _is_error_wrapper(errors_): # type: ignore[arg-type]
return None, [errors_]
elif isinstance(errors_, list):
- new_errors = v1._regenerate_error_with_loc(errors=errors_, loc_prefix=())
+ new_errors = may_v1._regenerate_error_with_loc(errors=errors_, loc_prefix=())
return None, new_errors
else:
return v_, []
diff --git a/fastapi/encoders.py b/fastapi/encoders.py
index 8ff7d58dd..bba9c970e 100644
--- a/fastapi/encoders.py
+++ b/fastapi/encoders.py
@@ -17,7 +17,7 @@ from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
from uuid import UUID
-from fastapi._compat import v1
+from fastapi._compat import may_v1
from fastapi.types import IncEx
from pydantic import BaseModel
from pydantic.color import Color
@@ -59,7 +59,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = {
bytes: lambda o: o.decode(),
Color: str,
- v1.Color: str,
+ may_v1.Color: str,
datetime.date: isoformat,
datetime.datetime: isoformat,
datetime.time: isoformat,
@@ -76,19 +76,19 @@ ENCODERS_BY_TYPE: Dict[Type[Any], Callable[[Any], Any]] = {
IPv6Interface: str,
IPv6Network: str,
NameEmail: str,
- v1.NameEmail: str,
+ may_v1.NameEmail: str,
Path: str,
Pattern: lambda o: o.pattern,
SecretBytes: str,
- v1.SecretBytes: str,
+ may_v1.SecretBytes: str,
SecretStr: str,
- v1.SecretStr: str,
+ may_v1.SecretStr: str,
set: list,
UUID: str,
Url: str,
- v1.Url: str,
+ may_v1.Url: str,
AnyUrl: str,
- v1.AnyUrl: str,
+ may_v1.AnyUrl: str,
}
@@ -220,10 +220,10 @@ def jsonable_encoder(
include = set(include)
if exclude is not None and not isinstance(exclude, (set, dict)):
exclude = set(exclude)
- if isinstance(obj, (BaseModel, v1.BaseModel)):
+ if isinstance(obj, (BaseModel, may_v1.BaseModel)):
# TODO: remove when deprecating Pydantic v1
encoders: Dict[Any, Any] = {}
- if isinstance(obj, v1.BaseModel):
+ if isinstance(obj, may_v1.BaseModel):
encoders = getattr(obj.__config__, "json_encoders", {}) # type: ignore[attr-defined]
if custom_encoder:
encoders = {**encoders, **custom_encoder}
diff --git a/fastapi/temp_pydantic_v1_params.py b/fastapi/temp_pydantic_v1_params.py
index 0535ee727..e41d71230 100644
--- a/fastapi/temp_pydantic_v1_params.py
+++ b/fastapi/temp_pydantic_v1_params.py
@@ -5,8 +5,8 @@ from fastapi.openapi.models import Example
from fastapi.params import ParamTypes
from typing_extensions import Annotated, deprecated
+from ._compat.may_v1 import FieldInfo, Undefined
from ._compat.shared import PYDANTIC_VERSION_MINOR_TUPLE
-from ._compat.v1 import FieldInfo, Undefined
_Unset: Any = Undefined
diff --git a/fastapi/utils.py b/fastapi/utils.py
index 3ea9271b1..2e79ee6b1 100644
--- a/fastapi/utils.py
+++ b/fastapi/utils.py
@@ -25,7 +25,7 @@ from fastapi._compat import (
Validator,
annotation_is_pydantic_v1,
lenient_issubclass,
- v1,
+ may_v1,
)
from fastapi.datastructures import DefaultPlaceholder, DefaultType
from pydantic import BaseModel
@@ -87,8 +87,8 @@ def create_model_field(
) -> ModelField:
class_validators = class_validators or {}
- v1_model_config = v1.BaseConfig
- v1_field_info = field_info or v1.FieldInfo()
+ v1_model_config = may_v1.BaseConfig
+ v1_field_info = field_info or may_v1.FieldInfo()
v1_kwargs = {
"name": name,
"field_info": v1_field_info,
@@ -102,9 +102,11 @@ def create_model_field(
if (
annotation_is_pydantic_v1(type_)
- or isinstance(field_info, v1.FieldInfo)
+ or isinstance(field_info, may_v1.FieldInfo)
or version == "1"
):
+ from fastapi._compat import v1
+
try:
return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return]
except RuntimeError:
@@ -122,6 +124,8 @@ def create_model_field(
raise fastapi.exceptions.FastAPIError(_invalid_args_message) from None
# Pydantic v2 is not installed, but it's not a Pydantic v1 ModelField, it could be
# a Pydantic v1 type, like a constrained int
+ from fastapi._compat import v1
+
try:
return v1.ModelField(**v1_kwargs) # type: ignore[no-any-return]
except RuntimeError:
@@ -138,6 +142,9 @@ def create_cloned_field(
if isinstance(field, v2.ModelField):
return field
+
+ from fastapi._compat import v1
+
# cloned_types caches already cloned types to support recursive models and improve
# performance by avoiding unnecessary cloning
if cloned_types is None:
diff --git a/tests/test_compat.py b/tests/test_compat.py
index f79dbdabc..0184c9a2e 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -7,7 +7,7 @@ from fastapi._compat import (
get_cached_model_fields,
is_scalar_field,
is_uploadfile_sequence_annotation,
- v1,
+ may_v1,
)
from fastapi._compat.shared import is_bytes_sequence_annotation
from fastapi.testclient import TestClient
@@ -27,7 +27,10 @@ def test_model_field_default_required():
assert field.default is Undefined
+@needs_py_lt_314
def test_v1_plain_validator_function():
+ from fastapi._compat import v1
+
# For coverage
def func(v): # pragma: no cover
return v
@@ -135,6 +138,8 @@ def test_is_uploadfile_sequence_annotation():
@needs_py_lt_314
def test_is_pv1_scalar_field():
+ from fastapi._compat import v1
+
# For coverage
class Model(v1.BaseModel):
foo: Union[str, Dict[str, Any]]
@@ -143,8 +148,11 @@ def test_is_pv1_scalar_field():
assert not is_scalar_field(fields[0])
+@needs_py_lt_314
def test_get_model_fields_cached():
- class Model(v1.BaseModel):
+ from fastapi._compat import v1
+
+ class Model(may_v1.BaseModel):
foo: str
non_cached_fields = v1.get_model_fields(Model)
diff --git a/tests/test_get_model_definitions_formfeed_escape.py b/tests/test_get_model_definitions_formfeed_escape.py
index 439e6d448..6601585ef 100644
--- a/tests/test_get_model_definitions_formfeed_escape.py
+++ b/tests/test_get_model_definitions_formfeed_escape.py
@@ -5,7 +5,6 @@ import fastapi.openapi.utils
import pydantic.schema
import pytest
from fastapi import FastAPI
-from fastapi._compat import v1
from pydantic import BaseModel
from starlette.testclient import TestClient
@@ -165,6 +164,8 @@ def test_model_description_escaped_with_formfeed(sort_reversed: bool):
Test `get_model_definitions` with models passed in different order.
"""
+ from fastapi._compat import v1
+
all_fields = fastapi.openapi.utils.get_fields_from_routes(app.routes)
flat_models = v1.get_flat_models_from_fields(all_fields, known_models=set())
diff --git a/tests/test_response_model_as_return_annotation.py b/tests/test_response_model_as_return_annotation.py
index c3c0ed6c4..1745c69b6 100644
--- a/tests/test_response_model_as_return_annotation.py
+++ b/tests/test_response_model_as_return_annotation.py
@@ -2,12 +2,13 @@ from typing import List, Union
import pytest
from fastapi import FastAPI
-from fastapi._compat import v1
from fastapi.exceptions import FastAPIError, ResponseValidationError
from fastapi.responses import JSONResponse, Response
from fastapi.testclient import TestClient
from pydantic import BaseModel
+from tests.utils import needs_pydanticv1
+
class BaseUser(BaseModel):
name: str
@@ -511,7 +512,10 @@ def test_invalid_response_model_field():
# TODO: remove when dropping Pydantic v1 support
+@needs_pydanticv1
def test_invalid_response_model_field_pv1():
+ from fastapi._compat import v1
+
app = FastAPI()
class Model(v1.BaseModel):