Выполнить код, когда Django запускается только ОДИН РАЗ?

177

Я пишу класс промежуточного программного обеспечения Django, который хочу запускать только один раз при запуске, чтобы инициализировать некоторый другой произвольный код. Я следовал очень хорошему решению, опубликованному здесь sdolan , но сообщение «Hello» выводится на терминал дважды . Например

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

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

и в моем файле настроек Django у меня есть класс, включенный в MIDDLEWARE_CLASSESсписок.

Но когда я запускаю Django с помощью runserver и запрашиваю страницу, я попадаю в терминал

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

Есть идеи, почему «Hello world» печатается дважды? Спасибо.

Bob_94
источник
1
просто для любопытства, вы поняли, почему код в init .py выполняется дважды?
Мутант
3
@Mutant выполняется только дважды под runserver ... потому что runserver сначала загружает приложения для их проверки, а затем фактически запускает сервер. Даже при автоматической перезагрузке runserver код выполняется только один раз.
Пиклер
1
Вау, я был здесь .... так что еще раз спасибо за комментарий @Pykler, это то, что мне было интересно.
WesternGun

Ответы:

112

Обновление от ответа Пиклера ниже: Django 1.7 теперь имеет крючок для этого


Не делай так.

Вы не хотите «промежуточное программное обеспечение» для единовременного запуска.

Вы хотите выполнить код на верхнем уровне urls.py. Этот модуль импортируется и выполняется один раз.

urls.py

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

urlpatterns = ...

one_time_startup()
С. Лотт
источник
1
@ Андрей: Команды управления - это отдельная проблема. Трудно понять идею специального однократного запуска перед всеми командами управления. Вы должны будете предоставить что-то конкретное . Возможно, в другом вопросе.
S.Lott
1
Пробовал печатать простой текст в urls.py, но не было абсолютно никакого вывода. Что происходит ?
Стив К
8
Код urls.py выполняется только при первом запросе (думаю, он отвечает на вопрос @SteveK) (django 1.5)
lajarre
4
Это выполняется один раз для каждого работника, в моем случае это выполняется в общей сложности 3 раза.
Рафаэль
9
@halilpazarlama Этот ответ устарел - вы должны использовать ответ от Пиклера.
Марк Чакериан
271

Обновление: Django 1.7 теперь имеет крючок для этого

файл: myapp/apps.py

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

файл: myapp/__init__.py

default_app_config = 'myapp.apps.MyAppConfig'

Для Джанго <1,7

Ответ номер один больше не работает, urls.py загружается при первом запросе.

Что работало в последнее время, так это вставка кода запуска в любой из ваших INSTALLED_APPS init .py, напримерmyapp/__init__.py

def startup():
    pass # load a big thing

startup()

При использовании ./manage.py runserver... это выполняется дважды, но это потому, что у runserver есть некоторые хитрости для проверки моделей в первую очередь и т. Д. ... при нормальном развертывании или даже при автоматической перезагрузке runserver это выполняется только один раз.

Pykler
источник
4
Я думаю, что это выполняется для каждого процесса, который загружает проект. Поэтому я не могу понять, почему это не будет работать идеально при любом сценарии развертывания. Это работает для команд управления. +1
Skylar Saveland
2
Я понимаю, что это решение можно использовать для выполнения произвольного кода при запуске сервера, но возможно ли поделиться некоторыми данными, которые будут загружены? Например, я хочу загрузить объект, который содержит огромную матрицу, поместить эту матрицу в переменную и использовать ее через веб-API в каждом запросе, который может выполнить пользователь. Это возможно?
Патрик
2
В документации говорится, что это не место для взаимодействия с базой данных. Это делает его непригодным для большого количества кода. Куда может пойти этот код?
Марк
3
РЕДАКТИРОВАТЬ: Возможный взлом, чтобы проверить аргументы командной строки any (x в sys.argv для x в ['makemigrations', 'migrate'])
Conchylicultor
2
Если ваш сценарий выполняется дважды, вы можете проверить этот ответ: stackoverflow.com/a/28504072/5443056
Брэден Холт,
37

На этот вопрос хорошо ответили в сообщении в блоге Хук точки входа для проектов Django , который будет работать для Django> = 1.4.

По сути, вы можете использовать <project>/wsgi.pyэто, и он будет запущен только один раз, при запуске сервера, но не при запуске команд или импорте определенного модуля.

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
источник
Снова добавив комментарий, чтобы подтвердить, что этот метод выполнит код только один раз. Нет необходимости в каких-либо механизмах блокировки.
ATOzTOA
Сценарии, добавленные здесь, не выполняются при запуске тестовой среды
Lewisou
Этот ответ закончил два с половиной дня поиска решений, которые просто не работали.
Нил Мунро
3
Обратите внимание, что это выполняется при первом обращении к веб-сайту, а не при запуске Apache.
user984003
18

Если это поможет кому - то, в дополнение к pykler в ответ, «--noreload» опция предотвращает runserver от выполнения команды на старте дважды:

python manage.py runserver --noreload

Но эта команда не будет перезагружать runserver после других изменений кода.

AnaPana
источник
1
Спасибо, это решило мою проблему! Я надеюсь, что когда я развернусь, этого не произойдет
Габо
2
В качестве альтернативы, вы можете проверить содержимое os.environ.get('RUN_MAIN')только выполнить свой код один раз в главном процессе (см stackoverflow.com/a/28504072 )
bdoering
Да, этот плюс плюс ответ Пиклера сработал и для меня, так как он предотвращал множественные ready(self)вызовы, но при этом мог запускать их только один раз. Ура!
DarkCygnus
runserverПо умолчанию Django запускает два процесса с разными номерами pid. --noreloadзаставляет это начать один процесс.
Евгений Гр. Филиппов
15

Как предлагает @Pykler, в Django 1.7+ вы должны использовать ловушку, объясненную в его ответе, но если вы хотите, чтобы ваша функция вызывалась только при вызове run server (а не при вызове миграций, миграции, оболочки и т. Д. ), и вы хотите избежать исключений AppRegistryNotReady, которые вы должны сделать следующим образом:

файл: 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
это работает в производственном режиме? AFAIK в прод. В режиме «запущенный сервер» нет.
nerdoc
Спасибо за это! В моем приложении есть расширенный планировщик Python, и я не хотел запускать планировщик при выполнении команд manage.py.
Лукик
4

Обратите внимание, что вы не можете надежно подключиться к базе данных или взаимодействовать с моделями внутри AppConfig.readyфункции (см. Предупреждение в документации).

Если вам нужно взаимодействовать с базой данных в вашем коде запуска, одна из возможностей - использовать connection_createdсигнал для выполнения кода инициализации при подключении к базе данных.

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

Очевидно, что это решение предназначено для запуска кода один раз для подключения к базе данных, а не один раз для начала проекта. Поэтому вам нужно разумное значение CONN_MAX_AGEпараметра, чтобы не повторять код инициализации при каждом запросе. Также обратите внимание, что сервер разработки игнорирует CONN_MAX_AGE, поэтому вы будете запускать код один раз для каждого запроса в разработке.

В 99% случаев это плохая идея - код инициализации базы данных должен идти в процессе миграции, но есть некоторые случаи использования, когда вы не можете избежать поздней инициализации, и приведенные выше предостережения являются приемлемыми.

RichardW
источник
2
Это хорошее решение, если вам нужен доступ к базе данных в вашем коде запуска. Простой способ , чтобы заставить его работать только один раз , чтобы иметь my_receiverфункцию отключиться от connection_createdсигнала, в частности, добавить следующее к my_receiverфункции: connection_created.disconnect(my_receiver).
Алан
1

если вы хотите печатать «hello world» один раз при запуске сервера, поместите print («hello world») из класса 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
Привет, Оскар! На SO мы предпочитаем, чтобы ответы содержали пояснения на английском языке, а не только код. Не могли бы вы дать краткое объяснение того, как / почему ваш код решает проблему?
Макс фон Хиппель