django-ninja-extra icon indicating copy to clipboard operation
django-ninja-extra copied to clipboard

How to infer response, rather than define in router

Open autoferrit opened this issue 3 years ago • 3 comments

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 avatar Oct 12 '22 18:10 autoferrit

@autoferrit I will look at this and get back to you

eadwinCode avatar Oct 18 '22 05:10 eadwinCode

@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)

eadwinCode avatar Oct 22 '22 13:10 eadwinCode

Also the example will be applicable on next release.

eadwinCode avatar Oct 22 '22 13:10 eadwinCode

Oh this is great! thanks. This was a really nice addition in the utils library and think it would be really good here.

autoferrit avatar Oct 29 '22 05:10 autoferrit

@autoferrit checkout new release

eadwinCode avatar Oct 30 '22 11:10 eadwinCode