How to avoid AppConfig.ready() method running twice in Django

PythonDjango

Python Problem Overview


I want to execute some code at startup of Django server but I want it to run only once. Currently when I start the server it's executed twice. Documentation says that this might happen and:

> you should put a flag on your AppConfig classes to prevent re-running > code which should be executed exactly one time.

Any idea how to achieve this? Print statement below is still executed twice.

from django.apps import AppConfig
import app.mqtt
from apscheduler.schedulers.background import BackgroundScheduler

class MyAppConfig(AppConfig):
    name = 'app'
    verbose_name = "HomeIoT"
    run_already = False

    def ready(self):
        if MyAppConfig.run_already: return
        MyAppConfig.run_already = True
        print("Hello")

Python Solutions


Solution 1 - Python

When you use python manage.py runserver Django start two processes, one for the actual development server and other to reload your application when the code change.

You can also start the server without the reload option, and you will see only one process running will only be executed once :

python manage.py runserver --noreload

You can see this link, it resolves the ready() method running twice in Django

Solution 2 - Python

if you don't want to use --noreload you can:

replace the line in your app's __init__.py that you use to specify the config:

default_app_config = 'mydjangoapp.apps.MydjangoappConfig'

by this:

import os

if os.environ.get('RUN_MAIN', None) != 'true':
    default_app_config = 'mydjangoapp.apps.MydjangoappConfig'

Solution 3 - Python

I found this worked for me without using the --noreload flag in python manage.py runserver.

Check for an environment variable in the ready() method. The env variable does not persist after the application ends but DOES persist if the code changes are detected by the server and after it automatically reloads itself.

# File located in mysite/apps.py

from django.apps import AppConfig
import os

class CommandLineRunner(AppConfig):
    name = 'mysite'

    def ready(self):
        run_once = os.environ.get('CMDLINERUNNER_RUN_ONCE') 
        if run_once is not None:
            return
        os.environ['CMDLINERUNNER_RUN_ONCE'] = 'True' 

        # The code you want to run ONCE here
  

Solution 4 - Python

You need to implement locking. It is not a simple problem and the solution will not feel natural as you are dealing with processes and threads. Be warned there are many answers to the problem of locking, some simpler approaches:

A file lock: https://stackoverflow.com/questions/220525/ensure-a-single-instance-of-an-application-in-linux#220709 (note that threads share file lock by default so this answer needs to be expanded to account for threads).

There is also this answer which uses a Python package called tendo that encapsulates the a file lock implementation: https://stackoverflow.com/a/1265445/181907

Django itself provides an abstracted portable file locking utility in django.core.files.locks.

Solution 5 - Python

As Roberto mentioned you will need to implement locking in order to do this when running your server through the runserver command, if you want to use the default auto_reload functionality.

Django implements it's auto_reload via threading and so imports the AppConfig in two separate threads, the main 'command/watch' thread and the 'reload' thread running the server. Add a print statement to the module and you will see this in action. The 'main' thread loads the AppConfig files as part of it's BaseCommand execute, and the 'reload' thread then load them again during it's startup of the server.

If you have code that can not be run in both of these threads then your options are somewhat limited. You can implement a thread lock so that the 'reload' thread will not run ready(); you can move to a production environment to run your server (Gunicorn for example is very quick to setup, even for testing); or you can call your method in another way rather than using ready().

I would advise moving to a proper environment, but the best option really depends on what exactly the method you are calling is supposed to do.

Solution 6 - Python

It was found that AppConfig was fired twice and caused the scheduler to be started twice with this configuration. Instead, instantiate the scheduler in urls.py as shown below:

urlpatterns = [
    path('api/v1/', include(router.urls)),
    path('api/v1/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/v1/login/refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
    path('api/v1/', include('rest_registration.api.urls'))
]

scheduler = BackgroundScheduler()
scheduler.add_job(task.run, trigger='cron', hour=settings.TASK_RUNNER_HOURS, minute=settings.TASK_RUNNER_MINUTES, max_instances=1)
scheduler.start()

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKoooopView Question on Stackoverflow
Solution 1 - PythonRedBencityView Answer on Stackoverflow
Solution 2 - PythonMohamed BenkedadraView Answer on Stackoverflow
Solution 3 - PythonDaniel H.View Answer on Stackoverflow
Solution 4 - PythonRoberto RosarioView Answer on Stackoverflow
Solution 5 - PythonAirsView Answer on Stackoverflow
Solution 6 - PythonMarcos PaoloView Answer on Stackoverflow