0
0
mirror of https://github.com/django/django.git synced 2024-11-29 06:03:25 +01:00

Terminated AppCache._populate() with extreme prejudice.

It was called _populate() before I renamed it to populate(). Since it
has been superseded by populate_models() there's no reason to keep it.

Removed the can_postpone argument of load_app() as it was only used by
populate(). It's a private API and there's no replacement. Simplified
load_app() accordingly. Then new version behaves exactly like the old
one even though it's much shorter.
This commit is contained in:
Aymeric Augustin 2013-12-18 14:49:29 +01:00
parent 2b56d69102
commit 86804ab063
9 changed files with 24 additions and 84 deletions

View File

@ -18,7 +18,7 @@ class BaseValidator(object):
def __init__(self): def __init__(self):
# Before we can introspect models, they need the app cache to be fully # Before we can introspect models, they need the app cache to be fully
# loaded so that inter-relations are set up correctly. # loaded so that inter-relations are set up correctly.
app_cache.populate() app_cache.populate_models()
def validate(self, cls, model): def validate(self, cls, model):
for m in dir(self): for m in dir(self):

View File

@ -2,17 +2,16 @@
from collections import defaultdict, OrderedDict from collections import defaultdict, OrderedDict
from contextlib import contextmanager from contextlib import contextmanager
from importlib import import_module
import os import os
import sys import sys
import warnings import warnings
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import import_lock, module_has_submodule from django.utils.module_loading import import_lock
from django.utils._os import upath from django.utils._os import upath
from .base import AppConfig, MODELS_MODULE_NAME from .base import AppConfig
class UnavailableApp(Exception): class UnavailableApp(Exception):
@ -51,15 +50,11 @@ class AppCache(object):
# Used by TransactionTestCase.available_apps for performance reasons. # Used by TransactionTestCase.available_apps for performance reasons.
self.available_apps = None self.available_apps = None
# Internal flags used when populating the cache. # Internal flags used when populating the master cache.
self._apps_loaded = False self._apps_loaded = not self.master
self._models_loaded = False self._models_loaded = not self.master
# -- Everything below here is only used when populating the cache -- # Cache for get_models.
self.loaded = False
self.handled = set()
self.postponed = []
self.nesting_level = 0
self._get_models_cache = {} self._get_models_cache = {}
def populate_apps(self): def populate_apps(self):
@ -138,71 +133,15 @@ class AppCache(object):
self._models_loaded = True self._models_loaded = True
def populate(self): def load_app(self, app_name):
"""
Fill in all the cache information. This method is threadsafe, in the
sense that every caller will see the same state upon return, and if the
cache is already initialised, it does no work.
"""
if self.loaded:
return
if not self.master:
self.loaded = True
return
# Note that we want to use the import lock here - the app loading is
# in many cases initiated implicitly by importing, and thus it is
# possible to end up in deadlock when one thread initiates loading
# without holding the importer lock and another thread then tries to
# import something which also launches the app loading. For details of
# this situation see #18251.
with import_lock():
if self.loaded:
return
for app_name in settings.INSTALLED_APPS:
if app_name in self.handled:
continue
self.load_app(app_name, can_postpone=True)
if not self.nesting_level:
for app_name in self.postponed:
self.load_app(app_name)
self.loaded = True
def load_app(self, app_name, can_postpone=False):
""" """
Loads the app with the provided fully qualified name, and returns the Loads the app with the provided fully qualified name, and returns the
model module. model module.
""" """
app_module = import_module(app_name)
self.handled.add(app_name)
self.nesting_level += 1
try:
models_module = import_module('%s.%s' % (app_name, MODELS_MODULE_NAME))
except ImportError:
# If the app doesn't have a models module, we can just swallow the
# ImportError and return no models for this app.
if not module_has_submodule(app_module, MODELS_MODULE_NAME):
models_module = None
# But if the app does have a models module, we need to figure out
# whether to suppress or propagate the error. If can_postpone is
# True then it may be that the package is still being imported by
# Python and the models module isn't available yet. So we add the
# app to the postponed list and we'll try it again after all the
# recursion has finished (in populate). If can_postpone is False
# then it's time to raise the ImportError.
else:
if can_postpone:
self.postponed.append(app_name)
return
else:
raise
finally:
self.nesting_level -= 1
app_config = AppConfig(app_name) app_config = AppConfig(app_name)
app_config.import_models(self.all_models[app_config.label]) app_config.import_models(self.all_models[app_config.label])
self.app_configs[app_config.label] = app_config self.app_configs[app_config.label] = app_config
return app_config.models_module
return models_module
def app_cache_ready(self): def app_cache_ready(self):
""" """
@ -211,7 +150,7 @@ class AppCache(object):
Useful for code that wants to cache the results of get_models() for Useful for code that wants to cache the results of get_models() for
themselves once it is safe to do so. themselves once it is safe to do so.
""" """
return self.loaded return self._models_loaded # implies self._apps_loaded.
def get_app_configs(self, only_with_models_module=False): def get_app_configs(self, only_with_models_module=False):
""" """
@ -220,7 +159,7 @@ class AppCache(object):
If only_with_models_module in True (non-default), only applications If only_with_models_module in True (non-default), only applications
containing a models module are considered. containing a models module are considered.
""" """
self.populate() self.populate_models()
for app_config in self.app_configs.values(): for app_config in self.app_configs.values():
if only_with_models_module and app_config.models_module is None: if only_with_models_module and app_config.models_module is None:
continue continue
@ -240,7 +179,7 @@ class AppCache(object):
If only_with_models_module in True (non-default), only applications If only_with_models_module in True (non-default), only applications
containing a models module are considered. containing a models module are considered.
""" """
self.populate() self.populate_models()
app_config = self.app_configs.get(app_label) app_config = self.app_configs.get(app_label)
if app_config is None: if app_config is None:
raise LookupError("No installed app with label %r." % app_label) raise LookupError("No installed app with label %r." % app_label)
@ -288,7 +227,7 @@ class AppCache(object):
return model_list return model_list
except KeyError: except KeyError:
pass pass
self.populate() self.populate_models()
if app_mod: if app_mod:
app_label = app_mod.__name__.split('.')[-2] app_label = app_mod.__name__.split('.')[-2]
if only_installed: if only_installed:
@ -331,7 +270,7 @@ class AppCache(object):
""" """
if not self.master: if not self.master:
only_installed = False only_installed = False
self.populate() self.populate_models()
if only_installed: if only_installed:
app_config = self.app_configs.get(app_label) app_config = self.app_configs.get(app_label)
if app_config is None: if app_config is None:
@ -496,7 +435,7 @@ class AppCache(object):
"[a.path for a in get_app_configs()] supersedes get_app_paths().", "[a.path for a in get_app_configs()] supersedes get_app_paths().",
PendingDeprecationWarning, stacklevel=2) PendingDeprecationWarning, stacklevel=2)
self.populate() self.populate_models()
app_paths = [] app_paths = []
for app in self.get_apps(): for app in self.get_apps():

View File

@ -139,7 +139,7 @@ class Deserializer(six.Iterator):
self.stream = stream_or_string self.stream = stream_or_string
# Make sure the app cache is loaded before deserialization starts # Make sure the app cache is loaded before deserialization starts
# (otherwise subclass calls to get_model() and friends might fail...) # (otherwise subclass calls to get_model() and friends might fail...)
app_cache.populate() app_cache.populate_models()
def __iter__(self): def __iter__(self):
return self return self

View File

@ -88,7 +88,7 @@ def Deserializer(object_list, **options):
db = options.pop('using', DEFAULT_DB_ALIAS) db = options.pop('using', DEFAULT_DB_ALIAS)
ignore = options.pop('ignorenonexistent', False) ignore = options.pop('ignorenonexistent', False)
app_cache.populate() app_cache.populate_models()
for d in object_list: for d in object_list:
# Look up the model and starting build a dict of data for it. # Look up the model and starting build a dict of data for it.

View File

@ -6,7 +6,7 @@ from functools import update_wrapper
from django.utils.six.moves import zip from django.utils.six.moves import zip
from django.core.apps import app_cache from django.core.apps import app_cache
from django.core.apps.cache import MODELS_MODULE_NAME from django.core.apps.base import MODELS_MODULE_NAME
import django.db.models.manager # NOQA: Imported to register signal handler. import django.db.models.manager # NOQA: Imported to register signal handler.
from django.conf import settings from django.conf import settings
from django.core.exceptions import (ObjectDoesNotExist, from django.core.exceptions import (ObjectDoesNotExist,

View File

@ -30,6 +30,6 @@ def get_app_errors():
try: try:
return app_cache.app_errors return app_cache.app_errors
except AttributeError: except AttributeError:
app_cache.populate() app_cache.populate_models()
app_cache.app_errors = {} app_cache.app_errors = {}
return app_cache.app_errors return app_cache.app_errors

View File

@ -93,7 +93,7 @@ class Options(object):
@property @property
def app_config(self): def app_config(self):
# Don't go through get_app_config to avoid triggering populate(). # Don't go through get_app_config to avoid triggering imports.
return self.app_cache.app_configs.get(self.app_label) return self.app_cache.app_configs.get(self.app_label)
@property @property

View File

@ -77,8 +77,9 @@ class EggLoadingTest(TestCase):
error. Refs #17667. error. Refs #17667.
""" """
app_cache = AppCache() app_cache = AppCache()
# Pretend we're the master app cache to test populate(). # Pretend we're the master app cache to test the population process.
app_cache.master = True app_cache._apps_loaded = False
app_cache._models_loaded = False
with override_settings(INSTALLED_APPS=('notexists',)): with override_settings(INSTALLED_APPS=('notexists',)):
with self.assertRaises(ImportError): with self.assertRaises(ImportError):
app_cache.get_model('notexists', 'nomodel') app_cache.get_model('notexists', 'nomodel')

View File

@ -128,7 +128,7 @@ def setup(verbosity, test_labels):
# Load all the ALWAYS_INSTALLED_APPS. # Load all the ALWAYS_INSTALLED_APPS.
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning) warnings.filterwarnings('ignore', 'django.contrib.comments is deprecated and will be removed before Django 1.8.', DeprecationWarning)
app_cache.populate() app_cache.populate_models()
# Load all the test model apps. # Load all the test model apps.
test_modules = get_test_modules() test_modules = get_test_modules()