a registry that stores the configuration of installed applications it also keeps track of models e g to provide reverse relations installedapps is set to none when creating the main registry because it cannot be populated at that point other registries must provide a list of installed apps and are populated immediately mapping of app labels model names model classes every time a model is imported modelbase new calls apps registermodel which creates an entry in allmodels all imported models are registered regardless of whether they re defined in an installed application and whether the registry has been populated since it isn t possible to reimport a module safely it could reexecute initialization code allmodels is never overridden or reset mapping of labels to appconfig instances for installed apps stack of appconfigs used to store the current state in setavailableapps and setinstalledapps whether the registry is populated for the autoreloader lock for threadsafe population maps applabel modelname tuples to lists of functions to be called when the corresponding model is ready used by this class s lazymodeloperation and dopendingoperations methods populate apps and models unless it s the main registry load application configurations and models import each application module and then each model module it is threadsafe and idempotent but not reentrant populate might be called by two threads in parallel on servers that create threads before initializing the wsgi callable an rlock prevents other threads from entering this section the compare and set operation below is atomic prevent reentrant calls to avoid running appconfig ready methods twice phase 1 initialize app configs and import app modules check for duplicate app names phase 2 import models modules phase 3 run ready methods of app configs raise an exception if all apps haven t been imported yet if not self appsready from django conf import settings if not ready is due to unconfigured settings accessing installedapps raises a more helpful improperlyconfigured exception settings installedapps raise appregistrynotreadyapps aren t loaded yet def checkmodelsreadyself import applications and return an iterable of app configs self checkappsready return self appconfigs values def getappconfigself applabel self checkappsready try return self appconfigsapplabel except keyerror message no installed app with label s applabel for appconfig in self getappconfigs if appconfig name applabel message did you mean s appconfig label break raise lookuperrormessage this method is performancecritical at least for django s test suite functools cache def getmodelsself includeautocreatedfalse includeswappedfalse self checkmodelsready result for appconfig in self appconfigs values result extendappconfig getmodelsincludeautocreated includeswapped return result def getmodelself applabel modelnamenone requirereadytrue if requireready self checkmodelsready else self checkappsready if modelname is none applabel modelname applabel split appconfig self getappconfigapplabel if not requireready and appconfig models is none appconfig importmodels return appconfig getmodelmodelname requirereadyrequireready def registermodelself applabel model since this method is called when models are imported it cannot perform imports because of the risk of import loops it mustn t call getappconfig modelname model meta modelname appmodels self allmodelsapplabel if modelname in appmodels if model name appmodelsmodelname name and model module appmodelsmodelname module warnings warn model s s was already registered reloading models is not advised as it can lead to inconsistencies most notably with related models applabel modelname runtimewarning stacklevel2 else raise runtimeerror conflicting s models in application s s and s modelname applabel appmodelsmodelname model appmodelsmodelname model self dopendingoperationsmodel self clearcache def isinstalledself appname self checkappsready return anyac name appname for ac in self appconfigs values def getcontainingappconfigself objectname self checkappsready candidates for appconfig in self appconfigs values if objectname startswithappconfig name subpath objectname removeprefixappconfig name if subpath or subpath0 candidates appendappconfig if candidates return sortedcandidates keylambda ac lenac name0 def getregisteredmodelself applabel modelname model self allmodelsapplabel getmodelname lower if model is none raise lookuperrormodel s s not registered applabel modelname return model functools cache def getswappablesettingsnameself tostring tostring tostring lower for model in self getmodelsincludeswappedtrue swapped model meta swapped is this model swapped out for the model given by tostring if swapped and swapped lower tostring return model meta swappable is this model swappable and the one given by tostring if model meta swappable and model meta labellower tostring return model meta swappable return none def setavailableappsself available available setavailable installed appconfig name for appconfig in self getappconfigs if not available issubsetinstalled raise valueerror available apps isn t a subset of installed apps extra apps s joinavailable installed self storedappconfigs appendself appconfigs self appconfigs label appconfig for label appconfig in self appconfigs items if appconfig name in available self clearcache def unsetavailableappsself enable a different set of installed apps for getappconfigs installed must be an iterable in the same format as installedapps setinstalledapps must be balanced with unsetinstalledapps even if it exits with an exception primarily used as a receiver of the settingchanged signal in tests this method may trigger new imports which may add new models to the registry of all imported models they will stay in the registry even after unsetinstalledapps since it isn t possible to replay imports safely e g that could lead to registering listeners twice models are registered when they re imported and never removed cancel a previous call to setinstalledapps self appconfigs self storedappconfigs pop self appsready self modelsready self ready true self clearcache def clearcacheself self getswappablesettingsname cacheclear call expire cache on each model this will purge the relation tree and the fields cache self getmodels cacheclear if self ready circumvent self getmodels to prevent that the cache is refilled this particularly prevents that an empty value is cached while cloning for appconfig in self appconfigs values for model in appconfig getmodelsincludeautocreatedtrue model meta expirecache def lazymodeloperationself function modelkeys base case no arguments just execute the function if not modelkeys function recursive case take the head of modelkeys wait for the corresponding model class to be imported and registered then apply that argument to the supplied function pass the resulting partial to lazymodeloperation along with the remaining model args and repeat until all models are loaded and all arguments are applied else nextmodel moremodels modelkeys this will be executed after the class corresponding to nextmodel has been imported and registered the func attribute provides ducktype compatibility with partials def applynextmodelmodel nextfunction partialapplynextmodel func model self lazymodeloperationnextfunction moremodels applynextmodel func function if the model has already been imported and registered partially apply it to the function now if not add it to the list of pending operations for the model where it will be executed with the model class as its sole argument once the model is ready try modelclass self getregisteredmodelnextmodel except lookuperror self pendingoperationsnextmodel appendapplynextmodel else applynextmodelmodelclass def dopendingoperationsself model key model meta applabel model meta modelname for function in self pendingoperations popkey functionmodel apps appsinstalledappsnone a registry that stores the configuration of installed applications it also keeps track of models e g to provide reverse relations installed_apps is set to none when creating the main registry because it cannot be populated at that point other registries must provide a list of installed apps and are populated immediately mapping of app labels model names model classes every time a model is imported modelbase __new__ calls apps register_model which creates an entry in all_models all imported models are registered regardless of whether they re defined in an installed application and whether the registry has been populated since it isn t possible to reimport a module safely it could reexecute initialization code all_models is never overridden or reset mapping of labels to appconfig instances for installed apps stack of app_configs used to store the current state in set_available_apps and set_installed_apps whether the registry is populated for the autoreloader lock for thread safe population maps app_label modelname tuples to lists of functions to be called when the corresponding model is ready used by this class s lazy_model_operation and do_pending_operations methods populate apps and models unless it s the main registry load application configurations and models import each application module and then each model module it is thread safe and idempotent but not reentrant populate might be called by two threads in parallel on servers that create threads before initializing the wsgi callable an rlock prevents other threads from entering this section the compare and set operation below is atomic prevent reentrant calls to avoid running appconfig ready methods twice phase 1 initialize app configs and import app modules check for duplicate app names phase 2 import models modules phase 3 run ready methods of app configs raise an exception if all apps haven t been imported yet if not ready is due to unconfigured settings accessing installed_apps raises a more helpful improperlyconfigured exception raise an exception if all models haven t been imported yet import applications and return an iterable of app configs import applications and returns an app config for the given label raise lookuperror if no application exists with this label this method is performance critical at least for django s test suite return a list of all installed models by default the following models aren t included auto created models for many to many relations without an explicit intermediate table models that have been swapped out set the corresponding keyword argument to true to include such models return the model matching the given app_label and model_name as a shortcut app_label may be in the form app_label model_name model_name is case insensitive raise lookuperror if no application exists with this label or no model exists with this name in the application raise valueerror if called with a single argument that doesn t contain exactly one dot since this method is called when models are imported it cannot perform imports because of the risk of import loops it mustn t call get_app_config check whether an application with this name exists in the registry app_name is the full name of the app e g django contrib admin look for an app config containing a given object object_name is the dotted python path to the object return the app config for the inner application in case of nesting return none if the object isn t in any registered app config similar to get_model but doesn t require that an app exists with the given app_label it s safe to call this method at import time even while the registry is being populated for a given model string e g auth user return the name of the corresponding settings name if it refers to a swappable model if the referred model is not swappable return none this method is decorated with functools cache because it s performance critical when it comes to migrations since the swappable settings don t change after django has loaded the settings there is no reason to get the respective settings attribute over and over again is this model swapped out for the model given by to_string is this model swappable and the one given by to_string restrict the set of installed apps used by get_app_config s available must be an iterable of application names set_available_apps must be balanced with unset_available_apps primarily used for performance optimization in transactiontestcase this method is safe in the sense that it doesn t trigger any imports cancel a previous call to set_available_apps enable a different set of installed apps for get_app_config s installed must be an iterable in the same format as installed_apps set_installed_apps must be balanced with unset_installed_apps even if it exits with an exception primarily used as a receiver of the setting_changed signal in tests this method may trigger new imports which may add new models to the registry of all imported models they will stay in the registry even after unset_installed_apps since it isn t possible to replay imports safely e g that could lead to registering listeners twice models are registered when they re imported and never removed cancel a previous call to set_installed_apps clear all internal caches for methods that alter the app registry this is mostly used in tests call expire cache on each model this will purge the relation tree and the fields cache circumvent self get_models to prevent that the cache is refilled this particularly prevents that an empty value is cached while cloning take a function and a number of app_label modelname tuples and when all the corresponding models have been imported and registered call the function with the model classes as its arguments the function passed to this method must accept exactly n models as arguments where n len model_keys base case no arguments just execute the function recursive case take the head of model_keys wait for the corresponding model class to be imported and registered then apply that argument to the supplied function pass the resulting partial to lazy_model_operation along with the remaining model args and repeat until all models are loaded and all arguments are applied this will be executed after the class corresponding to next_model has been imported and registered the func attribute provides duck type compatibility with partials if the model has already been imported and registered partially apply it to the function now if not add it to the list of pending operations for the model where it will be executed with the model class as its sole argument once the model is ready take a newly prepared model and pass it to each function waiting for it this is called at the very end of apps register_model
import functools
import sys
import threading
import warnings
from collections import Counter, defaultdict
from functools import partial

from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured

from .config import AppConfig


class Apps:
    

    def __init__(self, installed_apps=()):
        
        
        
        if installed_apps is None and hasattr(sys.modules[__name__], "apps"):
            raise RuntimeError("You must supply an installed_apps argument.")

        
        
        
        
        
        
        
        self.all_models = defaultdict(dict)

        
        self.app_configs = {}

        
        
        self.stored_app_configs = []

        
        self.apps_ready = self.models_ready = self.ready = False
        
        self.ready_event = threading.Event()

        
        self._lock = threading.RLock()
        self.loading = False

        
        
        
        self._pending_operations = defaultdict(list)

        
        if installed_apps is not None:
            self.populate(installed_apps)

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

        
        
        with self._lock:
            if self.ready:
                return

            
            
            if self.loading:
                
                
                raise RuntimeError("populate() isn't reentrant")
            self.loading = True

            
            for entry in installed_apps:
                if isinstance(entry, AppConfig):
                    app_config = entry
                else:
                    app_config = AppConfig.create(entry)
                if app_config.label in self.app_configs:
                    raise ImproperlyConfigured(
                        "Application labels aren't unique, "
                        "duplicates: %s" % app_config.label
                    )

                self.app_configs[app_config.label] = app_config
                app_config.apps = self

            
            counts = Counter(
                app_config.name for app_config in self.app_configs.values()
            )
            duplicates = [name for name, count in counts.most_common() if count > 1]
            if duplicates:
                raise ImproperlyConfigured(
                    "Application names aren't unique, "
                    "duplicates: %s" % ", ".join(duplicates)
                )

            self.apps_ready = True

            
            for app_config in self.app_configs.values():
                app_config.import_models()

            self.clear_cache()

            self.models_ready = True

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

            self.ready = True
            self.ready_event.set()

    def check_apps_ready(self):
        
        if not self.apps_ready:
            from django.conf import settings

            
            
            
            settings.INSTALLED_APPS
            raise AppRegistryNotReady("Apps aren't loaded yet.")

    def check_models_ready(self):
        
        if not self.models_ready:
            raise AppRegistryNotReady("Models aren't loaded yet.")

    def get_app_configs(self):
        
        self.check_apps_ready()
        return self.app_configs.values()

    def get_app_config(self, app_label):
        
        self.check_apps_ready()
        try:
            return self.app_configs[app_label]
        except KeyError:
            message = "No installed app with label '%s'." % app_label
            for app_config in self.get_app_configs():
                if app_config.name == app_label:
                    message += " Did you mean '%s'?" % app_config.label
                    break
            raise LookupError(message)

    
    @functools.cache
    def get_models(self, include_auto_created=False, include_swapped=False):
        
        self.check_models_ready()

        result = []
        for app_config in self.app_configs.values():
            result.extend(app_config.get_models(include_auto_created, include_swapped))
        return result

    def get_model(self, app_label, model_name=None, require_ready=True):
        
        if require_ready:
            self.check_models_ready()
        else:
            self.check_apps_ready()

        if model_name is None:
            app_label, model_name = app_label.split(".")

        app_config = self.get_app_config(app_label)

        if not require_ready and app_config.models is None:
            app_config.import_models()

        return app_config.get_model(model_name, require_ready=require_ready)

    def register_model(self, app_label, model):
        
        
        
        model_name = model._meta.model_name
        app_models = self.all_models[app_label]
        if model_name in app_models:
            if (
                model.__name__ == app_models[model_name].__name__
                and model.__module__ == app_models[model_name].__module__
            ):
                warnings.warn(
                    "Model '%s.%s' was already registered. Reloading models is not "
                    "advised as it can lead to inconsistencies, most notably with "
                    "related models." % (app_label, model_name),
                    RuntimeWarning,
                    stacklevel=2,
                )
            else:
                raise RuntimeError(
                    "Conflicting '%s' models in application '%s': %s and %s."
                    % (model_name, app_label, app_models[model_name], model)
                )
        app_models[model_name] = model
        self.do_pending_operations(model)
        self.clear_cache()

    def is_installed(self, app_name):
        
        self.check_apps_ready()
        return any(ac.name == app_name for ac in self.app_configs.values())

    def get_containing_app_config(self, object_name):
        
        self.check_apps_ready()
        candidates = []
        for app_config in self.app_configs.values():
            if object_name.startswith(app_config.name):
                subpath = object_name.removeprefix(app_config.name)
                if subpath == "" or subpath[0] == ".":
                    candidates.append(app_config)
        if candidates:
            return sorted(candidates, key=lambda ac: -len(ac.name))[0]

    def get_registered_model(self, app_label, model_name):
        
        model = self.all_models[app_label].get(model_name.lower())
        if model is None:
            raise LookupError("Model '%s.%s' not registered." % (app_label, model_name))
        return model

    @functools.cache
    def get_swappable_settings_name(self, to_string):
        
        to_string = to_string.lower()
        for model in self.get_models(include_swapped=True):
            swapped = model._meta.swapped
            
            if swapped and swapped.lower() == to_string:
                return model._meta.swappable
            
            if model._meta.swappable and model._meta.label_lower == to_string:
                return model._meta.swappable
        return None

    def set_available_apps(self, available):
        
        available = set(available)
        installed = {app_config.name for app_config in self.get_app_configs()}
        if not available.issubset(installed):
            raise ValueError(
                "Available apps isn't a subset of installed apps, extra apps: %s"
                % ", ".join(available - installed)
            )

        self.stored_app_configs.append(self.app_configs)
        self.app_configs = {
            label: app_config
            for label, app_config in self.app_configs.items()
            if app_config.name in available
        }
        self.clear_cache()

    def unset_available_apps(self):
        
        self.app_configs = self.stored_app_configs.pop()
        self.clear_cache()

    def set_installed_apps(self, installed):
        
        if not self.ready:
            raise AppRegistryNotReady("App registry isn't ready yet.")
        self.stored_app_configs.append(self.app_configs)
        self.app_configs = {}
        self.apps_ready = self.models_ready = self.loading = self.ready = False
        self.clear_cache()
        self.populate(installed)

    def unset_installed_apps(self):
        
        self.app_configs = self.stored_app_configs.pop()
        self.apps_ready = self.models_ready = self.ready = True
        self.clear_cache()

    def clear_cache(self):
        
        self.get_swappable_settings_name.cache_clear()
        
        
        self.get_models.cache_clear()
        if self.ready:
            
            
            for app_config in self.app_configs.values():
                for model in app_config.get_models(include_auto_created=True):
                    model._meta._expire_cache()

    def lazy_model_operation(self, function, *model_keys):
        
        
        if not model_keys:
            function()
        
        
        
        
        
        else:
            next_model, *more_models = model_keys

            
            
            
            def apply_next_model(model):
                next_function = partial(apply_next_model.func, model)
                self.lazy_model_operation(next_function, *more_models)

            apply_next_model.func = function

            
            
            
            
            try:
                model_class = self.get_registered_model(*next_model)
            except LookupError:
                self._pending_operations[next_model].append(apply_next_model)
            else:
                apply_next_model(model_class)

    def do_pending_operations(self, model):
        
        key = model._meta.app_label, model._meta.model_name
        for function in self._pending_operations.pop(key, []):
            function(model)


apps = Apps(installed_apps=None)