Restarting Django on Non-Python File Changes
Problem
The django development server will restart every time a python source file is changed. Template changes don’t need to trigger a restart because they’re loaded on each use.
But what if we’re using dotenv and want django to automatically reload on .env
file changes?
How can we get django to reload when the file changes?
Basic Solution
In the relevant django module we want to create an apps.py
from pathlib import Path
from django.apps import AppConfig
from django.utils import autoreload
class FooBarAppConfig(AppConfig):
extra_reload_files = [
Path(__file__).parent / ".env",
# insert any extra files here
]
def add_extra_files(self, sender: autoreload.StatReloader, **kwargs):
sender.extra_files.update(self.extra_reload_files)
def ready(self):
if settings.DEBUG:
autoreload.autoreload_started.connect(self.add_extra_files)
Django provides a autoreload_started
signal that we can have connected to; we simply add our files to the end of the StatReloader
class’ file list.
The StatReloader
class polls the list of files every second for timestamp changes.
That’s inefficient if you have a lot of files, so django also supports watchman (setup instructions) however as of 2023-09-29 the latest pypi release is 6 years old and doesn’t work with more recent python versions. Even installing from source is broken.
werkzeug
An alternative is werkzeug, included with django-extensions
(via the runserver_plus
command)
By default werkzeug will also poll for filesystem changes however if the watchdog package is installed it will use OS notification events instead.
We can hook into werkzeug’s file monitoring list in the same way regardless of which backend it’s using to watch for file changes:
from pathlib import Path
from django.apps import AppConfig
from django.utils import autoreload
class FooBarAppConfig(AppConfig):
extra_reload_files = [
Path(__file__).parent / ".env",
# insert any extra files here
]
def add_extra_files(self, sender: autoreload.StatReloader, **kwargs):
sender.extra_files.update(self.extra_reload_files)
def ready(self):
if settings.DEBUG:
# check if werkzeug is being used
try:
from werkzeug.serving import is_running_from_reloader
except ImportError:
is_running_from_reloader = None
if is_running_from_reloader and is_running_from_reloader():
# we're running from the main runserver_plus process
if not hasattr(settings, 'RUNSERVER_PLUS_EXTRA_FILES'):
settings.RUNSERVER_PLUS_EXTRA_FILES = []
settings.RUNSERVER_PLUS_EXTRA_FILES += self.extra_reload_files
else:
# fall back to django's file watcher
autoreload.autoreload_started.connect(self.add_extra_files)
django-allianceutils
If you don’t want to implement this yourself, the django-allianceutils
package wraps all of this into a single convenient add_autoreload_extra_files