-
-
Notifications
You must be signed in to change notification settings - Fork 341
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
Enhancement: Resource representation response based on Accept
header
#3272
Comments
Agree it would be nice to have more flexibility with response encoding. I'm not sure about specifying multiple response class types b/c I think this would worsen the UI for defining a customized response class. E.g,.: # currently possible
class MyCustomResponse(Response):
type_encoders = {...} # example response customization
app = Litestar(..., response_class=MyCustomResponse) If multiple response class types were defined, that customization would have to be applied to all of them. I think I noticed a mention in discord about passing request context to the response object in order to be able to discriminate how the response content should be encoded? So how about if we added a method like E.g., class Response:
def __init__(self, ..., request_context: ...) -> None: ...
def get_encoder(self) -> Callable[[Any], bytes]: ...
def render(self, ...) -> bytes:
encoder = self.get_content_encoder() |
In this case, the user would need to then give an encoder based on any custom logic they have right? I like this, but I think we could also consider taking in a mapping of accept headers to encoders at all layers, and then the default implementation wouldn't be to default to serializing as JSON but to find the encoder based on the accept header and then use that for the encoding (this is only in the case where |
@kedod We already have an
I don't really like this idea as it complicates things further. The response shouldn't need to worry about the request. If we were doing this, IMO this functionality should be in the place where the response is created. Seeing how this falls generally in the category of "metadata based routing", why don't we just do that? We can already define multiple handlers for the same path, given they differ in their method, why not allow the same for another set of parameters, such as the @get("/", match=[Accept(MediaType.JSON)])
async def get_json() -> dict[str, str]:
...
@get("/", match=[Accept(MediaType.XML)])
async def get_xml() -> dict[str, str]:
... |
@provinzkraut wouldn't the way you've suggested result in the users having to implement both @get("/")
async def get_foo() -> dict[str, str]:
... Then litestar would automatically figure out which format to serialize into based on the accept header. This would be the simplest from the user perspective.
I didn't understand this. We already have a dependency on the |
That feels like a different feature to me so maybe we're not talking about the same stuff here? 👀 If we want that, wouldn't the easiest solution to allow specifying additional response serialisers (ideally as layered parameters)? @get("/")
async def get_foo() -> dict[str, str]:
...
app = Litestar(
[get_foo],
serializers={MediaType.XML: to_xml}
) and then Litestar checks if for a given
Yeah I don't like that either 😬 It's something we currently need to do, but it's also only a workaround for some other issues we were facing. I can't actually remember anymore what exactly this was.. @peterschutt I remember we were talking about this a lot during the 2.0 churn when this was implemented and I think we agreed that it was the path of least resistance but there was some other way of handling this better.. Would have to dig up the conversation from back then 👀 |
I like this idea the most. |
Summary
In one of 3.0 wishlist points
@rob
suggested to support multiple resource representation in response based onAccept
header https://discord.com/channels/919193495116337154/1182745702942646313/1217489384669184064I've decided to write few words about current situation, problems, possible interface for this (possible) future feature.
Basic Example
What's all the fuss about?
In the current Litestar 2.x version if we want to return different resource representation based on
Accept
header we have to it manually using "ifology" which in case of many possible representations (json, xml, csv, yaml...) is not the most elegant solution. This can also lead to code duplication if we have more endpoints.Simple example can look like this:
Sure we can write custom methods and extend base data model classes (pydantic, msgspec, attrs...) but implementing logic for every model type with including/excluding format mechanisms may be complex and time consuming. And we still need to figure out how to handle different responses for DTOs.
Customizable, extendable and easy to use resource representation response based on
Accept
header can be nice addition to the Litestar. At least I think so :)Digging into the code
Accept
header is set to"*/*"
if request does not provide any (https://github.com/litestar-org/litestar/blob/main/litestar/connection/request.py#L120)media_type
is set on route handler level and passed toResponse.render
method ("application/json"
by default)Response.render
matchesmedia_type
patterns and renders properly encoded messageInterface
I like the way how the DRF handle this https://www.django-rest-framework.org/api-guide/renderers/
What if we can "borrow" their idea?
There is obstacle. We have
media_type
in route handler definition.But we can move
media_type
argumentResponse
object, right?Take a look at this simpliefied example:
It looks nice.
Simple and reusable.
Supports DTOs.
media_type
can be fixed content type, regex or*/*
Drawbacks and Impact
Content negotiation
There should be negotiation between client and server to check if
Accept
header is supported in endpoint.In
get_response_handler
we can iterate overresponse_classes
and check if any of them supports specifiedAccept
format.First matched class will be used as
response_class
in later steps. If none of them will match thenHTTP_406_NOT_ACCEPTABLE
can be returned by default (or not).New
Response
classesBreaking (and not) changes
media_type
in handler definitionUnresolved questions
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
The text was updated successfully, but these errors were encountered: