diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 3d6482f236..1b9597ee3d 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -11,15 +11,12 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.management.base import BaseCommand, CommandError, handle_default_options from django.core.management.color import color_style +from django.utils import lru_cache from django.utils import six # For backwards compatibility: get_version() used to be in this module. from django import get_version -# A cache of loaded commands, so that call_command -# doesn't have to reload every time it's called. -_commands = None - def find_commands(management_dir): """ @@ -85,6 +82,7 @@ def load_command_class(app_name, name): return module.Command() +@lru_cache.lru_cache(maxsize=None) def get_commands(): """ Returns a dictionary mapping command names to their callback applications. @@ -107,34 +105,32 @@ def get_commands(): The dictionary is cached on the first call and reused on subsequent calls. """ - global _commands - if _commands is None: - _commands = dict((name, 'django.core') for name in find_commands(__path__[0])) + commands = {name: 'django.core' for name in find_commands(__path__[0])} - # Find the installed apps + # Find the installed apps + try: + settings.INSTALLED_APPS + except ImproperlyConfigured: + # Still useful for commands that do not require functional + # settings, like startproject or help. + app_names = [] + else: + # Setup Django outside of the try/except block to avoid catching + # ImproperlyConfigured errors that aren't caused by the absence of + # a settings module. + django.setup() + app_configs = apps.get_app_configs() + app_names = [app_config.name for app_config in app_configs] + + # Find and load the management module for each installed app. + for app_name in app_names: try: - settings.INSTALLED_APPS - except ImproperlyConfigured: - # Still useful for commands that do not require functional - # settings, like startproject or help. - app_names = [] - else: - # Setup Django outside of the try/except block to avoid catching - # ImproperlyConfigured errors that aren't caused by the absence of - # a settings module. - django.setup() - app_configs = apps.get_app_configs() - app_names = [app_config.name for app_config in app_configs] + path = find_management_module(app_name) + commands.update({name: app_name for name in find_commands(path)}) + except ImportError: + pass # No management module - ignore this app - # Find and load the management module for each installed app. - for app_name in app_names: - try: - path = find_management_module(app_name) - _commands.update(dict((name, app_name) for name in find_commands(path))) - except ImportError: - pass # No management module - ignore this app - - return _commands + return commands def call_command(name, *args, **options): diff --git a/django/test/signals.py b/django/test/signals.py index b806592919..d5b98081b9 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -39,6 +39,9 @@ def update_installed_apps(**kwargs): # Rebuild templatetags module cache. from django.template import base base.templatetags_modules[:] = [] + # Rebuild management commands cache + from django.core.management import get_commands + get_commands.cache_clear() @receiver(setting_changed)