Proper RequestHandler for async usage

18 views
Skip to first unread message

Shane Spencer

unread,
Nov 25, 2020, 2:53:46 AM11/25/20
to Python Tornado
So here's my hack.. I don't like it.  It performs the task in a very ugly way.   Any suggestions on how to make a RequestHandler decorator that can wrap both sync and async functions and call an async defined function and gather the return value?


import functools

from typing import Awaitable, Callable, Optional

from tornado.web import HTTPError, RequestHandler

from something import somethingelse

def do_async_stuff_decorator(
    method: Callable[..., Optional[Awaitable[None]]]
) -> Callable[..., Optional[Awaitable[None]]]:
    @functools.wraps(method)
    async def wrapper(  # type: ignore
        self: RequestHandler, *args, **kwargs
    ) -> Optional[Awaitable[None]]:

        self.thing = await somethingelse()

        result = await method(self, *args, **kwargs)  # type: ignore

        return result

    return wrapper

Rajdeep Rath

unread,
Nov 25, 2020, 3:04:43 AM11/25/20
to python-...@googlegroups.com
Looks interesting! What are you trying to achieve?

--
You received this message because you are subscribed to the Google Groups "Tornado Web Server" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-tornad...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/python-tornado/CANmUDYvVcbwyS22tUuc8%3DB1kh9fSPmWjbd5s6pLDBzbFx3%2Bf2A%40mail.gmail.com.

Shane Spencer

unread,
Nov 25, 2020, 2:13:58 PM11/25/20
to Python Tornado
My use case here will be to validate roles for authorization to resources by way of async database fetches.

Ben Darnell

unread,
Nov 27, 2020, 11:54:05 AM11/27/20
to Tornado Mailing List
So something like the tornado.web.authenticated decorator with the ability to call async methods? You may want to look for old threads on "async get_current_user" for related discussions.

Personally, I no longer recommend decorators for this kind of thing. They're tricky to write and there's not much advantage to be able to say

    @authenticated
    def get(self):
        ...

instead of

    async def get(self):
        await self.check_authentication()
        ...

It's true that if it's a line of code in the body of the method it's not easy to audit that all your handlers perform authentication correctly, but a decorator that could go on one or more of several methods isn't much better. (What I'd really like is something that could go in the URL routing table so that the authentication status of all your handlers would be visible in one place, but the Application/RequestHandler interface isn't really set up for that).


Anyway, if you want to use a decorator, my advice would be to have it consume the `Optional` aspect of the returned method's awaitable status:

    def decorator(f: Callable[..., Optional[Awaitable[None]]) -> Callable[..., Awaitable[None]]:
        async def wrapper(self, *args, **kwargs) -> None:
            self.thing = await somethingelse()
            result = f(*args, **kwargs)
            if result is not None:
                await result
        return wrapper

Note that your example doesn't type check (or maybe mypy isn't smart enough to understand this) because `async def wrapper` adds another level of Awaitable: the decorated function has type signature `Callable[..., Awaitable[Optional[Awaitable[None]]]]`, which is probably not what you want (and it's not compatible with the RequestHandler.get interface because nothing will await that inner awaitable).

-Ben


Shane Spencer

unread,
Nov 28, 2020, 2:30:38 PM11/28/20
to Python Tornado
Thanks, I think I’m going to forgo with decorators as well since I need to use some complex arguments now.  I'll just await and raise as needed.

Rajdeep Rath

unread,
Nov 30, 2020, 3:45:56 AM11/30/20
to python-...@googlegroups.com
Am very new to tornado, but i will share something i had tried (and worked) some time ago. not sure if it is helpful.

Decor: https://gist.github.com/rajdeeprath/f0f367f5e01abf1f1a1ba5d9d1f9801a

Usage: 

@authorize_client("developer")
# Do something
pass



Reply all
Reply to author
Forward
0 new messages