Skip to content

Commit

Permalink
test: Remove Pydantic as default model from tests (#2458)
Browse files Browse the repository at this point in the history
* Remove Pydantic as default model from tests

---------

Signed-off-by: Janek Nouvertné <[email protected]>
  • Loading branch information
provinzkraut authored Oct 21, 2023
1 parent 5fd40c1 commit 56eaeeb
Show file tree
Hide file tree
Showing 22 changed files with 368 additions and 376 deletions.
5 changes: 2 additions & 3 deletions litestar/testing/request_factory.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import json
from dataclasses import asdict
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import urlencode
Expand Down Expand Up @@ -288,9 +289,7 @@ def _create_request_with_data(

data = attrs_as_dict(data) # type: ignore[arg-type]
elif is_pydantic_model_instance(data):
from litestar.contrib.pydantic import _model_dump

data = _model_dump(data)
data = data.model_dump(mode="json") if hasattr(data, "model_dump") else json.loads(data.json())

if request_media_type == RequestEncodingType.JSON:
encoding_headers, stream = httpx_encode_json(data)
Expand Down
105 changes: 0 additions & 105 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,105 +0,0 @@
from dataclasses import dataclass as vanilla_dataclass
from enum import Enum
from typing import Dict, List, Optional
from uuid import UUID

import attrs
import msgspec
from polyfactory.factories.pydantic_factory import ModelFactory
from pydantic import BaseModel, Field
from pydantic.dataclasses import dataclass as pydantic_dataclass
from typing_extensions import NotRequired, Required, TypedDict

from litestar.contrib.pydantic import PydanticDTO
from litestar.dto import DTOConfig


class Species(str, Enum):
DOG = "Dog"
CAT = "Cat"
MONKEY = "Monkey"
PIG = "Pig"


class PydanticPet(BaseModel):
name: str
species: Species = Field(default=Species.MONKEY)
age: float


class PydanticPerson(BaseModel):
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[PydanticPet]] = None


class PydanticPersonFactory(ModelFactory[PydanticPerson]):
__model__ = PydanticPerson


class PydanticPetFactory(ModelFactory[PydanticPet]):
__model__ = PydanticPet


class PartialPersonDTO(PydanticDTO[PydanticPerson]):
config = DTOConfig(partial=True)


@vanilla_dataclass
class VanillaDataClassPerson:
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[PydanticPet]] = None


@pydantic_dataclass
class PydanticDataClassPerson:
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[PydanticPet]] = None


class TypedDictPerson(TypedDict):
first_name: Required[str]
last_name: Required[str]
id: Required[str]
optional: NotRequired[Optional[str]]
complex: Required[Dict[str, List[Dict[str, str]]]]
pets: NotRequired[Optional[List[PydanticPet]]]


@attrs.define
class AttrsPerson:
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[PydanticPet]]


class MsgSpecStructPerson(msgspec.Struct):
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[PydanticPet]]


class User(BaseModel):
name: str
id: UUID


class UserFactory(ModelFactory[User]):
__model__ = User
11 changes: 3 additions & 8 deletions tests/e2e/test_routing/test_path_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
import pytest

from litestar import Controller, MediaType, Router, delete, get, post
from litestar.contrib.pydantic import _model_dump
from litestar.status_codes import (
HTTP_200_OK,
HTTP_204_NO_CONTENT,
HTTP_404_NOT_FOUND,
HTTP_405_METHOD_NOT_ALLOWED,
)
from litestar.testing import create_test_client
from tests import PydanticPerson, PydanticPersonFactory


@delete(sync_to_thread=False)
Expand Down Expand Up @@ -94,19 +92,16 @@ def mixed_params(path_param: int, value: int) -> str:
def test_root_route_handler(
decorator: Type[get], test_path: str, decorator_path: str, delete_handler: Optional[Callable]
) -> None:
person_instance = PydanticPersonFactory.build()

class MyController(Controller):
path = test_path

@decorator(path=decorator_path)
def test_method(self) -> PydanticPerson:
return person_instance
def test_method(self) -> str:
return "hello"

with create_test_client([MyController, delete_handler] if delete_handler else MyController) as client:
response = client.get(decorator_path or test_path)
assert response.status_code == HTTP_200_OK, response.json()
assert response.json() == _model_dump(person_instance)
assert response.status_code == HTTP_200_OK
if delete_handler:
delete_response = client.delete("/")
assert delete_response.status_code == HTTP_204_NO_CONTENT
Expand Down
100 changes: 100 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import dataclasses
from enum import Enum
from typing import Dict, List, Optional
from uuid import UUID

import attrs
import msgspec
from polyfactory.factories import DataclassFactory
from pydantic import BaseModel
from pydantic.dataclasses import dataclass as pydantic_dataclass
from typing_extensions import NotRequired, Required, TypedDict


class Species(str, Enum):
DOG = "Dog"
CAT = "Cat"
MONKEY = "Monkey"
PIG = "Pig"


@dataclasses.dataclass
class DataclassPet:
name: str
age: float
species: Species = Species.MONKEY


@dataclasses.dataclass
class DataclassPerson:
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[DataclassPet]] = None


@pydantic_dataclass
class PydanticDataclassPerson:
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[DataclassPet]] = None


class TypedDictPerson(TypedDict):
first_name: Required[str]
last_name: Required[str]
id: Required[str]
optional: NotRequired[Optional[str]]
complex: Required[Dict[str, List[Dict[str, str]]]]
pets: NotRequired[Optional[List[DataclassPet]]]


class PydanticPerson(BaseModel):
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[DataclassPet]] = None


@attrs.define
class AttrsPerson:
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[DataclassPet]]


class MsgSpecStructPerson(msgspec.Struct):
first_name: str
last_name: str
id: str
optional: Optional[str]
complex: Dict[str, List[Dict[str, str]]]
pets: Optional[List[DataclassPet]]


@dataclasses.dataclass
class User:
name: str
id: UUID


class UserFactory(DataclassFactory[User]):
__model__ = User


class DataclassPersonFactory(DataclassFactory[DataclassPerson]):
__model__ = DataclassPerson


class DataclassPetFactory(DataclassFactory[DataclassPet]):
__model__ = DataclassPet
16 changes: 1 addition & 15 deletions tests/unit/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

import pytest
from click import Group
from pydantic import VERSION
from pytest import MonkeyPatch

from litestar import Litestar, MediaType, Request, Response, get, post
from litestar import Litestar, MediaType, Request, Response, get
from litestar.config.app import AppConfig
from litestar.config.response_cache import ResponseCacheConfig
from litestar.contrib.sqlalchemy.plugins import SQLAlchemySerializationPlugin
Expand All @@ -29,7 +28,6 @@
from litestar.router import Router
from litestar.status_codes import HTTP_200_OK, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR
from litestar.testing import TestClient, create_test_client
from tests import PydanticPerson

if TYPE_CHECKING:
from typing import Dict
Expand Down Expand Up @@ -244,18 +242,6 @@ def on_startup(app: Litestar) -> None:
assert response.headers.get("My Header") == "value injected during send"


def test_default_handling_of_pydantic_errors() -> None:
@post("/{param:int}")
def my_route_handler(param: int, data: PydanticPerson) -> None:
...

with create_test_client(my_route_handler) as client:
response = client.post("/123", json={"first_name": "moishe"})
extra = response.json().get("extra")
assert extra is not None
assert 3 if len(extra) == VERSION.startswith("1") else 4


def test_using_custom_http_exception_handler() -> None:
@get("/{param:int}")
def my_route_handler(param: int) -> None:
Expand Down
7 changes: 4 additions & 3 deletions tests/unit/test_contrib/test_jwt/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING, Any, Dict, Optional
from uuid import uuid4

import msgspec
import pytest
from hypothesis import given, settings
from hypothesis.strategies import dictionaries, integers, none, one_of, sampled_from, text, timedeltas
Expand All @@ -14,7 +15,7 @@
from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED, HTTP_401_UNAUTHORIZED
from litestar.stores.memory import MemoryStore
from litestar.testing import create_test_client
from tests import User, UserFactory
from tests.models import User, UserFactory

if TYPE_CHECKING:
from litestar.connection import ASGIConnection
Expand Down Expand Up @@ -75,7 +76,7 @@ async def retrieve_user_handler(token: Token, _: "ASGIConnection") -> Any:
@get("/my-endpoint", middleware=[jwt_auth.middleware])
def my_handler(request: Request["User", Token, Any]) -> None:
assert request.user
assert _model_dump(request.user) == _model_dump(user)
assert msgspec.to_builtins(request.user) == msgspec.to_builtins(user)
assert request.auth.sub == str(user.id)

@get("/login")
Expand Down Expand Up @@ -183,7 +184,7 @@ async def retrieve_user_handler(token: Token, connection: Any) -> Any:
@get("/my-endpoint", middleware=[jwt_auth.middleware])
def my_handler(request: Request["User", Token, Any]) -> None:
assert request.user
assert _model_dump(request.user) == _model_dump(user)
assert msgspec.to_builtins(request.user) == msgspec.to_builtins(user)
assert request.auth.sub == str(user.id)

@get("/login")
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_contrib/test_pydantic/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from litestar import post
from litestar.contrib.pydantic.pydantic_dto_factory import PydanticDTO
from litestar.testing import create_test_client
from tests.models import PydanticPerson


def test_pydantic_validation_error_raises_400() -> None:
Expand Down Expand Up @@ -54,3 +55,15 @@ def handler(data: Model) -> Model:
extra[0].pop("url")

assert extra == expected_errors


def test_default_handling_of_pydantic_errors() -> None:
@post("/{param:int}")
def my_route_handler(param: int, data: PydanticPerson) -> None:
...

with create_test_client(my_route_handler) as client:
response = client.post("/123", json={"first_name": "moishe"})
extra = response.json().get("extra")
assert extra is not None
assert 3 if len(extra) == VERSION.startswith("1") else 4
Loading

0 comments on commit 56eaeeb

Please sign in to comment.