While experimenting with yamdl to prototype an idea for a wiki-like django site, I was curious about the various lower level Django signals. To better understand them, I wrote a simple app config to log the various checks. To help them be more visible in the terminal, I have also somewhat randomly added some assorted colors.
import os
from pathlib import Path
from django.apps import AppConfig
from django.core.checks import register
from django.db.backends.signals import connection_created
from django.db.models.signals import class_prepared
from django.dispatch import receiver
from django.utils.autoreload import BaseReloader, autoreload_started, file_changed
from django.utils.termcolors import make_style
red = make_style(fg="red")
blue = make_style(fg="blue")
cyan = make_style(fg="cyan")
yellow = make_style(fg="yellow")
magenta = make_style(fg="magenta")
bold_yellow = make_style(opts=("bold",), fg="yellow")
class TestappConfig(AppConfig):
name = __package__
def ready(self):
print(bold_yellow("ready"), __class__, os.getpid())
if os.environ.get("RUN_MAIN"):
print(bold_yellow("On app thread"))
@register
def test_checks(*args, **kwargs):
print(red("test_checks"), args, kwargs)
return []
@receiver(connection_created, dispatch_uid="print_connection_created")
def print_connection_created(*args, **kwargs):
print(red("connection_created"), args, kwargs)
@receiver(autoreload_started, dispatch_uid="print_autoreload_started")
def print_autoreload_started(sender: BaseReloader, **kwargs):
print(magenta("autoreload_started"), sender, kwargs)
@receiver(file_changed, dispatch_uid="print_file_changed")
def print_file_changed(sender: BaseReloader, file_path: Path, **kwargs):
print(magenta("file_changed"), sender, file_path, kwargs)
@receiver(class_prepared, dispatch_uid="print_class_prepared")
def print_class_prepared(sender, **kwargs):
print(cyan("print_class_prepared"), sender, kwargs)
After adding my new app to my INSTALLED_APPS
setting, I get a similar output when I run it.
uv run manage.py runserver
print_class_prepared <class 'django.contrib.contenttypes.models.ContentType'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.admin.models.LogEntry'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.auth.models.Permission'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.auth.models.Group_permissions'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.auth.models.Group'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.auth.models.User_groups'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.auth.models.User_user_permissions'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.auth.models.User'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
print_class_prepared <class 'django.contrib.sessions.models.Session'> {'signal': <django.dispatch.dispatcher.Signal object at 0x10b7d8e30>}
ready <class 'sigtest.testapp.apps.TestappConfig'> 16393
print_class_prepared <class 'django.contrib.contenttypes.models.ContentType'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.admin.models.LogEntry'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.auth.models.Permission'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.auth.models.Group_permissions'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.auth.models.Group'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.auth.models.User_groups'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.auth.models.User_user_permissions'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.auth.models.User'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
print_class_prepared <class 'django.contrib.sessions.models.Session'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
ready <class 'sigtest.testapp.apps.TestappConfig'> 16395
On app thread
Watching for file changes with StatReloader
Performing system checks...
test_checks () {'app_configs': None, 'databases': None}
autoreload_started <django.utils.autoreload.StatReloader object at 0x1034e14f0> {'signal': <django.dispatch.dispatcher.Signal object at 0x1028081d0>}
System check identified no issues (0 silenced).
connection_created () {'signal': <django.dispatch.dispatcher.Signal object at 0x1031824b0>, 'sender': <class 'django.db.backends.sqlite3.base.DatabaseWrapper'>, 'connection': <DatabaseWrapper vendor='sqlite' alias='default'>}
print_class_prepared <class 'django.db.migrations.recorder.MigrationRecorder.Migration.<locals>.Migration'> {'signal': <django.dispatch.dispatcher.Signal object at 0x102a7a540>}
December 24, 2024 - 10:23:57
Django version 5.1.4, using settings 'sigtest.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
file_changed <django.utils.autoreload.StatReloader object at 0x1015447a0> **/sigtest/testapp/apps.py {'signal': <django.dispatch.dispatcher.Signal object at 0x100df15b0>}
**/sigtest/testapp/apps.py changed, reloading.
It is interesting that class_prepared
and our app config’s ready
fires twice.
When using runserver
it loads the application, and then starts a thread for processing requests.
In the now running thread, it will then run system checks before triggering the autoreload_started
signal.
It is also interesting that this point it will start connecting to the database and trigger class_prepared
on the default Migration
class.
After changing a python file for a test, can also see where file_changed
fired.
I want to attempt my own interpretation of something like yamdl
so it seems like autoreload_started
and file_changed
are the signals I will need to consider.
It does not seem to be documented, but from reading the Django code, returning True
from a file_changed
handler prevents restarting the watch server, while anything else tells Django to restart the worker.
This will also be handy for what I’m working on.
Edit 2025-01-02: I have added a sample app to https://github.com/kfdm/django_debug_signals and uploaded to https://pypi.org/project/django-debug-signals/ to save others a bit of typing.