Wykonywać kod, gdy Django uruchamia się tylko RAZ?

177

Piszę klasę Django Middleware, którą chcę wykonać tylko raz podczas uruchamiania, aby zainicjować inny kod arbritarny. Postępowałem zgodnie z bardzo fajnym rozwiązaniem opublikowanym tutaj przez sdolan , ale wiadomość „Hello” jest wysyłana do terminala dwukrotnie . Na przykład

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

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

aw moim pliku ustawień Django mam klasę uwzględnioną na MIDDLEWARE_CLASSESliście.

Ale kiedy uruchamiam Django używając runerver i żądam strony, dostaję się do terminala

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

Jakieś pomysły, dlaczego „Hello world” jest drukowane dwukrotnie? Dzięki.

Bob_94
źródło
1
tak dla ciekawości, czy zorientowałeś się, dlaczego kod w init .py jest wykonywany dwukrotnie?
Mutant
3
@Mutant jest wykonywany tylko dwa razy na serwerze uruchomieniowym ... to dlatego, że serwer uruchomieniowy najpierw ładuje aplikacje, aby je sprawdzić, a następnie faktycznie uruchamia serwer. Nawet po automatycznym załadowaniu serwera uruchomieniowego kod jest wykonywany tylko raz.
Pykler,
1
Wow, byłem tutaj .... więc jeszcze raz dziękuję za komentarz @Pykler, nad tym się zastanawiałem.
WesternGun

Odpowiedzi:

112

Aktualizacja odpowiedzi Pyklera poniżej: Django 1.7 ma teraz haczyk do tego


Nie rób tego w ten sposób.

Nie potrzebujesz „oprogramowania pośredniego” do jednorazowego uruchomienia.

Chcesz wykonać kod na najwyższym poziomie urls.py. Ten moduł jest importowany i wykonywany raz.

urls.py

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

urlpatterns = ...

one_time_startup()
S.Lott
źródło
1
@Andrei: Polecenia zarządzania to zupełnie osobny problem. Pomysł specjalnego jednorazowego uruchomienia przed wszystkimi poleceniami zarządzania jest trudny do zrozumienia. Musisz podać coś konkretnego . Może w innym pytaniu.
S.Lott
1
Próbowałem drukować prosty tekst w urls.py, ale nie było absolutnie żadnego wyniku. Co się dzieje ?
Steve K
8
Kod urls.py jest wykonywany tylko przy pierwszym żądaniu (zgadnij, że odpowiada na pytanie @SteveK) (django 1.5)
lajarre
4
Jest to wykonywane raz dla każdego pracownika, w moim przypadku jest to wykonywane łącznie 3 razy.
Raphael
9
@halilpazarlama Ta odpowiedź jest nieaktualna - powinieneś używać odpowiedzi od Pyklera.
Mark Chackerian
271

Aktualizacja: Django 1.7 ma teraz do tego haczyk

plik: myapp/apps.py

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

plik: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Dla Django <1.7

Wydaje się, że odpowiedź numer jeden już nie działa, adres urls.py jest ładowany przy pierwszym żądaniu.

To, co ostatnio zadziałało, to umieszczenie kodu startowego w dowolnym init .py INSTALLED_APPS, npmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

Podczas używania ./manage.py runserver... to jest wykonywane dwukrotnie, ale to dlatego, że runerver ma kilka sztuczek, aby najpierw sprawdzić poprawność modeli itp ... normalne wdrożenia lub nawet, gdy runerver ładuje się automatycznie, jest to wykonywane tylko raz.

Pykler
źródło
4
Myślę, że jest to wykonywane dla każdego procesu, który ładuje projekt. Nie mogę więc wymyślić, dlaczego nie działałoby to idealnie w żadnym scenariuszu wdrożenia. Działa to w przypadku poleceń zarządzania. +1
Skylar Saveland
2
Rozumiem, że to rozwiązanie można wykorzystać do wykonania dowolnego kodu podczas uruchamiania serwera, ale czy jest możliwe udostępnienie niektórych danych, które zostałyby załadowane? Na przykład chcę załadować obiekt, który zawiera ogromną macierz, umieścić tę macierz w zmiennej i używać jej za pośrednictwem internetowego interfejsu API w każdym żądaniu, które może wykonać użytkownik. Czy to możliwe?
Patrick,
2
Dokumentacja mówi, że to nie jest miejsce do interakcji z bazą danych. To sprawia, że ​​jest nieodpowiedni dla dużej ilości kodu. Gdzie może iść ten kod?
Mark
3
EDYCJA: Możliwym hackem jest sprawdzenie dowolnych argumentów wiersza poleceń (x in sys.argv for x in ['makemigrations', 'migrate'])
Conchylicultor
2
Jeśli twój skrypt działa dwukrotnie, sprawdź tę odpowiedź: stackoverflow.com/a/28504072/5443056
Braden Holt
37

Odpowiedzi na to pytanie można znaleźć w poście na blogu Hak punktu wejścia dla projektów Django , który będzie działał dla Django> = 1.4.

Zasadniczo możesz <project>/wsgi.pyto zrobić i zostanie uruchomione tylko raz, podczas uruchamiania serwera, ale nie podczas uruchamiania poleceń lub importowania określonego modułu.

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
źródło
Ponownie dodaj komentarz, aby potwierdzić, że ta metoda wykona kod tylko raz. Nie ma potrzeby stosowania żadnych mechanizmów blokujących.
ATOzTOA
Dodane tutaj skrypty wydają się nie być wykonywane po uruchomieniu platformy testowej
Lewisou
Ta odpowiedź zakończyła trwające dwa i pół dnia poszukiwania rozwiązań, które po prostu nie zadziałały.
Neil Munro,
3
Zwróć uwagę, że jest to wykonywane, gdy pierwsze żądanie jest wysyłane do witryny internetowej, a nie podczas uruchamiania Apache.
user984003
18

Jeśli to pomoże komuś, oprócz pykler za odpowiedź, „--noreload” zapobiega opcja uruchomieniowego z komendy na starcie wykonywania dwukrotnie:

python manage.py runserver --noreload

Ale to polecenie nie załaduje ponownie serwera uruchomieniowego po innych zmianach kodu.

AnaPana
źródło
1
Dzięki temu rozwiązałem mój problem! Mam nadzieję, że kiedy to wdrożę, to się nie stanie
Gabo
2
Alternatywnie możesz sprawdzić zawartość, os.environ.get('RUN_MAIN')aby wykonać kod tylko raz w procesie głównym (patrz stackoverflow.com/a/28504072 )
bdoering
Tak, ta odpowiedź plus pyklera również działała dla mnie, ponieważ zapobiegała wielu ready(self)połączeniom, a jednocześnie była w stanie rozpocząć je tylko raz. Twoje zdrowie!
DarkCygnus
Django runserverdomyślnie uruchamia dwa procesy z różnymi (różnymi) numerami pid. --noreloadsprawia, że ​​rozpoczyna jeden proces.
Eugene Gr. Philippov
15

Jak zasugerował @Pykler, w Django 1.7+ powinieneś użyć haka wyjaśnionego w jego odpowiedzi, ale jeśli chcesz, aby twoja funkcja była wywoływana tylko wtedy, gdy wywoływany jest serwer run (a nie podczas wykonywania migracji, wywoływana jest migracja, powłoka itp. ) i chcesz uniknąć wyjątków AppRegistryNotReady , musisz wykonać następujące czynności:

plik: 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
źródło
12
czy to działa w trybie produkcyjnym? AFAIK w prod. tryb nie został uruchomiony żaden serwer uruchomieniowy.
nerdoc
Dzięki za to! Mam w swojej aplikacji Advanced Python Scheduler i nie chciałem go uruchamiać podczas wykonywania poleceń manage.py.
lukik,
4

Zwróć uwagę, że nie możesz niezawodnie łączyć się z bazą danych ani wchodzić w interakcje z modelami wewnątrz AppConfig.readyfunkcji (zobacz ostrzeżenie w dokumentacji).

Jeśli potrzebujesz wejść w interakcję z bazą danych w swoim kodzie startowym, jedną z możliwości jest użycie connection_createdsygnału do wykonania kodu inicjalizacyjnego po połączeniu się z bazą danych.

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

Oczywiście to rozwiązanie służy do uruchamiania kodu raz na połączenie z bazą danych, a nie raz na rozpoczęcie projektu. Dlatego będziesz potrzebować rozsądnej wartości CONN_MAX_AGEustawienia, aby nie uruchamiać ponownie kodu inicjalizacji przy każdym żądaniu. Należy również pamiętać, że serwer deweloperski ignoruje CONN_MAX_AGE, więc kod zostanie uruchomiony raz na żądanie w trakcie tworzenia.

W 99% przypadków jest to zły pomysł - kod inicjalizacji bazy danych powinien być przenoszony podczas migracji - ale są pewne przypadki użycia, w których nie można uniknąć późnej inicjalizacji, a powyższe zastrzeżenia są dopuszczalne.

Richard W.
źródło
2
Jest to dobre rozwiązanie, jeśli potrzebujesz dostępu do bazy danych w kodzie startowym. Prosta metoda, aby go uruchomić tylko raz ma mieć my_receiverfunkcję odłączyć się od connection_createdsygnału, w szczególności, należy dodać następujące do my_receiverfunkcji: connection_created.disconnect(my_receiver).
alan
1

jeśli chcesz wydrukować "hello world" raz, kiedy uruchomisz serwer, umieść print ("hello world") poza klasą 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
źródło
3
Cześć Oscar! W SO wolimy, aby odpowiedzi zawierały wyjaśnienia w języku angielskim, a nie tylko kod. Czy mógłbyś podać krótkie wyjaśnienie, w jaki sposób / dlaczego Twój kod rozwiązuje problem?
Max von Hippel