Opened 2 years ago

Last modified 2 years ago

#33716 closed New feature

async middleware can be a regular function too — at Version 12

Reported by: abetkin Owned by: nobody
Component: Uncategorized Version: 4.0
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by abetkin)

Here is what we have in MiddlewareMixin:

    def _async_check(self):
        """
        If get_response is a coroutine function, turns us into async mode so
        a thread is not consumed during a whole request.
        """
        if asyncio.iscoroutinefunction(self.get_response):
            # Mark the class as async-capable, but do the actual switch
            # inside __call__ to avoid swapping out dunder methods
            self._is_coroutine = asyncio.coroutines._is_coroutine
        else:
            self._is_coroutine = None

This checks if the next middleware is a coroutine, and if not fallbacks to sync mode. However, I think this is redundant: if the middleware is async-capable, and we have an ASGI request, what else we need ti check?

The downside of _async_check is that this common usecase is not supported:

def MyMiddleware(get_response):

    def middleware(request):
        # Do some stuff with request that does not involve I/O
        request.vip_user = True
        return get_response(request)

    return middleware

MyMiddleware.async_capable=True

middleware(request) will return the response in sync case and a coroutine in the async case, despite being a regular function (because get_response is a coroutine function in the latter case).

Here is a patch that I use that explains a possible way to fix it:

def is_next_middleware_async_capable(mw):
    path = f'{mw.__class__.__module__}.{mw.__class__.__name__}'
    next_index = settings.MIDDLEWARE.index(path) + 1
    mw_class = import_string(settings.MIDDLEWARE[next_index])
    return mw_class.async_capable


def call_mw(mw, request, _call_mw=MiddlewareMixin.__call__):
    if isinstance(request, ASGIRequest) and is_next_middleware_async_capable(mw):
        return mw.__acall__(request)
    return _call_mw(mw, request)


MiddlewareMixin.__call__ = call_mw

Github project that shows the error: https://github.com/pwtail/django_bug

Change History (13)

by abetkin, 2 years ago

Attachment: bug.png added

comment:1 by abetkin, 2 years ago

Description: modified (diff)

comment:2 by abetkin, 2 years ago

Description: modified (diff)

comment:3 by abetkin, 2 years ago

Description: modified (diff)
Triage Stage: UnreviewedAccepted
Type: UncategorizedBug

comment:4 by abetkin, 2 years ago

Resolution: invalid
Status: newclosed

comment:5 by abetkin, 2 years ago

Severity: Release blockerNormal

comment:6 by abetkin, 2 years ago

Description: modified (diff)
Resolution: invalid
Status: closednew
Summary: Async-capable middleware is not async-capableasync get_response can be a regular function too

comment:7 by abetkin, 2 years ago

Severity: NormalRelease blocker

comment:8 by abetkin, 2 years ago

Severity: Release blockerNormal

comment:9 by abetkin, 2 years ago

Description: modified (diff)

comment:10 by abetkin, 2 years ago

Description: modified (diff)

comment:11 by abetkin, 2 years ago

Summary: async get_response can be a regular function tooasync middleware can be a regular function too

comment:12 by abetkin, 2 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top