Browse Source

Add missing RequestValidationError.__str__

`PYDANTIC_V2` moved from `_compat` to `exceptions` to avoid duplication
(`exceptions` can't import `_compat` due to circular imports).

Fixes #12125.
pull/12972/head
Tamir Duberstein 8 months ago
parent
commit
5661455f7e
Failed to extract signature
  1. 15
      fastapi/_compat.py
  2. 27
      fastapi/exceptions.py
  3. 26
      tests/test_tutorial/test_handling_errors/test_tutorial004.py

15
fastapi/_compat.py

@ -19,17 +19,20 @@ from typing import (
cast, cast,
) )
from fastapi.exceptions import RequestErrorModel from fastapi.exceptions import (
PYDANTIC_V2 as PYDANTIC_V2,
)
from fastapi.exceptions import (
PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE,
)
from fastapi.exceptions import (
RequestErrorModel,
)
from fastapi.types import IncEx, ModelNameMap, UnionType from fastapi.types import IncEx, ModelNameMap, UnionType
from pydantic import BaseModel, create_model from pydantic import BaseModel, create_model
from pydantic.version import VERSION as PYDANTIC_VERSION
from starlette.datastructures import UploadFile from starlette.datastructures import UploadFile
from typing_extensions import Annotated, Literal, get_args, get_origin from typing_extensions import Annotated, Literal, get_args, get_origin
PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
sequence_annotation_to_type = { sequence_annotation_to_type = {
Sequence: list, Sequence: list,
List: list, List: list,

27
fastapi/exceptions.py

@ -1,10 +1,27 @@
from typing import Any, Dict, Optional, Sequence, Type, Union from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Type, Union, cast
from pydantic import BaseModel, create_model from pydantic import BaseModel, create_model
from pydantic.version import VERSION as PYDANTIC_VERSION
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.exceptions import WebSocketException as StarletteWebSocketException from starlette.exceptions import WebSocketException as StarletteWebSocketException
from typing_extensions import Annotated, Doc from typing_extensions import Annotated, Doc
PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2])
PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
if PYDANTIC_V2:
from pydantic.v1.error_wrappers import display_errors
if TYPE_CHECKING: # pragma: nocover
from pydantic.v1.error_wrappers import ErrorDict
else:
from pydantic.error_wrappers import ( # type: ignore[no-redef]
display_errors,
)
if TYPE_CHECKING: # pragma: nocover
from pydantic.error_wrappers import ErrorDict # type: ignore[no-redef]
class HTTPException(StarletteHTTPException): class HTTPException(StarletteHTTPException):
""" """
@ -159,6 +176,14 @@ class RequestValidationError(ValidationException):
super().__init__(errors) super().__init__(errors)
self.body = body self.body = body
def __str__(self) -> str:
errors = cast(List["ErrorDict"], self.errors())
no_errors = len(errors)
return (
f"{no_errors} validation error{'' if no_errors == 1 else 's'}\n"
f"{display_errors(errors)}"
)
class WebSocketRequestValidationError(ValidationException): class WebSocketRequestValidationError(ValidationException):
pass pass

26
tests/test_tutorial/test_handling_errors/test_tutorial004.py

@ -1,3 +1,4 @@
from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from docs_src.handling_errors.tutorial004 import app from docs_src.handling_errors.tutorial004 import app
@ -8,18 +9,19 @@ client = TestClient(app)
def test_get_validation_error(): def test_get_validation_error():
response = client.get("/items/foo") response = client.get("/items/foo")
assert response.status_code == 400, response.text assert response.status_code == 400, response.text
# TODO: remove when deprecating Pydantic v1 if PYDANTIC_VERSION_MINOR_TUPLE < (2, 0):
assert ( assert response.text == (
# TODO: remove when deprecating Pydantic v1 "1 validation error\n"
"path -> item_id" in response.text "path -> item_id\n"
or "'loc': ('path', 'item_id')" in response.text " value is not a valid integer (type=type_error.integer)"
) )
assert ( else:
# TODO: remove when deprecating Pydantic v1 assert response.text == (
"value is not a valid integer" in response.text "1 validation error\n"
or "Input should be a valid integer, unable to parse string as an integer" "path -> item_id\n"
in response.text " Input should be a valid integer, unable to parse string as an integer "
) "(type=int_parsing)"
)
def test_get_http_error(): def test_get_http_error():

Loading…
Cancel
Save