litestar-org / litestar

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

Enhancement: route level option to use model alias when serialising #2260

Closed skewty closed 1 year ago

skewty commented 1 year ago

Summary

Each route should have an option to specify if alias should be used when serialising (applies to pydantic for sure, perhaps other). If route level is too troublesome, global would suffice for my needs.

I know I can use:

Starlite(type_encoder={BaseModel: lambda m: json.loads(m.json(alias=True))})

but that is clearly not good for performance and is global.

I cannot use field renaming strategies because, believe it or not, I have to interface with some systems that use PascalCase, camelCase and snake_case all within a same (nested) model.

I found: starlite.config.OpenAPIConfig which has a by_alias=True by default but that seems to be ignored when returning pydantic models. (Perhaps this is a bug?)

PS: I worked with "cofin" on Discord regarding this issue. He was super helpful and great to work with. Thanks again cofin for all the energy you put into this!

Basic Example

@get("/api/v1/apartment", by_alias=True)
async def get_room() -> Room:
    return await data_adapter.get_room(1)

Drawbacks and Impact

Likely additional parameters that may not be supported would need to be carried for non pydantic models.

Should be no drawbacks or impacts for the pydantic code path as model.json() is already being called.

Unresolved questions

What is the point of starlite.config.OpenAPIConfig.by_alias in your JSON schema if your returned data never conforms to it?


Funding

Fund with Polar

skewty commented 1 year ago
# Just tried pydantic v2 and it supports this using:
Starlite(type_encoder={BaseModel: lambda m: m.model_dump(mode="json", by_alias=True)})
skewty commented 1 year ago

Perhaps since most will be moving to pydantic v2 this isn't so important anymore and can / should be closed?

provinzkraut commented 1 year ago
# Just tried pydantic v2 and it supports this using:
Starlite(type_encoder={BaseModel: lambda m: m.model_dump(mode="json", by_alias=True)})

The same thing works for Pydantic 1 as well. No need to do the roundtrip like in your opening post:

Starlite(type_encoder={BaseModel: lambda m: m.dict(by_alias=True)})

and is global

type_encoders are available on every layer of the application.

skewty commented 1 year ago

Incorrect I believe. A datetime for example will not be serialized into a string in v1 when calling dict. This is an important point because the model may contain special serialization for datetime objects. The mode=json feature is lacking in v1 AFAIK.

+Scott

Sent from Proton Mail mobile

-------- Original Message -------- On Sept 17, 2023, 1:51 a.m., Janek Nouvertné wrote:

Just tried pydantic v2 and it supports this using:

Starlite

(

type_encoder

=

{

BaseModel

:

lambda

m

:

m

.

model_dump

(

mode

=

"json"

,

by_alias

=

True

)})

The same thing works for Pydantic 1 as well. No need to do the roundtrip like in your opening post:

Starlite

(

type_encoder

=

{

BaseModel

:

lambda

m

:

m

.

dict

(

by_alias

=

True

)})

and is global

type_encoders are available on every layer of the application.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

provinzkraut commented 1 year ago

Incorrect I believe. A datetime for example will not be serialized into a string in v1 when calling dict. This is an important point because the model may contain special serialization for datetime objects.

This is more of a limitation of Pydantic v1 though. These special serialization won't be applied in any case, as there's no way to get to them without using pydantic's JSON serializer or doing the roundtrip thing (which Starlite does not do because it's very expensive).