Amin Etesamian
Amin Etesamian

Reputation: 3699

aiohttp: chain of decorators sequence

Consider the following code:

from aiohttp_mako import template

def authorize():
    def wrapper(func):
        @asyncio.coroutine
        @functools.wraps(func)
        def wrapped(*args):
            allowed = # Some auth stuff
            if not allowed:
                return HTTPUnauthorized()
            return func()
        return wrapped
    return wrapper


@authorize()
@template('admin.mak')
async def admin(request):
    return dict(ok=True)

I expect authorize()' wrapper to get template decorator as its func so I can return the Response it generates in my authorize decorator. But authorize()' wrapper takes the admin() coroutine as the func and it ends up with

File "/Users/etemin/virtualenvs/onlinelux/lib/python3.5/site-packages/aiohttp/web.py", line 306, in _handle
    resp = yield from handler(request)
File "/Users/etemin/virtualenvs/onlinelux/lib/python3.5/site-packages/aiohttp_session/__init__.py", line 134, in middleware
    raise RuntimeError("Expect response, not {!r}", type(response))
RuntimeError: ('Expect response, not {!r}', <class 'generator'>)

Cause it tries to return the coroutine. How should I make it to return the template decorator?

Upvotes: 2

Views: 1606

Answers (2)

Saurav Kumar
Saurav Kumar

Reputation: 583

This is what worked for me.

Inspired from @aiohttp_csrf.csrf_protect.

from functools import wraps

from aiohttp import web


def is_authorized(request):
    # Write logic to validate request
    return True


def authorize(handler=None):  # When python module having view was imported, it expected to have this.

    def wrapper(handler):
        @wraps(handler)
        async def wrapped(*args, **kwargs):
            request = args[-1]

            if isinstance(request, web.View):
                # Should also work for Class-Based Views
                request = request.request

            try:
                is_authorized(request)
            except Exception as ex:
                # request not authorized. Write logic to handle it accordingly
                print('Request not authorized: ', repr(ex))
                raise ex

            response = await handler(*args, **kwargs)
            return response

        return wrapped

    if handler is None:
        return wrapper

    return wrapper(handler)

Somewhere in your view:

@authorize()
@template('admin.mak')
async def admin(request):
    return dict(ok=True)

Upvotes: 0

Martijn Pieters
Martijn Pieters

Reputation: 1121644

You have wrapped a co-routine, so you need to await that co-routine (yield from it):

def authorize():
    def wrapper(func):
        @asyncio.coroutine
        @functools.wraps(func)
        def wrapped(*args):
            allowed = # Some auth stuff
            if not allowed:
                return HTTPUnauthorized()
            return yield from func()
        return wrapped
    return wrapper

Since you are already using the async/await syntax, I'd just use that here too and not use @asyncio.coroutine:

def authorize():
    async def wrapper(func):
        @functools.wraps(func)
        async def wrapped(*args):
            allowed = # Some auth stuff
            if not allowed:
                return HTTPUnauthorized()
            return await func()
        return wrapped
    return wrapper

Note that I awaited on func() there, and returned the result.

Upvotes: 4

Related Questions