sanic-jwt: Async OPTIONS function not being awaited

Hi. I have the following error:

<CoroWrapper Tester.options() running at test.py:18, created at /fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py:85> was never yielded from
Coroutine object created at (most recent call last, truncated to 10 last lines):
  File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/server.py", line 609, in serve
    loop.run_forever()
  File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 126, in send
    return self.gen.send(value)
  File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/app.py", line 556, in handle_request
    response = await response
  File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic_jwt/decorators.py", line 42, in decorated_function
    return await utils.call(f, request, *args, **kwargs)
  File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic_jwt/utils.py", line 44, in call
    fn = fn(*args, **kwargs)
  File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/views.py", line 63, in view
    return self.dispatch_request(*args, **kwargs)
  File "/fidessa/uatutils/Tools/Delivery/main_venv/UGUI_UAT-480w8wdy/lib/python3.6/site-packages/sanic/views.py", line 46, in dispatch_request
    return handler(request, *args, **kwargs)
  File "/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py", line 85, in debug_wrapper
    return CoroWrapper(gen, None)
/fidessa/uatutils/Tools/Delivery/.pyenv/versions/3.6.4/lib/python3.6/asyncio/coroutines.py:126: RuntimeWarning: coroutine 'Tester.options' was never awaited
  return self.gen.send(value)

I ran into this issue in my application and was also able to reproduce it with the following code:

from sanic_cors.core import ALL_METHODS
from sanic.views import HTTPMethodView
from sanic.response import text
from sanic_jwt import Authentication, initialize, protected
from sanic import Sanic, Blueprint


class TestMethodView(HTTPMethodView):
    async def options(self, *args, **kwargs):
        return text('ok')

class Tester(TestMethodView):
    decorators = [protected()]

    async def get(self, request):
        return text('ok')

bp = Blueprint('bp')
bp.add_route(Tester.as_view(), '/test', methods=ALL_METHODS)


class CustomAuth(Authentication):
    async def authenticate(self, request, *args, **kwargs):
        return {'username': 'Rich', 'password': 'not secure'}

    async def retrieve_user(self, request, payload, *args, **kwargs):
        if payload:
            user_id = payload.get('username', None)
            passwd = payload.get('password', None)
            return {'username': user_id, 'password': passwd}

    async def extend_payload(self, payload, user=None, *args, **kwargs):
        if user:
            payload.update({'extra_info': 'awesome!'})
        return payload


def make_app():
    app = Sanic(__name__)
    app.config.SANIC_JWT_AUTHORIZATION_HEADER_PREFIX = 'JWT'
    app.config.SANIC_JWT_EXPIRATION_DELTA = 360000
    app.config.SANIC_JWT_USER_ID = 'username'

    initialize(app, authentication_class=CustomAuth)
    app.blueprint(bp)
    return app 

make_app().go_fast(debug=True, host='0.0.0.0', port=9000)

I should note that ALL_METHODS from sanic_cors is just a list that contains both ‘GET’ and ‘OPTIONS’

Replication steps:

  1. Log in
  2. Send an OPTIONS request to /test

I’m using

python==3.6.4
sanic-jwt==1.1.0
sanic==0.7.0

I’ve been able to trace the issue to the call function in sanic-jwt/utils.py. The issue appears to be that somehow the options function isn’t registered as a coroutine like the get function is. Maybe this issue needs to go to Sanic? I’m not sure. But the issue only occurs with sanic-jwt because the Sanic code automatically calls functions and then checks to see if they are awaitable. Relevant Sanic code:

                # Run response handler
                response = handler(request, *args, **kwargs)
                if isawaitable(response):
                    response = await response

I was able to solve the issue for myself by changing call to:

async def call(fn, *args, **kwargs):
    if callable(fn):
        fn = fn(*args, **kwargs)
    if inspect.iscoroutinefunction(fn) or inspect.isawaitable(fn):
        fn = await fn                                                                                                                                                                                                                                          
    return fn

I’d be more than happy to raise a PR for this. This is my first gh issue, so please let me know if I do something wrong.

Please let me know if you need any further information.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 16 (2 by maintainers)

Commits related to this issue

Most upvoted comments

Thanks so much @vltr! I’ll try out the solution.

Hello @rafmagns-skepa-dreag !

Sorry for the void. We (me and @ahopkins) briefly discussed about this issue (and some others), but we had no spare time to see everything through, we are really sorry. I know there must be a solution for this, for now I think you should consider having a copy of sanic-jwt with your workaround (if it is working for you) so it won’t stop your development … Until we find some time to figure everything out. I’m really sorry for this delay.