django-ninja-extra
django-ninja-extra copied to clipboard
How to infer response, rather than define in router
Currently, I have an endpoint defined like so:
@route.get("/{int:article_id}", response=ArticleDetail)
def detail(self, article_id: UUID4):
"""View an articles details."""
return self.handlers.detail(article_id, self.uow)
In this case, the handlers.detail returns an Article instance from the Django orm. with the get method on the "/" path it returns QuerySet[Article] instead as I am using the all() method (typed with Django-stubs).
I would like to be able to define the response as the method return type instead, like this
@route.get("/{int:article_id}")
def detail(self, article_id: UUID4) -> ArticleDetail:
"""View an articles details."""
return self.handlers.detail(article_id, self.uow)
This way when the handlers.detail returns the Article model, or QuerySet[Article] for list, it gets serialized based on the schema return type of ArticleDetail (a schema).
There is a discrepancy with mypy though as the handler returns an Article or QuerySet and I want to define the return type here as a Schema instance to be serialized.
I attempted to do this by using similar functionality in the fastapi-utils library by subclassing the Route class and using this route instance instead.
class Router(Route):
"""Custom router adding support to set response based on return type."""
def __init__(self, *args, **kwargs):
if kwargs.get("response") is None:
kwargs["response"] = get_type_hints(self.view_func).get("return")
super().__init__(*args, **kwargs)
route = Router
But this __init__ method never gets touched. Do you have any idea how I could accomplish this? It looks like I may have to also override the http_* methods.
@autoferrit I will look at this and get back to you
@autoferrit I have refactored the Route class to be more customizable @ #40
So with the current change in Route class this example should work just fine.
import typing as t
from ninja.types import TCallable
from ninja.constants import NOT_SET
from ninja_extra import route
class RouteDefinition(route):
"""Custom router adding support to set response based on return type."""
@classmethod
def _create_route_function(cls, view_func: TCallable, **kwargs: t.Any):
if kwargs.get("response") is NOT_SET:
kwargs["response"] = t.get_type_hints(view_func).get("return")
return super()._create_route_function(view_func, **kwargs)
Then usage:
from where_ever import RouteDefinition as route
@route.get("/{int:article_id}")
def detail(self, article_id: UUID4) -> ArticleDetail:
"""View an articles details."""
return self.handlers.detail(article_id, self.uow)
Also the example will be applicable on next release.
Oh this is great! thanks. This was a really nice addition in the utils library and think it would be really good here.
@autoferrit checkout new release