<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Guguweb - async</title><link href="https://www.guguweb.com/" rel="alternate"></link><link href="https://www.guguweb.com/feeds/async.atom.xml" rel="self"></link><id>https://www.guguweb.com/</id><updated>2022-01-14T21:10:47+01:00</updated><subtitle>Freelance developer and sysadmin</subtitle><entry><title>Django asynchronous tasks without Celery</title><link href="https://www.guguweb.com/2019/11/21/django-asynchronous-tasks-without-celery/" rel="alternate"></link><published>2019-11-21T10:18:45+00:00</published><updated>2022-01-14T21:10:47+01:00</updated><author><name>Augusto Destrero</name></author><id>tag:www.guguweb.com,2019-11-21:/2019/11/21/django-asynchronous-tasks-without-celery/</id><summary type="html">&lt;p&gt;In this blog post I will guide you to implement Django asynchronous tasks without Celery. First of all I will define what I mean with the term &amp;#8220;asynchronous task&amp;#8221;.&lt;/p&gt;
&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#what-are-django-asynchronous-tasks"&gt;What are Django asynchronous tasks?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#enter-the-uwsgi-spooler"&gt;Enter the uWSGI spooler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#install-uwsgidecorators-package"&gt;Install uwsgidecorators package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create-the-directory-for-the-spool-files"&gt;Create the directory for the &amp;#8220;spool …&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;In this blog post I will guide you to implement Django asynchronous tasks without Celery. First of all I will define what I mean with the term &amp;#8220;asynchronous task&amp;#8221;.&lt;/p&gt;
&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#what-are-django-asynchronous-tasks"&gt;What are Django asynchronous tasks?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#enter-the-uwsgi-spooler"&gt;Enter the uWSGI spooler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#install-uwsgidecorators-package"&gt;Install uwsgidecorators package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create-the-directory-for-the-spool-files"&gt;Create the directory for the &amp;#8220;spool files&amp;#8221;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#create-a-tasks-module-in-your-django-application"&gt;Create a tasks module in your Django application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#call-the-asynchronous-task-from-your-django-view"&gt;Call the asynchronous task from your Django view&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configure-uwsgi-to-use-the-spooler"&gt;Configure uWSGI to use the spooler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="what-are-django-asynchronous-tasks"&gt;What are Django asynchronous tasks?&lt;/h2&gt;
&lt;p&gt;Suppose that you want to perform a long running task in your Django web app, but you want to give an immediate response to the user without waiting for the task to finish. The task could be: sending an email, building a report, making a request to an external web service, etc. The response given to the user usually is a confirmation message that the task has started.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every task that &lt;strong&gt;could&lt;/strong&gt; take some time to complete should not block the request-response cycle between the user and your application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you Google for &amp;#8220;Django asynchronous tasks&amp;#8221; probably you&amp;#8217;ll be directed to &lt;a href="http://www.celeryproject.org/"&gt;Celery&lt;/a&gt; as &lt;strong&gt;the way&lt;/strong&gt; to implement asynchronous tasks with Django. Please don&amp;#8217;t get me wrong: there is nothing wrong in Celery. Many successful projects use Celery in production with success. I also used Celery in a couple of projects, and it works well. What I find annoying is the additional effort.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Celery is yet another service to configure, launch and maintain.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="enter-the-uwsgi-spooler"&gt;Enter the uWSGI spooler&lt;/h2&gt;
&lt;p&gt;As I wrote in my other post &lt;a href="https://www.guguweb.com/2019/11/13/django-nginx-deploy-your-django-project-on-a-production-server/"&gt;Django &amp;#8211; NGINX: deploy your Django project on a production server&lt;/a&gt;, I like to use the &lt;a href="https://uwsgi-docs.readthedocs.io/en/latest/"&gt;uWSGI&lt;/a&gt; application server. Well, it turns out that uWSGI provides a full featured, production ready, system to implement asynchronous tasks. They call it the &lt;a href="https://uwsgi-docs.readthedocs.io/en/latest/Spooler.html"&gt;uWSGI Spooler&lt;/a&gt;. Citing the uWSGI documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Spooler is a queue manager built into uWSGI that works like a printing/mail system.&lt;/p&gt;
&lt;p&gt;You can enqueue massive sending of emails, image processing, video encoding, etc. and let the spooler do the hard work in background while your users get their requests served by normal workers.&lt;/p&gt;
&lt;p&gt;A spooler works by defining a directory in which “spool files” will be written, every time the spooler find a file in its directory it will parse it and will run a specific function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The uWSGI Spooler is very easy to configure!&lt;/p&gt;
&lt;h2 id="install-uwsgidecorators-package"&gt;Install uwsgidecorators package&lt;/h2&gt;
&lt;p&gt;To interact with uWSGI spooler from your Python code it is really convenient to install the &lt;strong&gt;uwsgidecorators&lt;/strong&gt; package. Unfortunately there isn&amp;#8217;t a recent version of uwsgidecorators on PyPI, but you can install it using apt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo apt install python3-uwsgidecorators
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you use a virtualenv for your Django project (as you should) you can link the uwsgidecorator module in your virtualenv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ln -s /usr/lib/python3/dist-packages/uwsgidecorators.py /path/to/your/virtualenv/lib/python3.6/site-packages
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="create-the-directory-for-the-spool-files"&gt;Create the directory for the &amp;#8220;spool files&amp;#8221;&lt;/h2&gt;
&lt;p&gt;First of all you have to create the directory where uWSGI will save the &amp;#8220;spool files&amp;#8221; used by the spooler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir -p /home/ubuntu/tasks
sudo chown www-data.www-data /home/ubuntu/tasks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Please make sure that the directory is writable by the user running uWSGI, that is &lt;strong&gt;www-data&lt;/strong&gt; in the example.&lt;/p&gt;
&lt;h2 id="create-a-tasks-module-in-your-django-application"&gt;Create a tasks module in your Django application&lt;/h2&gt;
&lt;p&gt;Assuming that you have a Django app named &lt;strong&gt;app1&lt;/strong&gt; you should create a module named &lt;strong&gt;tasks.py&lt;/strong&gt; in the app directory. The content of the module should be something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;uwsgidecorators&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;spool&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;spool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;func_wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;func_wrapper&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt;
&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Model1&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@spool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;long_running_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;obj1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Model1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;obj1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;long_running_model_method&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The try/except construct will let you test the code even when not running in a uWSGI application server, for instance when running locally using &lt;code&gt;./manage.py runserver&lt;/code&gt;. In that case the task will be executed synchronously, as it was a regular python function call.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;long_running_task&lt;/strong&gt; function is the task you will invoke using the uWSGI Spooler. As you can see you have to decorate it with the &lt;strong&gt;@spool&lt;/strong&gt; decorator, and the parameters for the task are passed in a dictionary called &lt;strong&gt;arguments&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this example I&amp;#8217;m using the Django ORM to get an instance of a model and to call a model method that will take a long time to complete.&lt;/p&gt;
&lt;h2 id="call-the-asynchronous-task-from-your-django-view"&gt;Call the asynchronous task from your Django view&lt;/h2&gt;
&lt;p&gt;Here you can find how to call the asynchronous task from an example view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;long_running_task&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;async_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;long_running_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;Task started correctly&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;some_other_view&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here I call the task, then I use the &lt;a href="https://docs.djangoproject.com/en/2.2/ref/contrib/messages/"&gt;Django messages framework&lt;/a&gt; to show the user a confirmation message on the next page view, and finally I redirect the user to another view.&lt;/p&gt;
&lt;h2 id="configure-uwsgi-to-use-the-spooler"&gt;Configure uWSGI to use the spooler&lt;/h2&gt;
&lt;p&gt;To configure the uWSGI Spooler you should edit the uWSGI configuration file I presented in the post &lt;a href="https://www.guguweb.com/2019/11/13/django-nginx-deploy-your-django-project-on-a-production-server/"&gt;Django &amp;#8211; NGINX: deploy your Django project on a production server&lt;/a&gt;. The file is located here: &lt;code&gt;/etc/uwsgi/apps-enabled/django.ini&lt;/code&gt;. The content of the file should be something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[uwsgi]&lt;/span&gt;
&lt;span class="na"&gt;chdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/home/ubuntu/django_project # customize with your django installation directory&lt;/span&gt;
&lt;span class="na"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;DJANGO_SETTINGS_MODULE=project.settings.production # customize with your settings module&lt;/span&gt;
&lt;span class="na"&gt;wsgi-file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;project/wsgi.py # customize with the relative path to your wsgi.py file&lt;/span&gt;
&lt;span class="na"&gt;workers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;spooler-chdir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/home/ubuntu/django_project # customize with your django installation directory&lt;/span&gt;
&lt;span class="na"&gt;spooler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/home/ubuntu/tasks/&lt;/span&gt;
&lt;span class="na"&gt;import&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;app1.tasks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Restart uWSGI with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;service uwsgi restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You will find the uWSGI logs in &lt;strong&gt;/var/log/uwsgi/apps/django.log&lt;/strong&gt;. Therefore you can check them to see if the Python process started correctly or there are issues.&lt;/p&gt;
&lt;p&gt;In the log file you&amp;#8217;ll also find messages about the Spooler, something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Mon Nov 18 14:50:45 2019 - [spooler /home/ubuntu/tasks pid: 8646] managing request uwsgi_spoolfile_on_www.example.com_8647_3_1383635828_1574067630_419736 ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post I&amp;#8217;ve shown how to setup Django asynchronous tasks using uWSGI Spooler. That is a simple and convenient way to perform long running tasks outside the request-response cycle. Especially if you already use uWSGI application server.&lt;/p&gt;
&lt;p&gt;I used this technique in many different projects, for example an &amp;#8220;Instagram like&amp;#8221; &lt;a href="https://media.mtb-mag.com/en/pv/"&gt;photo and video sharing platform&lt;/a&gt; I built from scratch for the popular online magazine &lt;a href="https://www.mtb-mag.com/"&gt;mtb-mag.com&lt;/a&gt;. In that case videos are processed asynchronously to make two versions suitable for all devices, in standard and high definition.&lt;/p&gt;
&lt;p&gt;Please let me know if you have comments or question by leaving a reply below. Happy coding!&lt;/p&gt;</content><category term="web"></category><category term="async"></category><category term="django"></category><category term="uwsgi"></category></entry></feed>