應(yīng)用程序注冊表(app registry):
當運行Django項目時航邢,Django需要做的第一件事情是查找與該項目關(guān)聯(lián)的應(yīng)用程序(apps)妄帘,以便知道該項目使用的代碼器瘪。Django使用配置文件里的INSTALLED_APPS
設(shè)置來查找項目中的所有應(yīng)用程序株汉,并構(gòu)建要運行的應(yīng)用程序列表败明。Django在此上下文中將應(yīng)用程序列表稱為應(yīng)用程序注冊表app registry
。
比如下面一段setting.py配置文件中的代碼野宜,是剛創(chuàng)建好的項目都會在setting.py文件中默認生成的扫步。Django會循環(huán)載入這些apps和相應(yīng)的models。下面我們來分析Django是如何實現(xiàn)的這些操作速缨。
# project/setting.py
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
代碼分析
從django.setup
入手分析锌妻,看到導(dǎo)入apps模塊的代碼是from django.apps import apps
,因此可以確認模塊路徑是django.apps
旬牲。而且使用的是apps變量仿粹,馬上我們就能知道apps是什么東西。
它下面有3個文件原茅,__init__.py
吭历,config.py
,registry.py
擂橘,接下來分別來分析晌区。
# django/apps/__init__.py
from .config import AppConfig
from .registry import apps
__all__ = ['AppConfig', 'apps']
定義了__all__
變量,也就是當from <module> import *
通贞,只會導(dǎo)出'AppConfig'和'apps'朗若,接下來我們會發(fā)現(xiàn)它們兩分別在config.py
和registry.py
文件中。
先來看下config.py
文件
# django/apps/config.py
class AppConfig:
"""表示Django應(yīng)用程序及其配置的類昌罩。"""
def __init__(self, app_name, app_module):
# 到應(yīng)用程序的完整的Python路徑 e.g. 'django.contrib.admin'.
self.name = app_name
# 應(yīng)用程序的Root模塊 e.g. <module 'django.contrib.admin'
# from 'django/contrib/admin/__init__.py'>.
self.module = app_module
# 對持有此AppConfig的應(yīng)用程序注冊表的引用哭懈。 當注冊AppConfig實例時由注冊表設(shè)置。
self.apps = None
# 以下屬性可以在子類中的類級定義茎用,因此用了test-and-set模式遣总。
# 應(yīng)用程序的Python路徑的最后一部分, e.g. 'admin'.這個值在Django項目中必須是唯一的轨功。
if not hasattr(self, 'label'):
self.label = app_name.rpartition(".")[2]
# 應(yīng)用程序的Human-readable名字 e.g. "Admin".
if not hasattr(self, 'verbose_name'):
self.verbose_name = self.label.title()
# 到應(yīng)用程序目錄的文件系統(tǒng)路徑 e.g. '/path/to/django/contrib/admin'.
if not hasattr(self, 'path'):
self.path = self._path_from_module(app_module)
# 包含models的模塊 e.g. <module 'django.contrib.admin.models'
# from 'django/contrib/admin/models.py'>. 由import_models()設(shè)置. 如果沒有models旭斥,則為None
self.models_module = None
# 將model名稱(小寫格式)映射到model類。防止import_models()運行之前被意外訪問古涧,一開始設(shè)置為None垂券。
self.models = None
(此處略過部分不影響理解核心邏輯的代碼)...
@classmethod
def create(cls, entry):
"""
用INSTALLED_APPS中的條目來創(chuàng)建AppConfig實例的工廠方法,
其中條目可以是一個應(yīng)用程序模塊的路徑羡滑,也可以是一個應(yīng)用程序配置類的路徑圆米。
"""
try:
# 如果import_module成功,則INSTALLED_APPS中的條目是應(yīng)用程序模塊的路徑啄栓,
# 它可能使用default_app_config指定了一個應(yīng)用程序配置類(AppConfig類)娄帖;
# 否則,該條目可能是指向一個應(yīng)用程序配置類的路徑或者就是條錯誤的條目昙楚。
module = import_module(entry)
except ImportError:
# 發(fā)現(xiàn)按應(yīng)用程序模塊導(dǎo)入失敗近速,如果按應(yīng)用程序配置類(AppConfig類)導(dǎo)入也失敗的話,就觸發(fā)ImportError。
module = None
mod_path, _, cls_name = entry.rpartition('.')
# 安AppConfig類導(dǎo)入也失敗的話削葱,拋出異常奖亚,即ImportError。
if not mod_path:
raise
else:
try:
# 按應(yīng)用程序模塊導(dǎo)入成功析砸,判斷是否有指定應(yīng)用程序配置類昔字。
entry = module.default_app_config
except AttributeError:
# 沒有指定app config class, 用默認的應(yīng)用程序配置類。
return cls(entry, module)
else:
mod_path, _, cls_name = entry.rpartition('.')
# 如果我們達到了這里首繁,我們必須嘗試加載位于<mod_path>.<cls_name>的應(yīng)用程序配置類作郭。
mod = import_module(mod_path)
try:
cls = getattr(mod, cls_name)
except AttributeError:
if module is None:
# If importing as an app module failed, that error probably
# contains the most informative traceback. Trigger it again.
import_module(entry)
else:
raise
# Check for obvious errors. (This check prevents duck typing, but
# it could be removed if it became a problem in practice.)
if not issubclass(cls, AppConfig):
raise ImproperlyConfigured(
"'%s' isn't a subclass of AppConfig." % entry)
# Obtain app name here rather than in AppClass.__init__ to keep
# all error checking for entries in INSTALLED_APPS in one place.
try:
app_name = cls.name
except AttributeError:
raise ImproperlyConfigured(
"'%s' must supply a name attribute." % entry)
# Ensure app_name points to a valid module.
try:
app_module = import_module(app_name)
except ImportError:
raise ImproperlyConfigured(
"Cannot import '%s'. Check that '%s.%s.name' is correct." % (
app_name, mod_path, cls_name,
)
)
# 條目是一個應(yīng)用程序配置類的路徑。
return cls(app_name, app_module)
def get_model(self, model_name, require_ready=True):
"""
用給定的不區(qū)分大小寫的model_name返回model弦疮。
如果沒有此名稱的model存在夹攒,請拋出LookupError。
"""
if require_ready:
self.apps.check_models_ready()
else:
self.apps.check_apps_ready()
try:
return self.models[model_name.lower()]
except KeyError:
raise LookupError(
"App '%s' doesn't have a '%s' model." % (self.label, model_name))
(此處略過部分不影響理解核心邏輯的代碼)...
def import_models(self):對持有此AppConfig的應(yīng)用程序注冊表的引用
# 此應(yīng)用程序的models字典胁塞,維護在該應(yīng)用程序的的AppConfig附屬的應(yīng)用程序注冊表(變量Apps)的'all_models'屬性中咏尝。
self.models = self.apps.all_models[self.label]
if module_has_submodule(self.module, MODELS_MODULE_NAME):
models_module_name = '%s.%s' % (self.name, MODELS_MODULE_NAME)
self.models_module = import_module(models_module_name)
def ready(self):
"""
Override this method in subclasses to run code when Django starts.
"""
可以看到AppConfig是應(yīng)用程序配置類,主要用來管理Django應(yīng)用程序及其配置信息啸罢,所以每個應(yīng)用都會有一個應(yīng)用程序配置類编检。它核心的方法是一個工廠類方法,它接收INSTALLED_APPS中的條目來創(chuàng)建AppConfig實例扰才,接收的條目可以是一個應(yīng)用程序模塊的路徑蒙谓,這時候創(chuàng)建的是默認的AppConfig實例;接收的條目也可以是一個應(yīng)用程序配置類的路徑训桶,這時候創(chuàng)建的就是用戶自己定義的AppConfig實例。
接著分析文件registry.py
酣倾,其實我們也能猜到它就是注冊表了舵揭,維護項目所有的應(yīng)用程序配置信息。
# django/apps/registry.py
class Apps:
"""
存儲所有應(yīng)用程序配置信息的注冊表躁锡。它還跟蹤models午绳,例如 提供反向關(guān)系。
"""
def __init__(self, installed_apps=()):
# installed_apps is set to None when creating the master registry
# because it cannot be populated at that point. Other registries must
# provide a list of installed apps and are populated immediately.
if installed_apps is None and hasattr(sys.modules[__name__], 'apps'):
raise RuntimeError("You must supply an installed_apps argument.")
# model的映射表映之,app labels => model names => model classes
self.all_models = defaultdict(OrderedDict)
# AppConfig實例的映射表
self.app_configs = OrderedDict()
# Stack of app_configs. Used to store the current state in
# set_available_apps and set_installed_apps.
self.stored_app_configs = []
# Whether the registry is populated.
self.apps_ready = self.models_ready = self.ready = False
# Lock for thread-safe population.
self._lock = threading.RLock()
self.loading = False
# 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.
self._pending_operations = defaultdict(list)
# Populate apps and models, unless it's the master registry.
if installed_apps is not None:
self.populate(installed_apps)
def populate(self, installed_apps=None):
"""
加載應(yīng)用程序配置和models拦焚。
先導(dǎo)入每個應(yīng)用模塊,然后再導(dǎo)入每個model模塊杠输。
它是線程安全和冪等的赎败,但不可重入(reentrant)。
"""
if self.ready:
return
# populate() might be called by two threads in parallel on servers
# that create threads before initializing the WSGI callable.
with self._lock:
if self.ready:
return
# An RLock prevents other threads from entering this section. The
# compare and set operation below is atomic.
if self.loading:
# Prevent reentrant calls to avoid running AppConfig.ready()
# methods twice.
raise RuntimeError("populate() isn't reentrant")
self.loading = True
# 階段1:遍歷installed_apps蠢甲,初始化每一條條目對應(yīng)的應(yīng)用程序配置并導(dǎo)入應(yīng)用程序模塊僵刮。
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
# 檢查重復(fù)的應(yīng)用程序名稱。
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))
# 成功導(dǎo)入導(dǎo)入應(yīng)用模塊
self.apps_ready = True
# 階段2:導(dǎo)入models模塊。
for app_config in self.app_configs.values():
# 這里調(diào)用的上面分析的AppConfig的import_models方法
app_config.import_models()
self.clear_cache()
self.models_ready = True
# 階段3:運行每個應(yīng)用程序配置的ready()方法搞糕。
for app_config in self.get_app_configs():
# 這里調(diào)用的上面分析的AppConfig的ready方法
app_config.ready()
self.ready = True
(此處略過部分不影響理解核心邏輯的代碼)...
apps = Apps(installed_apps=None)
最后的apps = Apps(installed_apps=None)
勇吊,就是__init__.py
文件包含的apps變量,是個注冊表Apps的實例窍仰。以及我們回去看django.setup
汉规,會發(fā)現(xiàn)那里是調(diào)用apps.populate(settings.INSTALLED_APPS)
方法的地方,從這時候開始循環(huán)載入apps和相應(yīng)的models驹吮。
總結(jié)
當Django由wsgi啟動或者management命令啟動時针史,會由django.setup()
負責填充應(yīng)用程序注冊表。它通過以下方式配置Django:
1)加載配置文件钥屈,生成settings對象悟民;2)設(shè)置日志;3)如果set_prefix為True篷就,則將URL解析器腳本前綴設(shè)置為FORCE_SCRIPT_NAME(如果已定義)射亏,否則則為/
;4)初始化應(yīng)用程序注冊表竭业。
其中應(yīng)用程序注冊表分為三個階段初始化智润。 在每個階段,Django按照INSTALLED_APPS的順序處理所有應(yīng)用程序未辆。1)首先會導(dǎo)入INSTALLED_APPS中所有應(yīng)用程序(apps)窟绷;2)嘗試導(dǎo)入每個應(yīng)用程序的models子模塊(如果有的話);3)最后運行每個應(yīng)用程序配置的ready()方法。