Restarting Django on Non-Python File Changes

Django

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

Published: 2023-09-28