I have an AppConfig.ready()
implementation which depends on the readiness of an other application.
Is there a signal or method (which I could implement) which gets called after all application ready()
methods have been called?
I know that django processes the signals in the order of INSTALLED_APPS
.
But I don't want to enforce a particular ordering of INSTALLED_APPS
.
Example:
INSTALLED_APPS=[
'app_a',
'app_b',
...
]
How can "app_a" receive a signal (or method call) after "app_b" processed AppConfig.ready()
?
(reordering INSTALLED_APPS
is not a solution)
There are 3 types of signal. pre_save/post_save: This signal works before/after the method save(). pre_delete/post_delete: This signal works before after delete a model's instance (method delete()) this signal is thrown.
Django Signals - pre_save()To execute some code dealing with another part of your application before the object gets saved to the database, you have to use a pre_save signal. That way, the signal is sent at the beginning of a model's save() method.
Whenever you delete any object/row from django user model, Django post delete method will automatically start after delete method is called.
For the uninitiated, signals are synchronously (this will be important later) firing events that trigger handlers. There are a few common built in ones, we'll be discussing post_save and pre_save signals that are triggered whenever a Django model is saved.
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.
You can add a dummy app which only purpose is to fire a custom all_apps_are_ready
signal (or method call on AppConfig
).
Put this app at the end of INSTALLED_APPS
.
If this app receives the AppConfig.ready()
method call, you know that all other apps are ready.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With