Eseguire il codice quando Django avvia ONCE solo?


177

Sto scrivendo una classe Django Middleware che voglio eseguire solo una volta all'avvio, per inizializzare un altro codice arbritario. Ho seguito la bella soluzione pubblicata da sdolan qui , ma il messaggio "Hello" viene inviato al terminale due volte . Per esempio

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

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

e nel mio file delle impostazioni di Django, ho la classe inclusa MIDDLEWARE_CLASSESnell'elenco.

Ma quando eseguo Django usando runtime server e richiedo una pagina, arrivo nel terminale

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

Qualche idea sul perché "Hello world" sia stampato due volte? Grazie.


1
solo per curiosità, hai capito perché il codice in init .py viene eseguito due volte?
Mutante,

3
@Mutant viene eseguito solo due volte con il server di esecuzione ... questo perché il server di esecuzione carica prima le app per ispezionarle e quindi avvia effettivamente il server. Anche in caso di caricamento automatico di RunServer, il codice viene eseguito una sola volta.
Pykler,

1
Caspita sono stato qui .... quindi grazie ancora per il commento @Pykler, è quello che mi chiedevo.
WesternGun

Risposte:


112

Aggiornamento dalla risposta di Pykler qui sotto: Django 1.7 ora ha un gancio per questo


Non farlo in questo modo.

Non vuoi "middleware" per una cosa di avvio una tantum.

Vuoi eseguire il codice al livello superiore urls.py. Quel modulo viene importato ed eseguito una volta.

urls.py

from django.confs.urls.defaults import *
from my_app import one_time_startup

urlpatterns = ...

one_time_startup()

1
@Andrei: i comandi di gestione sono un problema completamente separato. L'idea di avvio una tantum speciale prima di tutti i comandi di gestione è difficile da capire. Dovrai fornire qualcosa di specifico . Forse in un'altra domanda.
S.Lott

1
Ho provato a stampare un testo semplice in urls.py, ma non c'era assolutamente alcun risultato. Che cosa sta succedendo ?
Steve K,

8
Il codice urls.py viene eseguito solo alla prima richiesta (indovina che risponde alla domanda di @SteveK) (django 1.5)
lajarre

4
Questo viene eseguito una volta per ogni lavoratore, nel mio caso, viene eseguito 3 volte in totale.
Raffaello

9
@halilpazarlama Questa risposta non è aggiornata - dovresti usare la risposta di Pykler.
Mark Chackerian,

271

Aggiornamento: Django 1.7 ora ha un gancio per questo

file: myapp/apps.py

from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = "My Application"
    def ready(self):
        pass # startup code here

file: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Per Django <1.7

La risposta numero uno non sembra più funzionare, urls.py viene caricato alla prima richiesta.

Ciò che ha funzionato di recente è inserire il codice di avvio in uno dei tuoi init INSTALLp_APPS. Esmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

Quando si utilizza ./manage.py runserver... questo viene eseguito due volte, ma ciò è dovuto al fatto che runtime server ha alcuni trucchi per convalidare prima i modelli, ecc ... distribuzioni normali o anche quando si ricarica automaticamente il runtime server, questo viene eseguito solo una volta.


4
Penso che questo venga eseguito per ogni processo che carica il progetto. Quindi, non riesco a pensare al motivo per cui questo non funzionerebbe perfettamente in nessuno scenario di distribuzione. Questo funziona per i comandi di gestione. +1
Skylar Saveland,

2
Comprendo che questa soluzione può essere utilizzata per eseguire un codice arbitrario all'avvio del server, ma è possibile condividere alcuni dati che verrebbero caricati? Ad esempio, voglio caricare un oggetto che contiene una matrice enorme, inserire questa matrice in una variabile e usarla, tramite un'API Web, in ogni richiesta che un utente può fare. È possibile una cosa del genere?
Patrick,

2
La documentazione dice che questo non è il posto dove fare alcuna interazione con il database. Ciò lo rende inadatto per un sacco di codice. Dove potrebbe andare questo codice?
Segna il

3
EDIT: Un possibile hack è quello di controllare qualsiasi argomento delle righe di comando (x in sys.argv per x in ['makemigrations', 'migrate'])
Conchylicultor,

2
Se lo script è in esecuzione due volte, controlla questa risposta: stackoverflow.com/a/28504072/5443056
Braden Holt,

37

Questa domanda ha una risposta nel post sul blog Hook punto di entrata per progetti Django , che funzionerà per Django> = 1.4.

Fondamentalmente, puoi usarlo <project>/wsgi.pyper farlo, e verrà eseguito solo una volta, all'avvio del server, ma non quando esegui comandi o importi un particolare modulo.

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")

# Run startup code!
....

from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Aggiungendo di nuovo un commento per confermare che questo metodo eseguirà il codice una sola volta. Non sono necessari meccanismi di blocco.
ATOzTOA,

Gli script aggiunti qui sembrano non essere eseguiti all'avvio del framework di test
Lewisou,

Questa risposta è terminata con una ricerca di due giorni e mezzo per soluzioni che semplicemente non funzionavano.
Neil Munro,

3
Si noti che ciò viene eseguito quando viene effettuata la prima richiesta al sito Web, non all'avvio di Apache.
user984003,

18

Se aiuta qualcuno, oltre a di pykler risposta, "--noreload" impedisce opzione runserver dal comando all'avvio eseguendo due volte:

python manage.py runserver --noreload

Ma quel comando non ricaricherà il server di esecuzione anche dopo le modifiche di altri codici.


1
Grazie questo risolto il mio problema! Spero che quando schiererò questo non accadrà
Gabo,

2
In alternativa, è possibile controllare il contenuto di os.environ.get('RUN_MAIN')solo eseguire il codice di una volta nel processo principale (vedi stackoverflow.com/a/28504072 )
bdoering

Sì, questa risposta di pykler in più ha funzionato anche per me, poiché ha impedito le ready(self)chiamate multiple pur essendo in grado di avviarle solo una volta. Saluti!
DarkCygnus,

Per runserverimpostazione predefinita, Django avvia due processi con numeri pid distinti (diversi). --noreloadfa iniziare un processo.
Eugene Gr. Filippov

15

Come suggerito da @Pykler, in Django 1.7+ dovresti usare l'hook spiegato nella sua risposta, ma se vuoi che la tua funzione venga chiamata solo quando viene chiamato run server (e non quando si effettuano migrazioni, migrare, shell, ecc. ) e vuoi evitare eccezioni AppRegistryNotReady devi fare come segue:

file: myapp/apps.py

import sys
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = 'my_app'

    def ready(self):
        if 'runserver' not in sys.argv:
            return True
        # you must import your modules here 
        # to avoid AppRegistryNotReady exception 
        from .models import MyModel 
        # startup code here

12
funziona in modalità produzione? AFAIK nel prod. modalità non è stato avviato alcun "RunServer".
nerdoc,

Grazie per questo! Ho Advanced Python Scheduler nella mia app e non volevo eseguire lo scheduler quando eseguo i comandi manage.py.
lukik,

4

Si noti che non è possibile connettersi al database in modo affidabile o interagire con i modelli all'interno della AppConfig.readyfunzione (vedere l' avviso nei documenti).

Se è necessario interagire con il database nel codice di avvio, una possibilità è utilizzare il connection_createdsegnale per eseguire il codice di inizializzazione al momento della connessione al database.

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

Ovviamente, questa soluzione è per eseguire il codice una volta per connessione al database, non una volta per l'avvio del progetto. Quindi vorrai un valore ragionevole per l' CONN_MAX_AGEimpostazione in modo da non rieseguire il codice di inizializzazione su ogni richiesta. Si noti inoltre che il server di sviluppo ignora CONN_MAX_AGE, quindi si eseguirà il codice una volta per richiesta in fase di sviluppo.

Il 99% delle volte questa è una cattiva idea - il codice di inizializzazione del database dovrebbe andare nelle migrazioni - ma ci sono alcuni casi d'uso in cui non è possibile evitare l'inizializzazione tardiva e le avvertenze sopra sono accettabili.


2
Questa è una buona soluzione se devi accedere al database nel tuo codice di avvio. Un metodo semplice per farlo funzionare una sola volta è di avere la my_receiverfunzione stessa staccare dal connection_createdsegnale, in particolare, aggiungere la seguente alla my_receiverfunzione: connection_created.disconnect(my_receiver).
alan

1

se vuoi stampare "ciao mondo" una volta quando esegui il server, metti print ("ciao mondo") fuori dalla 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"

3
Ciao Oscar! Su SO, preferiamo che le risposte includano una spiegazione in inglese e non solo il codice. Potresti fornire una breve spiegazione di come / perché il tuo codice risolve il problema?
Max von Hippel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.