App Engine Python SDK version 1.7.4 (2)
[gae.git] / python / lib / django_1_4 / django / db / models / loading.py
blobc34468643fbae6b3d610bb54a71999c3d4e65e91
1 "Utilities for loading models and the modules that contain them."
3 from django.conf import settings
4 from django.core.exceptions import ImproperlyConfigured
5 from django.utils.datastructures import SortedDict
6 from django.utils.importlib import import_module
7 from django.utils.module_loading import module_has_submodule
9 import sys
10 import os
11 import threading
13 __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
14 'load_app', 'app_cache_ready')
16 class AppCache(object):
17 """
18 A cache that stores installed applications and their models. Used to
19 provide reverse-relations and for app introspection (e.g. admin).
20 """
21 # Use the Borg pattern to share state between all instances. Details at
22 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531.
23 __shared_state = dict(
24 # Keys of app_store are the model modules for each application.
25 app_store = SortedDict(),
27 # Mapping of installed app_labels to model modules for that app.
28 app_labels = {},
30 # Mapping of app_labels to a dictionary of model names to model code.
31 # May contain apps that are not installed.
32 app_models = SortedDict(),
34 # Mapping of app_labels to errors raised when trying to import the app.
35 app_errors = {},
37 # -- Everything below here is only used when populating the cache --
38 loaded = False,
39 handled = {},
40 postponed = [],
41 nesting_level = 0,
42 write_lock = threading.RLock(),
43 _get_models_cache = {},
46 def __init__(self):
47 self.__dict__ = self.__shared_state
49 def _populate(self):
50 """
51 Fill in all the cache information. This method is threadsafe, in the
52 sense that every caller will see the same state upon return, and if the
53 cache is already initialised, it does no work.
54 """
55 if self.loaded:
56 return
57 self.write_lock.acquire()
58 try:
59 if self.loaded:
60 return
61 for app_name in settings.INSTALLED_APPS:
62 if app_name in self.handled:
63 continue
64 self.load_app(app_name, True)
65 if not self.nesting_level:
66 for app_name in self.postponed:
67 self.load_app(app_name)
68 self.loaded = True
69 finally:
70 self.write_lock.release()
72 def _label_for(self, app_mod):
73 """
74 Return app_label for given models module.
76 """
77 return app_mod.__name__.split('.')[-2]
79 def load_app(self, app_name, can_postpone=False):
80 """
81 Loads the app with the provided fully qualified name, and returns the
82 model module.
83 """
84 self.handled[app_name] = None
85 self.nesting_level += 1
86 app_module = import_module(app_name)
87 try:
88 models = import_module('.models', app_name)
89 except ImportError:
90 self.nesting_level -= 1
91 # If the app doesn't have a models module, we can just ignore the
92 # ImportError and return no models for it.
93 if not module_has_submodule(app_module, 'models'):
94 return None
95 # But if the app does have a models module, we need to figure out
96 # whether to suppress or propagate the error. If can_postpone is
97 # True then it may be that the package is still being imported by
98 # Python and the models module isn't available yet. So we add the
99 # app to the postponed list and we'll try it again after all the
100 # recursion has finished (in populate). If can_postpone is False
101 # then it's time to raise the ImportError.
102 else:
103 if can_postpone:
104 self.postponed.append(app_name)
105 return None
106 else:
107 raise
109 self.nesting_level -= 1
110 if models not in self.app_store:
111 self.app_store[models] = len(self.app_store)
112 self.app_labels[self._label_for(models)] = models
113 return models
115 def app_cache_ready(self):
117 Returns true if the model cache is fully populated.
119 Useful for code that wants to cache the results of get_models() for
120 themselves once it is safe to do so.
122 return self.loaded
124 def get_apps(self):
125 "Returns a list of all installed modules that contain models."
126 self._populate()
128 # Ensure the returned list is always in the same order (with new apps
129 # added at the end). This avoids unstable ordering on the admin app
130 # list page, for example.
131 apps = [(v, k) for k, v in self.app_store.items()]
132 apps.sort()
133 return [elt[1] for elt in apps]
135 def get_app(self, app_label, emptyOK=False):
137 Returns the module containing the models for the given app_label. If
138 the app has no models in it and 'emptyOK' is True, returns None.
140 self._populate()
141 self.write_lock.acquire()
142 try:
143 for app_name in settings.INSTALLED_APPS:
144 if app_label == app_name.split('.')[-1]:
145 mod = self.load_app(app_name, False)
146 if mod is None:
147 if emptyOK:
148 return None
149 raise ImproperlyConfigured("App with label %s is missing a models.py module." % app_label)
150 else:
151 return mod
152 raise ImproperlyConfigured("App with label %s could not be found" % app_label)
153 finally:
154 self.write_lock.release()
156 def get_app_errors(self):
157 "Returns the map of known problems with the INSTALLED_APPS."
158 self._populate()
159 return self.app_errors
161 def get_models(self, app_mod=None,
162 include_auto_created=False, include_deferred=False,
163 only_installed=True):
165 Given a module containing models, returns a list of the models.
166 Otherwise returns a list of all installed models.
168 By default, auto-created models (i.e., m2m models without an
169 explicit intermediate table) are not included. However, if you
170 specify include_auto_created=True, they will be.
172 By default, models created to satisfy deferred attribute
173 queries are *not* included in the list of models. However, if
174 you specify include_deferred, they will be.
176 cache_key = (app_mod, include_auto_created, include_deferred, only_installed)
177 try:
178 return self._get_models_cache[cache_key]
179 except KeyError:
180 pass
181 self._populate()
182 if app_mod:
183 if app_mod in self.app_store:
184 app_list = [self.app_models.get(self._label_for(app_mod),
185 SortedDict())]
186 else:
187 app_list = []
188 else:
189 if only_installed:
190 app_list = [self.app_models.get(app_label, SortedDict())
191 for app_label in self.app_labels.iterkeys()]
192 else:
193 app_list = self.app_models.itervalues()
194 model_list = []
195 for app in app_list:
196 model_list.extend(
197 model for model in app.values()
198 if ((not model._deferred or include_deferred) and
199 (not model._meta.auto_created or include_auto_created))
201 self._get_models_cache[cache_key] = model_list
202 return model_list
204 def get_model(self, app_label, model_name,
205 seed_cache=True, only_installed=True):
207 Returns the model matching the given app_label and case-insensitive
208 model_name.
210 Returns None if no model is found.
212 if seed_cache:
213 self._populate()
214 if only_installed and app_label not in self.app_labels:
215 return None
216 return self.app_models.get(app_label, SortedDict()).get(model_name.lower())
218 def register_models(self, app_label, *models):
220 Register a set of models as belonging to an app.
222 for model in models:
223 # Store as 'name: model' pair in a dictionary
224 # in the app_models dictionary
225 model_name = model._meta.object_name.lower()
226 model_dict = self.app_models.setdefault(app_label, SortedDict())
227 if model_name in model_dict:
228 # The same model may be imported via different paths (e.g.
229 # appname.models and project.appname.models). We use the source
230 # filename as a means to detect identity.
231 fname1 = os.path.abspath(sys.modules[model.__module__].__file__)
232 fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__)
233 # Since the filename extension could be .py the first time and
234 # .pyc or .pyo the second time, ignore the extension when
235 # comparing.
236 if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]:
237 continue
238 model_dict[model_name] = model
239 self._get_models_cache.clear()
241 cache = AppCache()
243 # These methods were always module level, so are kept that way for backwards
244 # compatibility.
245 get_apps = cache.get_apps
246 get_app = cache.get_app
247 get_app_errors = cache.get_app_errors
248 get_models = cache.get_models
249 get_model = cache.get_model
250 register_models = cache.register_models
251 load_app = cache.load_app
252 app_cache_ready = cache.app_cache_ready