¿Ejecutar código cuando Django solo comienza UNA VEZ?

177

Estoy escribiendo una clase de Django Middleware que quiero ejecutar solo una vez al inicio, para inicializar algún otro código arbitrario. He seguido la muy buena solución publicada por sdolan aquí , pero el mensaje "Hola" se envía al terminal dos veces . P.ej

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

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

y en mi archivo de configuración de Django, tengo la clase incluida en el MIDDLEWARE_CLASSES lista.

Pero cuando ejecuto Django usando runserver y solicito una página, entro en la 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

¿Alguna idea de por qué "Hola mundo" se imprime dos veces? Gracias.

Bob_94
fuente
1
solo por curiosidad, ¿pensaste por qué el código en init .py se ejecuta dos veces?
Mutante
3
@Mutant solo se ejecuta dos veces en runserver ... eso se debe a que runserver primero carga las aplicaciones para inspeccionarlas y luego inicia el servidor. Incluso tras la recarga automática del servidor de ejecución, el código solo se ejecuta una vez.
Pykler
1
Wow, he estado aquí ... así que gracias de nuevo por el comentario @Pykler, eso es lo que me preguntaba.
WesternGun

Respuestas:

112

Actualización de la respuesta de Pykler a continuación: Django 1.7 ahora tiene un gancho para esto


No lo hagas de esta manera.

No desea "middleware" para una cosa de inicio única.

Desea ejecutar código en el nivel superior urls.py. Ese módulo se importa y ejecuta una vez.

urls.py

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

urlpatterns = ...

one_time_startup()
S.Lott
fuente
1
@Andrei: Los comandos de administración son un problema completamente diferente. La idea de un inicio especial de una sola vez antes de todos los comandos de administración es difícil de entender. Tendrás que proporcionar algo específico . Quizás en otra pregunta.
S.Lott
1
Intenté imprimir texto simple en urls.py, pero no hubo absolutamente ningún resultado. Qué está pasando ?
Steve K
8
El código urls.py se ejecuta solo en la primera solicitud (supongo que responde a la pregunta de @SteveK) (django 1.5)
lajarre
44
Esto se ejecuta una vez para cada trabajador, en mi caso, se ejecuta 3 veces en total.
Rafael
9
@halilpazarlama Esta respuesta está desactualizada: debería usar la respuesta de Pykler.
Mark Chackerian
271

Actualización: Django 1.7 ahora tiene un gancho para esto

expediente: myapp/apps.py

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

expediente: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Para Django <1.7

La respuesta número uno ya no parece funcionar, urls.py se carga a la primera solicitud.

Lo que ha funcionado últimamente es poner el código de inicio en cualquiera de sus INSTALLED_APPS init .py ej.myapp/__init__.py

def startup():
    pass # load a big thing

startup()

Cuando se usa ./manage.py runserver... esto se ejecuta dos veces, pero eso se debe a que runserver tiene algunos trucos para validar primero los modelos, etc. ... implementaciones normales o incluso cuando las recargas automáticas de runserver, esto solo se ejecuta una vez.

Pykler
fuente
44
Creo que esto se ejecuta para cada proceso que carga el proyecto. Por lo tanto, no puedo pensar por qué esto no funcionaría perfectamente en ningún escenario de implementación. Esto funciona para los comandos de administración. +1
Skylar Saveland
2
Entiendo que esta solución se puede usar para ejecutar algún código arbitrario cuando se inicia el servidor, pero ¿es posible compartir algunos datos que se cargarían? Por ejemplo, quiero cargar un objeto que contenga una matriz enorme, poner esta matriz en una variable y usarla, a través de una API web, en cada solicitud que un usuario pueda hacer. ¿Es tal cosa posible?
Patrick
2
La documentación dice que este no es el lugar para interactuar con la base de datos. Eso lo hace inadecuado para una gran cantidad de código. ¿Dónde podría ir este código?
Mark
3
EDITAR: Un posible truco es verificar los argumentos de las líneas de comando any (x en sys.argv para x en ['makemigrations', 'migrate'])
Conchylicultor
2
Si su script se ejecuta dos veces, consulte esta respuesta: stackoverflow.com/a/28504072/5443056
Braden Holt
37

Esta pregunta está bien respondida en la publicación del blog Enlace de punto de entrada para proyectos de Django , que funcionará para Django> = 1.4.

Básicamente, puede <project>/wsgi.pyhacerlo, y se ejecutará solo una vez, cuando se inicie el servidor, pero no cuando ejecute comandos o importe un módulo en particular.

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()
augustomen
fuente
Nuevamente agregando un comentario para confirmar que este método ejecutará el código solo una vez. No es necesario ningún mecanismo de bloqueo.
ATOzTOA
Las secuencias de comandos que se han agregado aquí parecen no ejecutarse cuando comienza el marco de prueba
Lewisou
Esta respuesta finalizó una búsqueda de dos días y medio de soluciones que simplemente no funcionaron.
Neil Munro
3
Tenga en cuenta que esto se ejecuta cuando se realiza la primera solicitud al sitio web, no cuando inicia Apache.
user984003
18

Si ayuda a alguien, además de la respuesta de pykler, la opción "--noreload" evita que el servidor de ejecución ejecute el comando al inicio dos veces:

python manage.py runserver --noreload

Pero ese comando no volverá a cargar runserver después de los cambios de otros códigos también.

AnaPana
fuente
1
Gracias esto resolvió mi problema! Espero que cuando despliegue esto no suceda
Gabo
2
Como alternativa, puede verificar el contenido de os.environ.get('RUN_MAIN')solo ejecutar su código una vez en el proceso principal (consulte stackoverflow.com/a/28504072 )
bdoering
Sí, esta respuesta más de pykler también funcionó para mí, ya que impidió las múltiples ready(self)llamadas y al mismo tiempo pudo iniciarlas solo una vez. ¡Salud!
DarkCygnus
runserverPor defecto, Django inicia dos procesos con números pid distintos (diferentes). --noreloadhace que comience un proceso.
Eugene Gr. Philippov
15

Según lo sugerido por @Pykler, en Django 1.7+ debe usar el gancho explicado en su respuesta, pero si desea que se llame a su función solo cuando se ejecuta el servidor de ejecución (y no cuando se realizan migraciones, migraciones, shell, etc.) ) y desea evitar las excepciones AppRegistryNotReady que debe hacer de la siguiente manera:

expediente: 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
Alberto Pianon
fuente
12
funciona esto en modo de producción? AFAIK en prod. modo no hay "runserver" iniciado.
nerdoc
¡Gracias por esto! Tengo el Programador avanzado de Python en mi aplicación y no quería ejecutar el programador al ejecutar los comandos manage.py.
lukik
4

Tenga en cuenta que no puede conectarse con fiabilidad a la base de datos o interactuar con modelos dentro de la AppConfig.readyfunción (consulte la advertencia en los documentos).

Si necesita interactuar con la base de datos en su código de inicio, una posibilidad es utilizar la connection_createdseñal para ejecutar el código de inicialización al conectarse a la base de datos.

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

Obviamente, esta solución es para ejecutar código una vez por conexión de base de datos, no una vez por inicio de proyecto. Por lo tanto, querrá un valor razonable para la CONN_MAX_AGEconfiguración para que no vuelva a ejecutar el código de inicialización en cada solicitud. También tenga en cuenta que el servidor de desarrollo ignoraCONN_MAX_AGE , por lo que ejecutará el código una vez por solicitud en desarrollo.

El 99% de las veces es una mala idea (el código de inicialización de la base de datos debe ir en migraciones), pero hay algunos casos de uso en los que no se puede evitar la inicialización tardía y las advertencias anteriores son aceptables.

RichardW
fuente
2
Esta es una buena solución si necesita acceder a la base de datos en su código de inicio. Un método sencillo para conseguir que se ejecute una sola vez es tener la my_receiverfunción de desconectarse de la connection_createdseñal, en concreto, añadir lo siguiente a la my_receiverfunción: connection_created.disconnect(my_receiver).
Alan
1

si desea imprimir "hello world" una vez cuando ejecute el servidor, ponga print ("hello world") fuera de clase 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"
Oscar
fuente
3
Hola oscar En SO, preferimos que las respuestas incluyan una explicación en inglés, y no solo el código. ¿Podría dar una breve explicación de cómo / por qué su código soluciona el problema?
Max von Hippel