Django: Signal/Method called after "AppConfig.ready()"

I'm afraid the answer is No. Populating the application registry happens in django.setup(). If you look at the source code, you will see that neither apps.registry.Apps.populate() nor django.setup() dispatch any signals upon completion.

Here are some ideas:

  • You could dispatch a custom signal yourself, but that would require that you do that in all entry points of your Django project, e.g. manage.py, wsgi.py and any scripts that use django.setup().

  • You could connect to request_started and disconnect when your handler is called.

  • If you are initializing some kind of property, you could defer that initialization until the first access.

If any of these approaches work for you obviously depends on what exactly you are trying to achieve.


So there is a VERY hackish way to accomplish what you might want...

Inside the django.apps.registry is the singleton apps which is used by Django to populate the applications. See setup in django.__init__.py.

The way that apps.populate works is it uses a non-reentrant (thread-based) locking mechanism to only allow apps.populate to happen in an idempotent, thread-safe manner.

The stripped down source for the Apps class which is what the singleton apps is instantiated from:

class Apps(object):

    def __init__(self, installed_apps=()):
        # Lock for thread-safe population.
        self._lock = threading.Lock()

    def populate(self, installed_apps=None):
        if self.ready:
            return

        with self._lock:
            if self.ready:
                return

            for app_config in self.get_app_configs():
                app_config.ready()

            self.ready = True

With this knowledge, you could create some threading.Thread's that await on some condition. These consumer threads will utilize threading.Condition to send cross-thread signals (which will enforce your ordering problem). Here is a mocked out example of how that would work:

import threading

from django.apps import apps, AppConfig

# here we are using the "apps._lock" to synchronize our threads, which
# is the dirty little trick that makes this work
foo_ready = threading.Condition(apps._lock)

class FooAppConfig(AppConfig):
    name = "foo"

    def ready(self):
        t = threading.Thread(name='Foo.ready', target=self._ready_foo, args=(foo_ready,))
        t.daemon = True
        t.start()

    def _ready_foo(self, foo_ready):
        with foo_ready:
            # setup foo
            foo_ready.notifyAll() # let everyone else waiting continue

class BarAppConfig(AppConfig):
    name = "bar"

    def ready(self):
        t = threading.Thread(name='Bar.ready', target=self._ready_bar, args=(foo_ready,))
        t.daemon = True
        t.start()

    def _ready_bar(self, foo_ready):
        with foo_ready:
            foo_ready.wait() # wait until foo is ready
            # setup bar

Again, this ONLY allows you to control the flow of the ready calls from the individual AppConfig's. This doesn't control the order models get loaded, etc.

But if your first assertion was true, you have an app.ready implementation that depends on another app being ready first, this should do the trick.

Reasoning:

Why Conditions? The reason this uses threading.Condition over threading.Event is two-fold. Firstly, conditions are wrapped in a locking layer. This means that you will continue to operate under controlled circumstances if the need arises (accessing shared resources, etc). Secondly, because of this tight level of control, staying inside the threading.Condition's context will allow you to chain the configurations in some desirable ordering. You can see how that might be done with the following snippet:

lock = threading.Lock()
foo_ready = threading.Condition(lock)
bar_ready = threading.Condition(lock)
baz_ready = threading.Condition(lock)

Why Deamonic Threads? The reason for this is, if your Django application were to die sometime between acquiring and releasing the lock in apps.populate, the background threads would continue to spin waiting for the lock to release. Setting them to daemon-mode will allow the process to exit cleanly without needing to .join those threads.