Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Django Asynchronous Request handling

Tags:

python

django

I am working in an application where i am doing a huge data processing to generate a completely new set of data which is then finally saved to database. The application is taking a huge time in processing and saving the data to data base. I want to improve the user experience to some extent by redirecting user to result page first and then doing the data saving part in background(may be in the asynchronous way) . My problem is that for displaying the result page i need to have the new set of processed data. Is there any way that i can do so that the data processing and data saving part is done in background and whenever the data processing part is completed(before saving to database) i would get the processed data in result page?.

like image 229
Gunjan chhetri Avatar asked Sep 12 '15 08:09

Gunjan chhetri


People also ask

Is Django good for asynchronous?

Django has support for writing asynchronous (“async”) views, along with an entirely async-enabled request stack if you are running under ASGI. Async views will still work under WSGI, but with performance penalties, and without the ability to have efficient long-running requests.

Is Django 4 asynchronous?

Latest version of the popular Python web framework also provides an asynchronous interface for all data access operations. Django 4.1, a new version of the major Python-based web framework, adds capabilities such as asynchronous handlers and an ORM interface but also makes some backward-incompatible changes.

What is asynchronous Django?

Django Rest Framework. When writing an application, asynchronous code allows you to speed up an application that has to deal with a high number of tasks simultaneously. With the Django 3.1 release, Django now supports async views, so if you are running ASGI, writing async specific tasks is now possible!

Is FastAPI asynchronous?

The most exciting feature of FastAPI is that it supports asynchronous code out of the box using the async/await Python keywords. Testing FastAPI endpoints are really straightforward and can be done using TestClient provided by FastAPI.


2 Answers

Another strategy is to writing a threading class that starts up custom management commands you author to behave as worker threads. This is perhaps a little lighter weight than working with something like celery, and of course has both advantages and disadvantages. I also used this technique to sequence/automate migration generation/application during application startup (because it lives in a pipeline). My gunicorn startup script then starts these threads in pre_exec() or when_ready(), etc, as appropriate, and then stops them in on_exit().

# Description: Asychronous Worker Threading via Django Management Commands
# Lets you run an arbitrary Django management command, either a pre-baked one like migrate,
# or a custom one that you've created, as a worker thread, that can spin forever, or not.
# You can use this to take care of maintenance tasks at start-time, like db migration,
# db flushing, etc, or to run long-running asynchronous tasks.  
# I sometimes find this to be a more useful pattern than using something like django-celery,
# as I can debug/use the commands I write from the shell as well, for administrative purposes.

import json
import os
import requests
import sys
import time
import uuid
import logging
import threading
import inspect
import ctypes

from django.core.management import call_command
from django.conf import settings

class DjangoWorkerThread(threading.Thread):
    """
    Initializes a seperate thread for running an arbitrary Django management command.  This is
    one (simple) way to make asynchronous worker threads.  There exist richer, more complex
    ways of doing this in Django as well (django-cerlery).

    The advantage of this pattern is that you can run the worker from the command line as well,
    via manage.py, for the sake of rapid development, easy testing, debugging, management, etc.

    :param commandname: name of a properly created Django management command, which exists
        inside the app/management/commands folder in one of the apps in your project.

    :param arguments: string containing command line arguments formatted like you would
        when calling the management command via manage.py in a shell

    :param restartwait: integer seconds to wait before restarting worker if it dies,
        or if a once-through command, acts as a thread-loop delay timer
    """

    def __init__(self, commandname,arguments="",restartwait=10,logger=""):
        super(DjangoWorkerThread, self).__init__()
        self.commandname = commandname
        self.arguments = arguments
        self.restartwait = restartwait
        self.name = commandname
        self.event = threading.Event()
        if logger:
            self.l = logger
        else:
            self.l = logging.getLogger('root')

    def run(self):
        """
        Start the thread.
        """

        try:
            exceptioncount = 0
            exceptionlimit = 10
            while not self.event.is_set():
                try:
                    if self.arguments:
                        self.l.info('Starting ' + self.name + '  worker thread with arguments ' + self.arguments)
                        call_command(self.commandname,self.arguments)
                    else:
                        self.l.info('Starting ' + self.name + '  worker thread with no arguments')
                        call_command(self.commandname)
                    self.event.wait(self.restartwait)

                except Exception as e:
                    self.l.error(self.commandname + ' Unkown error: {}'.format(str(e)))
                    exceptioncount += 1
                    if exceptioncount > exceptionlimit:
                        self.l.error(self.commandname + " : " + self.arguments + " : Exceeded exception retry limit, aborting.")
                        self.event.set()
        finally:
            self.l.info('Stopping command: ' + self.commandname + " " + self.arguments)

    def stop(self):
        """Nice Stop

        Stop nicely by setting an event.
        """
        self.l.info("Sending stop event to self...")
        self.event.set()
        #then make sure it's dead...and schwack it harder if not.
        #kill it with fire!  be mean to your software.  it will make you write better code.
        self.l.info("Sent stop event, checking to see if thread died.")
        if self.isAlive():
            self.l.info("Still not dead, telling self to murder self...")
            time.sleep( 0.1 )
            os._exit(1)

def start_worker(command_name, command_arguments="", restart_wait=10,logger=""):
    """
    Starts a background worker thread running a Django management command. 

    :param str command_name: the name of the Django management command to run,
        typically would be a custom command implemented in yourapp/management/commands,
        but could also be used to automate standard Django management tasks
    :param str command_arguments: a string containing the command line arguments 
        to supply to the management command, formatted as if one were invoking
        the command from a shell
    """
    if logger:
        l = logger
    else:
        l = logging.getLogger('root')

    # Start the thread
    l.info("Starting worker: "+ command_name + " : " + command_arguments + " : " + str(restart_wait) )
    worker = DjangoWorkerThread(command_name,command_arguments, restart_wait,l)
    worker.start()
    l.info("Worker started: "+ command_name + " : " + command_arguments + " : " + str(restart_wait) )

    # Return the thread instance
    return worker

#<----------------------------------------------------------------------------->

def stop_worker(worker,logger=""):
    """
    Gracefully shutsdown the worker thread 

    :param threading.Thread worker: the worker thread object
    """
    if logger:
        l = logger
    else:
        l = logging.getLogger('root')

    # Shutdown the thread
    l.info("Stopping worker: "+ worker.commandname + " : " + worker.arguments + " : " + str(worker.restartwait) )
    worker.stop()        
    worker.join(worker.restartwait)
    l.info("Worker stopped: "+ worker.commandname + " : " + worker.arguments + " : " + str(worker.restartwait) )
like image 134
Jonathan Nikkel Avatar answered Oct 22 '22 14:10

Jonathan Nikkel


Asynchronous tasks can be accomplished in Python using Celery. You can simply push the task to Celery queue and the task will be performed in an asynchronous way. You can then do some polling from the result page to check if it is completed.

Other alternative can be something like Tornado.

like image 45
Sudip Kafle Avatar answered Oct 22 '22 15:10

Sudip Kafle