Repository: litestar-org/litestar

Issue:
DTO Interface: consider removing support for declaring the DTO as data/return type
This is mostly a feature hangover from when the DTO types _were_ pydantic models, and were relying on being in the signature model. 

Now that they are declarable at the layers, I'm not totally sure if having this support is necessary: less is more, can always be added later, want to hear actual use-cases for it, and all that.


Discussion:

Resolving patch:
diff --git a/litestar/_kwargs/extractors.py b/litestar/_kwargs/extractors.py
--- a/litestar/_kwargs/extractors.py
+++ b/litestar/_kwargs/extractors.py
@@ -11,7 +11,6 @@
     parse_url_encoded_form_data,
 )
 from litestar.datastructures.upload_file import UploadFile
-from litestar.dto.interface import DTOInterface
 from litestar.enums import ParamType, RequestEncodingType
 from litestar.exceptions import ValidationException
 from litestar.params import BodyKwarg
@@ -22,7 +21,7 @@
     from litestar._kwargs.parameter_definition import ParameterDefinition
     from litestar._signature.field import SignatureField
     from litestar.connection import ASGIConnection, Request
-    from litestar.utils.signature import ParsedParameter
+    from litestar.dto.interface import DTOInterface
 
 __all__ = (
     "body_extractor",
@@ -383,7 +382,7 @@ def create_data_extractor(kwargs_model: KwargsModel) -> Callable[[dict[str, Any]
             "Callable[[ASGIConnection[Any, Any, Any, Any]], Coroutine[Any, Any, Any]]", msgpack_extractor
         )
     elif kwargs_model.expected_dto_data:
-        data_extractor = create_dto_extractor(*kwargs_model.expected_dto_data)
+        data_extractor = create_dto_extractor(kwargs_model.expected_dto_data)
     else:
         data_extractor = cast(
             "Callable[[ASGIConnection[Any, Any, Any, Any]], Coroutine[Any, Any, Any]]", json_extractor
@@ -399,23 +398,18 @@ def extractor(
 
 
 def create_dto_extractor(
-    parsed_parameter: ParsedParameter, dto_type: type[DTOInterface]
+    dto_type: type[DTOInterface],
 ) -> Callable[[ASGIConnection[Any, Any, Any, Any]], Coroutine[Any, Any, Any]]:
     """Create a DTO data extractor.
 
     Args:
-        parsed_parameter: :class:`ParsedParameter` instance representing the ``"data"`` kwarg.
         dto_type: The :class:`DTOInterface` subclass.
 
     Returns:
         An extractor function.
     """
-    is_dto_annotated = parsed_parameter.parsed_type.is_subclass_of(DTOInterface)
 
     async def dto_extractor(connection: Request[Any, Any, Any]) -> Any:
-        dto = dto_type.from_bytes(await connection.body(), connection)
-        if is_dto_annotated:
-            return dto
-        return dto.to_data_type()
+        return dto_type.from_bytes(await connection.body(), connection).to_data_type()
 
     return dto_extractor  # type:ignore[return-value]
diff --git a/litestar/_kwargs/kwargs_model.py b/litestar/_kwargs/kwargs_model.py
--- a/litestar/_kwargs/kwargs_model.py
+++ b/litestar/_kwargs/kwargs_model.py
@@ -32,7 +32,6 @@
 from litestar._signature import SignatureModel, get_signature_model
 from litestar._signature.field import SignatureField
 from litestar.constants import RESERVED_KWARGS
-from litestar.dto.interface import DTOInterface
 from litestar.enums import ParamType, RequestEncodingType
 from litestar.exceptions import ImproperlyConfiguredException
 from litestar.params import BodyKwarg, ParameterKwarg
@@ -43,7 +42,8 @@
 if TYPE_CHECKING:
     from litestar.connection import ASGIConnection
     from litestar.di import Provide
-    from litestar.utils.signature import ParsedParameter, ParsedSignature
+    from litestar.dto.interface import DTOInterface
+    from litestar.utils.signature import ParsedSignature
 
 
 class KwargsModel:
@@ -72,7 +72,7 @@ def __init__(
         self,
         *,
         expected_cookie_params: set[ParameterDefinition],
-        expected_dto_data: tuple[ParsedParameter, type[DTOInterface]] | None,
+        expected_dto_data: type[DTOInterface] | None,
         expected_dependencies: set[Dependency],
         expected_form_data: tuple[RequestEncodingType | str, SignatureField] | None,
         expected_msgpack_data: SignatureField | None,
@@ -304,7 +304,7 @@ def create_for_signature_model(
 
         expected_form_data: tuple[RequestEncodingType | str, SignatureField] | None = None
         expected_msgpack_data: SignatureField | None = None
-        expected_dto_data: tuple[ParsedParameter, type[DTOInterface]] | None = None
+        expected_dto_data: type[DTOInterface] | None = None
 
         data_signature_field = signature_fields.get("data")
 
@@ -322,13 +322,8 @@ def create_for_signature_model(
             elif media_type == RequestEncodingType.MESSAGEPACK:
                 expected_msgpack_data = data_signature_field
 
-        elif data_signature_field:
-            parsed_parameter = parsed_signature.parameters["data"]
-            parsed_type = parsed_parameter.parsed_type
-            if parsed_type.is_subclass_of(DTOInterface):
-                expected_dto_data = (parsed_parameter, parsed_type.annotation)
-            elif data_dto:
-                expected_dto_data = (parsed_parameter, data_dto)
+        elif data_signature_field and data_dto:
+            expected_dto_data = data_dto
 
         for dependency in expected_dependencies:
             dependency_kwargs_model = cls.create_for_signature_model(


Test patch:
diff --git a/tests/dto/test_integration.py b/tests/dto/test_integration.py
--- a/tests/dto/test_integration.py
+++ b/tests/dto/test_integration.py
@@ -86,15 +86,3 @@ def handler(data: Model) -> Model:
         response = client.post("/", json={"what": "ever"})
         assert response.status_code == 201
         assert response.json() == {"a": 1, "b": "2"}
-
-
-def test_dto_annotated_handler() -> None:
-    @post()
-    def handler(data: MockDTO) -> MockDTO:
-        assert isinstance(data, MockDTO)
-        return data
-
-    with create_test_client(route_handlers=handler) as client:
-        response = client.post("/", json={"what": "ever"})
-        assert response.status_code == 201
-        assert response.json() == {"a": 1, "b": "2"}
diff --git a/tests/kwargs/test_dto_extractor.py b/tests/kwargs/test_dto_extractor.py
--- a/tests/kwargs/test_dto_extractor.py
+++ b/tests/kwargs/test_dto_extractor.py
@@ -3,26 +3,9 @@
 from unittest.mock import AsyncMock
 
 from litestar._kwargs.extractors import create_dto_extractor
-from litestar.types.empty import Empty
-from litestar.utils.signature import ParsedParameter, ParsedType
 from tests.dto import MockDTO, Model
 
 
-async def test_create_dto_extractor_not_dto_annotated() -> None:
-    parsed_parameter = ParsedParameter(
-        name="data",
-        default=Empty,
-        parsed_type=ParsedType.from_annotation(Model),
-    )
-    extractor = create_dto_extractor(parsed_parameter, MockDTO)
+async def test_create_dto_extractor() -> None:
+    extractor = create_dto_extractor(MockDTO)
     assert await extractor(AsyncMock()) == Model(a=1, b="2")
-
-
-async def test_create_dto_extractor_dto_annotated() -> None:
-    parsed_parameter = ParsedParameter(
-        name="data",
-        default=Empty,
-        parsed_type=ParsedType.from_annotation(MockDTO),
-    )
-    extractor = create_dto_extractor(parsed_parameter, MockDTO)
-    assert isinstance(await extractor(AsyncMock()), MockDTO)