Tareas asíncronas simples en Django [sin Celery, Redis ni más sobreingeniería]
Si al igual que a mí, te enfrentas ante una situación en la que necesitas realizar ciertos procesos de manera asíncrona y te da mucha pereza tener que instalar dos servidores extra para hacer algo que casi cualquier shell de Linux ya puede hacer por defecto sin tener que instalar más carga en tus servidores; este es tu lugar.
El operador & en bash
If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0.
En el manual de usuario de bash (man bash), podemos encontrar algo que hace llamar mucho la atención: al usar el operador "&" al final de un comando, podremos ejecutar en una subshell el comando indicado.
Si en lugar de un solo operador "&", usamos dos: "&&", la lógica es algo así:
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
¿Qué quiere decir esto?
Que si mezclamos una orden como por ejemplo: sleep 5 && ls &, podremos observar que tras esperar 5 segundos, se ejecuta la impresión de archivos en el directorio de trabajo.
Sin embargo, en producción no queremos que nuestro proceso se detenga 5 segundos (o los que necesitemos en mostrar información), por lo que si lo ejecutamos así, tenemos un proceso que se ejecuta sin esperar a que pase ese tiempo:
import subprocess
# El siguiente llamado no bloqueará el proceso y podremos
# dejar en segundo plano, en una subshell la ejecución del
# código que querramos ejecutar.
subprocess.run("sleep 5 && ls&",shell=True)
ALERTA: Jamás de los jamases permitas la ejecución de procesos con shell=True a partir de datos suministrados por el usuario, esto puede llevar a ejecución de código arbitrario por parte de usuarios malintencionados y hacerte llegar a un estado en donde pienses que lo mejor sería que todos regresáramos a las cuevas a hacer fuego con pedernal.
Hay que tener en cuenta, que como la ejecución se realiza en un entorno separado al que se tiene con el servidor web que ejecuta Django, tener ciertas consideraciones como ejecutar el mismo intérprete de Python con las configuraciones de Django que deben hacerse, algo como esto
# Código del archivo que se va a invocar
import pathlib
import sys
import os
# Se asume que este archivo se encuentra en la raíz del proyecto, es
# decir, en donde se encuentra manage.py. Ajustar de ser necesario
path = pathlib.Path(__file__).parent.absolute()
# Cambia el directorio de trabajo a la raíz de Django
os.chdir(path)
# Añade al Path de Python ese directorio de Django, para poder importar
# sus módulos
sys.path.insert(0,path)
# Ajuste de las configuraciones del proyecto
os.environ["DJANGO_SETTINGS_MODULE"] = "nombre_de_proyecto.settings"
os.environ["FORKED_BY_MULTIPROCESSING"] = "1"
os.environ["DEBUG_DJANGO"] = "True"
import django
django.setup()
El resto de la magia es el poder invocar este archivo mediante el truco de los & y los && desde la vista, modelo, serializador o porción del código que se desea y voilá, logramos conseguir tareas asíncronas simples en Django sin sobreingeniería.
Observación: Usar un gestor en toda regla para esto, como Celery con Redis, puede traer muchas ventajas, esto no es un reemplazo total, pero puede servir para casos pequeños.
Comentarios
Publicar un comentario