Skip to content

Commit 174bc0f

Browse files
authored
Merge branch 'litestar-org:main' into sse_timeout_events
2 parents 0b5f8f0 + a98d958 commit 174bc0f

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

litestar/dto/base_dto.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from litestar.dto.types import RenameStrategy
1616
from litestar.enums import RequestEncodingType
1717
from litestar.exceptions.dto_exceptions import InvalidAnnotationException
18+
from litestar.response.base import Response
1819
from litestar.types.builtin_types import NoneType
1920
from litestar.types.composite_types import TypeEncodersMap
2021
from litestar.typing import FieldDefinition
@@ -229,7 +230,7 @@ def create_openapi_schema(
229230
key = "data_backend" if field_definition.name == "data" else "return_backend"
230231
backend = cls._dto_backends[handler_id][key] # type: ignore[literal-required]
231232

232-
if backend.wrapper_attribute_name:
233+
if backend.wrapper_attribute_name and not field_definition.is_subclass_of(Response):
233234
# The DTO has been built for a handler that has a DTO supported type wrapped in a generic type.
234235
#
235236
# The backend doesn't receive the full annotation, only the type of the attribute on the outer type that
@@ -238,7 +239,7 @@ def create_openapi_schema(
238239
# This special casing rebuilds the outer generic type annotation with the original model replaced by the DTO
239240
# generated transfer model type in the type arguments.
240241
transfer_model = backend.transfer_model_type
241-
generic_args = tuple(transfer_model if a is cls.model_type else a for a in field_definition.args)
242+
generic_args = tuple(transfer_model if arg is cls.model_type else arg for arg in field_definition.args)
242243
annotation = field_definition.safe_generic_origin[generic_args]
243244
else:
244245
annotation = backend.annotation

tests/unit/test_dto/test_factory/test_integration.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,11 @@ def handler() -> Response[PaginatedUser]:
697697
response = client.get("/")
698698
assert response.json() == {"name": "John"}
699699

700+
schema_response = client.get("/schema/openapi.json")
701+
schemas = list(schema_response.json()["components"]["schemas"].values())
702+
assert len(schemas) == 1
703+
assert schemas[0]["title"] == "HandlerPaginatedUserResponseBody"
704+
700705

701706
def test_dto_response_wrapped_collection_return_type(use_experimental_dto_backend: bool) -> None:
702707
@get(
@@ -713,6 +718,11 @@ def handler() -> Response[List[PaginatedUser]]:
713718
response = client.get("/")
714719
assert response.json() == [{"name": "John"}, {"name": "Jane"}]
715720

721+
schema_response = client.get("/schema/openapi.json")
722+
schemas = list(schema_response.json()["components"]["schemas"].values())
723+
assert len(schemas) == 1
724+
assert schemas[0]["title"] == "HandlerPaginatedUserResponseBody"
725+
716726

717727
def test_schema_required_fields_with_msgspec_dto(use_experimental_dto_backend: bool) -> None:
718728
class MsgspecUser(Struct):
@@ -1009,6 +1019,46 @@ def get_users() -> WithCount[User]:
10091019
assert not_none(model_schema.properties).keys() == {"id", "name"}
10101020

10111021

1022+
def test_openapi_schema_for_type_with_litestar_response_generic_type(
1023+
create_module: Callable[[str], ModuleType], use_experimental_dto_backend: bool
1024+
) -> None:
1025+
module = create_module("""
1026+
from dataclasses import dataclass
1027+
from typing import List
1028+
1029+
from litestar import Litestar, Response, get
1030+
from litestar.dto import DataclassDTO, DTOConfig
1031+
1032+
1033+
@dataclass
1034+
class Item:
1035+
id: int
1036+
name: str
1037+
secret: str
1038+
1039+
1040+
class ItemReadDTO(DataclassDTO[Item]):
1041+
config = DTOConfig(exclude=["secret"])
1042+
1043+
1044+
@get("/get_items", return_dto=ItemReadDTO)
1045+
async def get_items() -> Response[List[Item]]:
1046+
return Response(
1047+
headers={"X-SomeParam": "SomeValue"},
1048+
content=[
1049+
Item(id=1, name="Item 1", secret="123"),
1050+
Item(id=2, name="Item 2", secret="456"),
1051+
],
1052+
)
1053+
1054+
app = Litestar(route_handlers=[get_items])
1055+
""")
1056+
1057+
openapi = cast("Litestar", module.app).openapi_schema
1058+
schema = openapi.components.schemas["GetItemsItemResponseBody"]
1059+
assert not_none(schema.properties).keys() == {"id", "name"}
1060+
1061+
10121062
def test_openapi_schema_for_dto_includes_body_examples(create_module: Callable[[str], ModuleType]) -> None:
10131063
module = create_module(
10141064
"""

0 commit comments

Comments
 (0)