256 votes

Exécuter le code au démarrage de Django UNE SEULE FOIS ?

J'écris une classe Django Middleware que je veux exécuter une seule fois au démarrage, pour initialiser un autre code arbitraire. J'ai suivi la très belle solution postée par sdolan. aquí mais le message "Hello" est envoyé au terminal. deux fois . Par exemple

from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings

class StartupMiddleware(object):
    def __init__(self):
        print "Hello world"
        raise MiddlewareNotUsed('Startup complete')

et dans mon fichier de configuration Django, j'ai inclus la classe dans la section MIDDLEWARE_CLASSES liste.

Mais lorsque j'exécute Django en utilisant runserver et que je demande une page, j'obtiens dans le terminal

Django version 1.3, using settings 'config.server'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Hello world
[22/Jul/2011 15:54:36] "GET / HTTP/1.1" 200 698
Hello world
[22/Jul/2011 15:54:36] "GET /static/css/base.css HTTP/1.1" 200 0

Avez-vous une idée de la raison pour laquelle "Hello world" est imprimé deux fois ? Merci.

1 votes

Juste par curiosité, avez-vous compris pourquoi le code dans init .py est exécuté deux fois ?

12 votes

@Mutant il n'est exécuté que deux fois sous runserver ... c'est parce que runserver charge d'abord les applications pour les inspecter et ensuite démarre réellement le serveur. Même lors du chargement automatique de runserver, le code n'est exécuté qu'une fois.

4 votes

Wow j'ai été ici.... donc merci encore pour le commentaire @Pykler, c'est ce que je me demandais.

17voto

J_Zar Points 957

Avec Django 3.1+, vous pouvez écrire ce code pour qu'il ne soit exécuté qu'une seule fois par méthode au démarrage. La différence avec les autres questions est que le processus de démarrage principal est vérifié (runserver par défaut démarre 2 processus, un comme observateur pour un rechargement rapide du code) :

import os 
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'app_name'

    def ready(self):
        if os.environ.get('RUN_MAIN'):
            print("STARTUP AND EXECUTE HERE ONCE.")
            # call here your code

Une autre solution est d'éviter la vérification d'environ mais d'appeler --noreload pour forcer un seul processus.

14voto

RichardW Points 362

Notez que vous ne peut pas la fiabilité de la connexion à la base de données ou de l'interaction avec les modèles à l'intérieur de l'interface utilisateur. AppConfig.ready (voir la avertissement dans la documentation).

Si vous devez interagir avec la base de données dans votre code de démarrage, une possibilité est d'utiliser la fonction connection_created pour exécuter le code d'initialisation lors de la connexion à la base de données.

from django.dispatch import receiver
from django.db.backends.signals import connection_created

@receiver(connection_created)
def my_receiver(connection, **kwargs):
    with connection.cursor() as cursor:
        # do something to the database

Évidemment, cette solution permet d'exécuter le code une fois par connexion à la base de données, et non une fois par lancement de projet. Vous voudrez donc une valeur raisonnable pour l'attribut CONN_MAX_AGE afin de ne pas ré-exécuter le code d'initialisation à chaque requête. Notez également que le serveur de développement ignore CONN_MAX_AGE Ainsi, vous n'exécuterez le code qu'une fois par demande en cours de développement.

Dans 99% des cas, c'est une mauvaise idée - le code d'initialisation de la base de données devrait aller dans les migrations - mais il y a certains cas d'utilisation où vous ne pouvez pas éviter une initialisation tardive et où les mises en garde ci-dessus sont acceptables.

2voto

Oscar Points 21

Si vous voulez imprimer "hello world" une seule fois lorsque vous lancez le serveur, mettez print ("hello world") hors de la classe StartupMiddleware

from django.core.exceptions import MiddlewareNotUsed
from django.conf import settings

class StartupMiddleware(object):
    def __init__(self):
        #print "Hello world"
        raise MiddlewareNotUsed('Startup complete')

print "Hello world"

0voto

nathan Points 1

Dans mon cas, j'utilise Django pour héberger un site, et j'utilise Heroku. J'utilise 1 dyno (tout comme 1 conteneur) chez Heroku, et ce dyno crée deux workers. Je veux faire tourner un bot discord dessus. J'ai essayé toutes les méthodes sur cette page et toutes sont invalides.

Comme il s'agit d'un déploiement, il ne devrait pas utiliser manage.py. A la place, il utilise gunicorn, que je ne sais pas comment ajouter --noreload paramètre. Chaque travailleur exécute wsgi.py une fois, donc chaque code sera exécuté deux fois. Et l'environnement local de deux travailleurs est le même.

Mais je remarque une chose, à chaque fois que Heroku se déploie, il utilise le même pid worker. Donc j'ai juste

if not sys.argv[1] in ["makemigrations", "migrate"]: # Prevent execute in some manage command
    if os.getpid() == 1: # You should check which pid Heroku will use and choose one.
        code_I_want_excute_once_only()

Je ne sais pas si le pid va changer dans le futur, j'espère qu'il sera toujours le même. Si vous avez une meilleure méthode pour vérifier quel est le travailleur, s'il vous plaît dites-moi.

0voto

Fahad Alduraibi Points 300

J'ai utilisé la solution acceptée de aquí qui vérifie si elle a été exécutée en tant que serveur, et non lors de l'exécution d'autres managy.py des commandes telles que migrate

apps.py :

from .tasks import tasks

class myAppConfig(AppConfig):
    ...

    def ready(self, *args, **kwargs):
        is_manage_py = any(arg.casefold().endswith("manage.py") for arg in sys.argv)
        is_runserver = any(arg.casefold() == "runserver" for arg in sys.argv)

        if (is_manage_py and is_runserver) or (not is_manage_py):
            tasks.is_running_as_server = True

Et comme cela sera toujours exécuté deux fois en mode développement, sans utiliser le paramètre --noreload J'ai ajouté un drapeau à déclencher lorsqu'il fonctionne en tant que serveur et j'ai mis mon code de démarrage dans le fichier urls.py qui n'est appelé qu'une seule fois.

tasks.py :

class tasks():
    is_running_as_server = False

    def runtask(msg):
        print(msg)

urls.py :

from . import tasks

task1 = tasks.tasks()

if task1.is_running_as_server:
    task1.runtask('This should print once and only when running as a server')

Donc pour résumer, j'utilise la fonction read() dans AppConfig pour lire les arguments et savoir comment le code a été exécuté. Mais comme en mode développement la fonction ready() est exécutée deux fois, une pour le serveur et une pour le rechargement du serveur quand le code change, alors que urls.py n'est exécuté qu'une seule fois pour le serveur. Dans ma solution, j'ai donc combiné les deux pour exécuter ma tâche une fois et seulement lorsque le code est exécuté en tant que serveur.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X