Asynchronous signals with asyncio - django

My model post processing is using the post_save signal:
from django.core.signals import request_finished
from django.dispatch import receiver
from models import MyModel
from pipeline import this_takes_forever
#receiver(post_save, sender=MyModel)
def my_callback(sender, **kwargs):
this_takes_forever(sender)
The this_takes_forever routine does IO so I want to defer it to avoid blocking the request too much.
I thought this was a great use case for the new asyncio module. But I have a hard time getting my mind around the whole process.
I think I should be able to adapt the signal receiver like this:
#receiver(post_save, sender=MyModel)
def my_callback(sender, **kwargs):
loop = asyncio.get_event_loop()
loop.run_until_complete(this_takes_forever(sender))
loop.close()
Provided this_takes_forever is also adapted to be a coroutine.
#coroutine
def this_takes_forever(instance):
# do something with instance
return instance
This sounds too magical to work. And in fact it halts with an AssertionError:
AssertionError at /new/
There is no current event loop in thread 'Thread-1'.
I don't see where should I start the loop in this context. Anyone tried something like this?

You get no any benefit in your case:
#receiver(post_save, sender=MyModel)
def my_callback(sender, **kwargs):
this_takes_forever(sender)
is equal to
#receiver(post_save, sender=MyModel)
def my_callback(sender, **kwargs):
loop = asyncio.get_event_loop()
loop.run_until_complete(this_takes_forever(sender))
loop.close()
in terms of execution time. loop.run_until_complete waits for end of this_takes_forever(sender) coroutine call, so you get synchronous call in second case as well as in former one.
About AssertionError: you start Django app in multithreaded mode, but asyncio makes default event loop for main thread only -- you should to register new loop for every user-created thread where you need to call asyncio code.
But, say again, asyncio cannot solve your particular problem, it just incompatible with Django.
The standard way for Django is to defer long-running code into celery task (see http://www.celeryproject.org/)

Related

Creating a handler for user_activated signal

I want to receive a signal when user is activated (i.e. when auth_user.is_active becomes 1). I only want to receive this signal once, the very first time that the user is activated.
I have used the answer given to this question, and it works for me:
#receiver(pre_save, sender=User, dispatch_uid='get_active_user_once')
def new_user_activation_handler(sender, instance, **kwargs):
if instance.is_active and User.objects.filter(pk=instance.pk, is_active=False).exists():
logger.info('user is activated')
However this seems to be a customized signal, I believe django has a built-in user_activated signal. I have tried using the built-in signal but it does not fire:
signals.py:
from django_registration.signals import user_activated
#receiver(user_activated, sender=User, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')
Also this is what I have in apps.py:
class MyClassConfig(AppConfig):
name = 'myclass'
def ready(self):
logger.info('ready...')
import myclass.signals # wire up signals ?
Not sure why this signal is not being fired?
In order to get the above code running, I had to install django-registration package.
All the examples that I have seen have:
from registration.signals import user_activated
But in my case I have to use the a diferent namespace:
from django_registration.signals import user_activated
Not sure why...
You have wrong sender. Please see:
Replace:
#receiver(user_activated, sender=User, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')
With:
from django_registration.backends.activation.views import ActivationView
#receiver(user_activated, sender=ActivationView, dispatch_uid='django_registration.signals.user_activated')
def new_user_activation_handler(sender, instance, **kwargs):
logger.info('user is activated')

Async in Django ListCreateApiView

I have a simply ListCreateApiView class in Django, for example:
class Test(ListCreateApiView):
def list(self, request, *args, **kwargs):
""" Some code which is creating excel file with openpyxl and send as response """
return excel_file
The problem is, when user send a request to get an excel_file, he must wait for 5-10 minutes till the file will return. User can't go to others urls untill file will not download (I have 10k+ objects in my db)
So, how can I add async here? I want that while the file is being formed, a person can follow other links of the application.
Thanks a lot!
Django has some troubles with asynchrony, and async calls won't help you in cpu-intensive tasks, you'd better use some distributed task queue like celery
It will allow you to handle such heavy tasks (i.e. excel generation) in another thread without interrupting the main django thread. Follow the guide for integration with django.
Once installed and configured, create a task and fire it when a user reaches the endpoint:
E.g.:
# tasks.py
from celery import shared_task
#shared_task
def generate_excel(*args, **kwargs):
...
# views.py
from tasks import generate_excel
class Test(ListCreateApiView):
def list(self, request, *args, **kwargs):
generate_excel.delay()
return Response()

Django Signals.py. How to combine receiver's and initiate function in a new thread

which is the best way to combine Django receiver's from signals.py and initiate a function in a new thread.
Example with post_delete and post_save :
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver
#receiver(post_delete, sender=Application)
def test_delete_function(sender, instance, **kwargs):
if isinstance(instance, Application):
deletefunc()
#receiver(post_save, sender=Application)
def test_save_function(sender, instance, **kwargs):
if isinstance(instance, Application):
savefunc()
So,
Q1: Is this a good way to express different receiver types - #receiver and then functions after that?
Q2: When saving Application from the fronend with POST, test_save_function is initiated in the same thread. How to run test_save_function on a different thread?
- I expected this to be handled by Django Framework, but it seems I need additional configuration?
Thanks!

Maximum recursion depth exceeded in my Django signal

I have the following Django signal which basically triggers the signal to increase the points of my previous records in my postgres db by 5 when a new record is saved, but my Django signal saves the changes to 1 previous record and I get the error RecursionError: Maximum recursion depth exceeded
# models.py
from django.db.models.signals import post_save
class Task(models.Model):
....
def update_points(sender, instance, **kwargs):
qs = Task.objects.filter(status='Active')
for task in qs:
task.points = task.points + 5
task.save()
What am I doing wrong? I am using the .save() method to save the updated records in my db after a new record has been inserted.
Likely the point.save() triggers the same signal. So your signal triggers that signal, and thus this results in endless recursion.
You can update the value with a query in bulk:
from django.db.models import F
def update_points(sender, instance, **kwargs):
Task.objects.filter(status='Active').update(points=F('points')+5)
This will not only circument the signalling problem, it will furthermore make a query in bulk, and is thus more efficient.
Note that if you post_save, then the Task that was just added, will be part of the QuerySet. Perhaps you want to exclude that Task. In that case, we thus can implement this as:
from django.db.models import Q, F
from django.db.models.signals import post_save
def update_points(sender, instance, created, **kwargs):
if created:
Task.objects.filter(~Q(pk=instance.pk), status='Active').update(
points=F('points')+5
)
post_save.connect(update_points, sender=Task)

Catch django signal sent from celery task

Catch django signal sent from celery task. Is it possible? As far as I know they are running in different processes
#celery.task
def my_task():
...
custom_signal.send()
#receiver(custom_signal)
def my_signal_handler():
...
Please take in mind that your async task must be #shared_task decorator. in order to be called from outside since it will not be attached to a concrete app instance. Documentation #shared_task celery
task.py
#shared_task
def send_email(email):
# Do logic for sending email or other task
signal.py
as you can see below this will only execute when post_save (After user executes save) for the model Contract in your case would be anyother model that its being executed.
#receiver(post_save, sender=Contract)
def inflation_signal(sender, **kwargs):
if kwargs['created']:
send_email.delay('mymail#example.com')