Skip to content

Check extra action func.__name__ #7098

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

Merged
merged 3 commits into from
Aug 6, 2020
Merged

Conversation

rpkilby
Copy link
Member

@rpkilby rpkilby commented Dec 20, 2019

The extra @action rework is incompatible with some decorator patterns. For example:

def decorate(fn):
    def wrapper(self, request, *args, **kwargs):
        return fn(self, request, *args, **kwargs)
    return wrapper

The issue is that the @action rework looks up functions by their __name__ instead of their actual attribute name on the viewset. Normally, this isn't an issue since the two coincide. In the mean time, the workaround is to use functools.wraps, which will correctly set the __name__.

def decorate(fn):
    @functools.wraps(fn)
    def wrapper(self, request, *args, **kwargs):
        return fn(self, request, *args, **kwargs)
    return wrapper

Fixes #7097

@rpkilby
Copy link
Member Author

rpkilby commented Jan 12, 2020

So, fixing this isn't really possible given how method mapping works. The method map that's passed to the router maps HTTP methods to viewset method names. However, if an intermediate decorator returns a new function that doesn't use functools.wraps (or otherwise set __name__), then the router is passed a method name that doesn't exist. Take the following:

def decorate(fn):
    # doesn't use @functools.wraps(fn)
    def wrapper(self, request, *args, **kwargs):
        return fn(self, request, *args, **kwargs)
    return wrapper


class MyViewSet(viewsets.ViewSet):

    @action(detail=False)
    @decorate
    def extra(self, request, *args, **kwargs):
        pass

    @action.delete
    @decorate
    def extra_delete(self, request, *args, **kwargs):
        pass

What the router should be passed is {'get': 'extra', 'delete': 'extra_delete'}. However, since @action operates on the function directly, decorate's inner wrapper function is checked instead, creating a map of {'get': 'wrapper', 'delete': 'wrapper'}.

The best we can do is warn the user after the fact. If the attribute name doesn't match the function name, then we can raise an error.

@rpkilby rpkilby changed the title Test decorated extra action Check extra action func.__name__ Jan 12, 2020
@rpkilby rpkilby added this to the 3.12 Release milestone Jan 12, 2020
Copy link
Member

@auvipy auvipy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we drop python 3.5?

@kevin-brown
Copy link
Member

can we drop python 3.5?

Not unless we drop Django 2.2 LTS, which still supports it.

Copy link
Member

@tomchristie tomchristie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a conflict that needs resolving, but otherwise leaving this in your hands @rpkilby!
Thanks as ever!!! 😃

@rpkilby rpkilby force-pushed the decorated-actions branch from a3cc8a5 to b06c8ea Compare August 6, 2020 04:16
@rpkilby rpkilby merged commit 1e383f1 into encode:master Aug 6, 2020
@rpkilby rpkilby deleted the decorated-actions branch August 6, 2020 04:29
sigvef pushed a commit to sigvef/django-rest-framework that referenced this pull request Dec 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

use list_route add multiple decorate => 405 error
4 participants