Executar código quando o Django iniciar apenas UMA VEZ?

176

Estou escrevendo uma classe do Django Middleware que quero executar apenas uma vez na inicialização, para inicializar algum outro código arbritário. Eu segui a solução muito boa postada por sdolan aqui , mas a mensagem "Hello" é enviada ao terminal duas vezes . Por exemplo

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 no meu arquivo de configurações do Django, tenho a classe incluída na MIDDLEWARE_CLASSESlista.

Mas quando executo o Django usando o runserver e solicito uma página, entro no 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

Alguma idéia de por que "Hello world" é impresso duas vezes? Obrigado.

Bob_94
fonte
1
só por curiosidade, você imaginou por que o código no init .py é executado duas vezes?
Mutant
3
@Mutant, ele é executado apenas duas vezes no servidor de execução ... ou seja, porque o servidor de execução carrega primeiro os aplicativos para inspecioná-los e, em seguida, inicia o servidor. Mesmo após o carregamento automático do servidor de execução, o código é executado apenas uma vez.
Pykler
1
Uau, eu estive aqui .... então, obrigado novamente pelo comentário @Pykler, era isso que eu queria saber.
WesternGun 21/0318

Respostas:

111

Atualização da resposta do Pykler abaixo: O Django 1.7 agora tem um gancho para isso


Não faça assim.

Você não quer "middleware" para iniciar uma única vez.

Você deseja executar o código no nível superior urls.py. Esse módulo é importado e executado uma vez.

urls.py

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

urlpatterns = ...

one_time_startup()
S.Lott
fonte
1
@Andrei: Os comandos de gerenciamento são um problema totalmente separado. É difícil entender a idéia de uma inicialização especial antes de todos os comandos de gerenciamento. Você precisará fornecer algo específico . Talvez em outra pergunta.
31512 S.Lott
1
Tentei imprimir texto simples em urls.py, mas não havia absolutamente nenhuma saída. O que está acontecendo ?
Steve K
8
O código urls.py é executado somente no primeiro pedido (acho que responde à pergunta 's @SteveK) (Django 1.5)
lajarre
4
Isso é executado uma vez para cada trabalhador, no meu caso, é executado 3 vezes no total.
Raphael
9
@halilpazarlama Esta resposta está desatualizada - você deve usar a resposta do Pykler.
Mark Chackerian
271

Atualização: O Django 1.7 agora tem um gancho para isso

Arquivo: myapp/apps.py

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

Arquivo: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Para Django <1.7

A resposta número um parece não funcionar mais, urls.py é carregado na primeira solicitação.

O que funcionou ultimamente é colocar o código de inicialização em qualquer um dos arquivos init .py do seu INSTALLED_APPS.myapp/__init__.py

def startup():
    pass # load a big thing

startup()

Ao usar ./manage.py runserver... isso é executado duas vezes, mas isso ocorre porque o servidor de execução tem alguns truques para validar os modelos primeiro etc ... implantações normais ou mesmo quando o servidor de recarga é recarregado automaticamente, isso é executado apenas uma vez.

Pykler
fonte
4
Eu acho que isso é executado para cada processo que carrega o projeto. Portanto, não consigo pensar por que isso não funcionaria perfeitamente em nenhum cenário de implantação. Isso funciona para comandos de gerenciamento. 1
Skylar Saveland
2
Entendo que esta solução pode ser usada para executar algum código arbitrário quando o servidor é iniciado, mas é possível compartilhar alguns dados que seriam carregados? Por exemplo, quero carregar um objeto que contenha uma matriz enorme, colocar essa matriz em uma variável e usá-la, via uma API da Web, em cada solicitação que um usuário possa fazer. Isso é possível?
Patrick Patrick
2
A documentação diz que este não é o lugar para fazer qualquer interação com o banco de dados. Isso o torna inadequado para muito código. Para onde esse código pode ir?
Mark
3
EDIT: Um possível hack é verificar os argumentos das linhas de comando any (x em sys.argv para x em ['makemigrations', 'migrate']))
Conchylicultor
2
Se o seu script está sendo executado duas vezes que você verifique esta resposta: stackoverflow.com/a/28504072/5443056
Braden Holt
37

Esta pergunta é bem respondida na publicação do blog Gancho de ponto de entrada para projetos Django , que funcionará para Django> = 1.4.

Basicamente, você pode <project>/wsgi.pyfazer isso, e ele será executado apenas uma vez, quando o servidor iniciar, mas não quando você executar comandos ou importar um módulo específico.

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
fonte
Adicionando novamente um comentário para confirmar que este método executará o código apenas uma vez. Não há necessidade de nenhum mecanismo de bloqueio.
ATOzTOA
Scripts foram adicionados aqui não parecem ser executado quando a estrutura de teste começa
Lewisou
Essa resposta terminou uma pesquisa de dois dias e meio por soluções que simplesmente não funcionaram.
Neil Munro
3
Observe que isso é executado quando a primeira solicitação é feita ao site, não quando você inicia o Apache.
user984003
18

Se isso ajudar alguém, além da resposta do pykler, a opção "--noreload" evita que o runserver execute o comando na inicialização duas vezes:

python manage.py runserver --noreload

Mas esse comando não recarregará o runserver após as alterações de outros códigos.

AnaPana
fonte
1
Graças isso resolveu meu problema! Espero que quando eu implantar isso não aconteça
Gabo
2
Como alternativa, você pode verificar o conteúdo de os.environ.get('RUN_MAIN')apenas executar o seu código uma vez no processo principal (ver stackoverflow.com/a/28504072 )
bdoering
Sim, essa resposta do pykler mais funcionou para mim também, pois evitava as várias ready(self)chamadas enquanto ainda era possível iniciá-las apenas uma vez. Felicidades!
DarkCygnus 6/09/19
O Django's runserverpor padrão inicia dois processos com números pid distintos (diferentes). --noreloadfaz com que inicie um processo.
Eugene Gr. Philippov
15

Como sugerido por @Pykler, no Django 1.7+ você deve usar o gancho explicado em sua resposta, mas se quiser que sua função seja chamada apenas quando o servidor de execução for chamado (e não ao fazer migrações, migrações, shell etc.), é chamado ) e você deseja evitar as exceções AppRegistryNotReady , da seguinte maneira:

Arquivo: 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
fonte
12
isso é executado no modo de produção? AFAIK em prod. modo, não há "servidor de execução" iniciado.
Nerdoc 23/06
Obrigado por isso! Eu tenho o Advanced Python Scheduler no meu aplicativo e não queria executar o agendador ao executar os comandos manage.py.
lukik 7/09/19
4

Observe que você não pode conectar-se com confiabilidade ao banco de dados ou interagir com modelos dentro da AppConfig.readyfunção (consulte o aviso nos documentos).

Se você precisar interagir com o banco de dados no código de inicialização, uma possibilidade é usar o connection_createdsinal para executar o código de inicialização após a conexão com o banco de dados.

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 solução é para executar código uma vez por conexão com o banco de dados, não uma vez por início do projeto. Portanto, você desejará um valor razoável para a CONN_MAX_AGEconfiguração, para não executar novamente o código de inicialização em todas as solicitações. Observe também que o servidor de desenvolvimento ignora CONN_MAX_AGE, portanto você executará o código uma vez por solicitação em desenvolvimento.

Em 99% das vezes, isso é uma má idéia - o código de inicialização do banco de dados deve ser usado nas migrações - mas há alguns casos de uso em que você não pode evitar a inicialização tardia e as advertências acima são aceitáveis.

RichardW
fonte
2
Esta é uma boa solução se você precisar acessar o banco de dados no seu código de inicialização. Um método simples para obtê-lo para ser executado apenas uma vez é ter o my_receiverfunção de desconectar-se do connection_createdsinal, especificamente, adicione o seguinte para a my_receiverfunção: connection_created.disconnect(my_receiver).
quer
1

se você quiser imprimir "olá mundo" uma vez quando executar o servidor, coloque print ("olá mundo") fora da 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"
Oscar
fonte
3
Oi Oscar! No SO, preferimos que as respostas incluam uma explicação em inglês, e não apenas código. Você poderia dar uma breve explicação de como / por que seu código corrige o problema?
Max von Hippel