litestar-org / litestar

Production-ready, Light, Flexible and Extensible ASGI API framework | Effortlessly Build Performant APIs
https://litestar.dev/
MIT License
5.53k stars 377 forks source link

Enhancement: Support attrs converters #3680

Closed jeffcarrico closed 2 months ago

jeffcarrico commented 2 months ago

Description

Attrs has an attribute conversion feature where a function can be provided to be run whenever the attribute value is set.

https://www.attrs.org/en/stable/examples.html#conversion

This is not being run when the post request body is an attrs class. Looking for clarity on if this is by design or a fix if not. Also, is there another preferred pattern in litestar to do this type of payload attribute conversion?

URL to code causing the issue

No response

MCVE

requirements.txt

litestar[standard,attrs]==2.10.0

api.py

from __future__ import annotations

import attrs
from litestar import (
    Litestar,
    post,
)

@attrs.define
class TestRequestPayload:
    a: str = attrs.field(converter=str.lower)
    b: str = attrs.field(converter=str.lower)

@post("/test1")
async def test1(data: TestRequestPayload) -> dict:
    return attrs.asdict(data)

@post("/test2")
async def test2(data: TestRequestPayload) -> dict:
    # Naked evolve basically re-instantiates the object causing the converters to be run
    return attrs.asdict(attrs.evolve(data))

app = Litestar(
    route_handlers=[test1, test2],
)

Steps to reproduce

  1. litestar --app api:app run
  2. Make a request to the test1 route, note data response unchanged from input
    $ curl --request POST localhost:8000/test1 --data '{"a": "Hello", "b": "World"}' ; echo
    {"a":"Hello","b":"World"}
  3. Make a request to the test2 route, note data response lowercased relative to input
    $ curl --request POST localhost:8000/test2 --data '{"a": "Hello", "b": "World"}' ; echo
    {"a":"hello","b":"world"}

Screenshots

No response

Logs

No response

Litestar Version

2.10.0

Platform


[!NOTE]
While we are open for sponsoring on GitHub Sponsors and OpenCollective, we also utilize Polar.sh to engage in pledge-based sponsorship.

Check out all issues funded or available for funding on our Polar.sh dashboard

  • If you would like to see an issue prioritized, make a pledge towards it!
  • We receive the pledge once the issue is completed & verified
  • This, along with engagement in the community, helps us know which features are a priority to our users.

Fund with Polar

provinzkraut commented 2 months ago

hey @jeffcarrico! This is a known limitation of msgspec, which Litestar uses to provide (part of) its attrs support.

There's an open issue over at their repo for this as well.

The partial msgspec support however means that it's hard for us to work around this, as msgspec doesn't give us a way to hook into the de/encoding process there for types it supports natively.

I think as of now, the workaround you're using in your test2 is the best solution.