Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: return_dto is silently ignored if return data type does not match DTO definition #3485

Open
1 of 4 tasks
maxalbert opened this issue May 10, 2024 · 1 comment
Open
1 of 4 tasks
Labels
Bug 🐛 This is something that is not working as expected

Comments

@maxalbert
Copy link

Description

Below is a minimal example that showcases the issue.

The context in which this came up for me is that I'm trying to port an existing app which uses FastAPI/SQLModel to Litestar. As illustrated in the SQLModel docs (for example here), I have several pydantic models for read/write/db operations - most of which I would like to ditch in favour of Litestar's DTOs. I accidentally defined my return DTOs using the wrong pydantic model and things weren't working as expected - it would simply ignore the DTO and return the full, unfiltered data.

Since I'm new to the concept of DTOs it took me a while to figure out what the cause of the issue was. For a new user like me it would be very helpful to have Litestar print a prominent warning (if not abort with an error).

URL to code causing the issue

No response

MCVE

from pydantic import BaseModel

from litestar import Litestar, get
from litestar.dto import DTOConfig
from litestar.contrib.pydantic import PydanticDTO


class MyMessage(BaseModel):
    msg: str
    priority: int


class MyMessage2(BaseModel):
    msg: str
    priority: int


class ReadDTO(PydanticDTO[MyMessage]):
    config = DTOConfig(exclude={"priority"})


@get("/", return_dto=ReadDTO)
async def hello_world() -> MyMessage:
    """Handler function that returns a greeting dictionary."""
    return MyMessage(msg="Hello world", priority=1)


app = Litestar(route_handlers=[hello_world])

Steps to reproduce

1. Save the above MCVE as `app.py` and run it via `litestar run`
2. `curl http://127.0.0.1:8008` returns the expected output: `{"msg":"Hello world"}` (note that the `priority` field is correctly stripped by the `return_dto`).
3. In the definition of `ReadDTO`, change `PydanticDTO[MyMessage]` to `PydanticDTO[MyMessage2]`.
4. `curl http://127.0.0.1:8008` now returns the _full_ dictionary including the `priority` field: `{"msg": "Hello world", "priority": 1}`.

This is of course due to the fact that the `hello_world` handler constructs an instance of `MyMessage` while the return DTO is parameterised on `MyMessage2`.

However, there is no warning or any indication that there is a mismatch. Instead, the return DTO is just silently ignored and the full data is returned.

Screenshots

"![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"

Logs

No response

Litestar Version

$ litestar version
2.8.3final0

Platform

  • Linux
  • Mac
  • Windows
  • Other (Please specify in the description above)
@maxalbert maxalbert added the Bug 🐛 This is something that is not working as expected label May 10, 2024
@peterschutt
Copy link
Contributor

This isn't a bug, as DTOs can be defined on layers, and it would be quite restrictive to only allow routes beneath a layer that has a dto defined, to only return that DTO supported type. For example:

class MyController(Controller):
    return_dto = SomeDTO[SomeType]

    @get("/{id:int}")
    def get_handler(self) -> SomeType: ...

    @get("/")
    def list_handler(self) -> list[SomeType]: ...

    @delete("/{id:int}")
    def delete_handler(self) -> None: ...

In that example, the delete handler not returning a model instance shouldn't force the user to have to define the dto individually on all of the other handlers.

I can perhaps see some utility to raising an error if a dto is defined directly on a handler as in your example, and that handler doesn't utilise it - but strictly speaking, having a dto available for a handler, but not required to be used is a design decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug 🐛 This is something that is not working as expected
Projects
None yet
Development

No branches or pull requests

2 participants