Skip to content

Commit

Permalink
fix(validation): fix handling validation of subscribed generics (#3519)
Browse files Browse the repository at this point in the history
fix handling validation of subscribed generics
  • Loading branch information
provinzkraut authored May 24, 2024
1 parent b6a640a commit 9a3bd38
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 6 deletions.
13 changes: 10 additions & 3 deletions litestar/_signature/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from litestar.exceptions import InternalServerException, ValidationException
from litestar.params import KwargDefinition, ParameterKwarg
from litestar.typing import FieldDefinition # noqa
from litestar.utils import is_class_and_subclass
from litestar.utils import get_origin_or_inner_type, is_class_and_subclass
from litestar.utils.dataclass import simple_asdict

if TYPE_CHECKING:
Expand Down Expand Up @@ -85,8 +85,15 @@ def _deserializer(target_type: Any, value: Any, default_deserializer: Callable[[
if isinstance(value, DTOData):
return value

if isinstance(value, target_type):
return value
try:
if isinstance(value, target_type):
return value
except TypeError as exc:
if (origin := get_origin_or_inner_type(target_type)) is not None:
if isinstance(value, origin):
return value
else:
raise exc

if decoder := getattr(target_type, "_decoder", None):
return decoder(target_type, value)
Expand Down
16 changes: 14 additions & 2 deletions litestar/serialization/msgspec_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from litestar.datastructures.secret_values import SecretBytes, SecretString
from litestar.exceptions import SerializationException
from litestar.types import Empty, EmptyType, Serializer, TypeDecodersSequence
from litestar.utils.typing import get_origin_or_inner_type

if TYPE_CHECKING:
from litestar.types import TypeEncodersMap
Expand Down Expand Up @@ -107,8 +108,19 @@ def default_deserializer(

from litestar.datastructures.state import ImmutableState

if isinstance(value, target_type):
return value
try:
if isinstance(value, target_type):
return value
except TypeError as exc:
# we might get a TypeError here if target_type is a subscribed generic. For
# performance reasons, we let this happen and only unwrap this when we're
# certain this might be the case
if (origin := get_origin_or_inner_type(target_type)) is not None:
target_type = origin
if isinstance(value, target_type):
return value
else:
raise exc

if type_decoders:
for predicate, decoder in type_decoders:
Expand Down
16 changes: 15 additions & 1 deletion tests/unit/test_signature/test_validation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import List, Optional
from typing import Generic, List, Optional, TypeVar

import pytest
from attr import define
Expand Down Expand Up @@ -289,3 +289,17 @@ def fn(a: Annotated[int, Parameter(gt=5)], b: Annotated[int, Parameter(lt=5)]) -
{"message": "Expected `int` >= 6", "key": "a", "source": ParamType.QUERY},
{"message": "Expected `int` <= 4", "key": "b", "source": ParamType.QUERY},
]


def test_validate_subscribed_generics() -> None:
T = TypeVar("T")

class Foo(Generic[T]):
pass

@get("/")
async def something(foo: Foo[str] = Foo()) -> None:
return None

with create_test_client([something]) as client:
assert client.get("/").status_code == 200

0 comments on commit 9a3bd38

Please sign in to comment.