Skip to content
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

Django async support with ASGI. #5

Open
marcinocto opened this issue Jan 27, 2022 · 1 comment
Open

Django async support with ASGI. #5

marcinocto opened this issue Jan 27, 2022 · 1 comment

Comments

@marcinocto
Copy link

marcinocto commented Jan 27, 2022

I'm converting my project to be deployed under ASGI with daphne with the Django channels library. I have the IPRestrictMiddleware hooked up in my middleware and after turning on the channels app I started getting this issue

Traceback (most recent call last):
django_1    |   File "/usr/local/lib/python3.8/site-packages/channels/staticfiles.py", line 40, in __call__
django_1    |     return await self.staticfiles_handler_class()(
django_1    |   File "/usr/local/lib/python3.8/site-packages/channels/http.py", line 179, in __init__
django_1    |     self.load_middleware()
django_1    |   File "/usr/local/lib/python3.8/site-packages/channels/http.py", line 294, in load_middleware
django_1    |     super(AsgiHandler, self).load_middleware()
django_1    |   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 58, in load_middleware
django_1    |     mw_instance = middleware(adapted_handler)
django_1    |   File "/code/project/middlewares/ejn_ip_restrict.py", line 7, in __init__
django_1    |     self.ip_restrict = IPRestrictMiddleware(get_response)
django_1    |   File "/usr/local/lib/python3.8/site-packages/iprestrict/middleware.py", line 17, in __init__
django_1    |     self.restrictor = IPRestrictor()
django_1    |   File "/usr/local/lib/python3.8/site-packages/iprestrict/restrictor.py", line 12, in __init__
django_1    |     self.load_rules()
django_1    |   File "/usr/local/lib/python3.8/site-packages/iprestrict/restrictor.py", line 24, in load_rules
django_1    |     self.rules = list(Rule.objects.all())
django_1    |   File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__
django_1    |     self._fetch_all()
django_1    |   File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
django_1    |     self._result_cache = list(self._iterable_class(self))
django_1    |   File "/usr/local/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
django_1    |     results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
django_1    |   File "/usr/local/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
django_1    |     cursor = self.connection.cursor()
django_1    |   File "/usr/local/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
django_1    |     raise SynchronousOnlyOperation(message)
django_1    | django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.

This happens because of the DB call in the __init__ of the IPRestrictMiddleware when instantiating the IPRestrictor.

    def load_rules(self):
        # We are caching the rules, to avoid DB lookup on each request
        from .models import Rule

        self.rules = list(Rule.objects.all())
        self.last_reload = timezone.now()

I can't see anything specific here about middleware https://docs.djangoproject.com/en/4.0/topics/async/ but I assume that any call like DB call is not ideal in the async scope.

I implemented this workaround which delays the instantiation of the class to the process_request instead which with the help of the django's MiddlewareMixin is implemented to run with the sync_to_async wrapper.

from django.utils.deprecation import MiddlewareMixin
from iprestrict.middleware import IPRestrictMiddleware


class CustomIPRestrictMiddleware(MiddlewareMixin):
    ip_restrict: IPRestrictMiddleware = None

    def process_request(self, request):
        if self.ip_restrict is None:
            self.ip_restrict = IPRestrictMiddleware(self.get_response)
        return self.ip_restrict(request)

Any thoughts on moving the DB call away from the __init__ call in the IPRestrictMiddleware and having some similar solution to what I've done?

@zhixuan-loh
Copy link

Hi. I'm working on something similar involving using IP restrict redux (which is a more updated version of IPRestrict) in the django channels. I notice that their middleware are not compatible because channels middleware has a call method with signature async def __call__(self, scope, receive, send): while django channels middleware has method signature def __call__(self, request): I get error because of the mismatch in number of arguments (which is expected).

May I know if there is a way to incorporate iprestrict redux into django channels middleware?

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

No branches or pull requests

2 participants