django---加载INSTALLED_APPS的源码分析
Posted 进击的小猿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django---加载INSTALLED_APPS的源码分析相关的知识,希望对你有一定的参考价值。
运行django项目,我们除了可以通过django图形界面启动,我们也可以通过命令行方式启动,启动方式如下:
python manage.py runserver
当我们创建django项目时候,会生成如下目录
mysite/
├── manage.py # 管理文件
└── mysite # 项目目录
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函数的对应关系
└── wsgi.py # runserver命令就使用wsgiref模块做简单的web server
我们来看下python manage.py runserver
中的manage.py
做了什么操作,可以用来启动django项目
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "s8day88.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
try:
import django
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)
上述代码中setdefault
是不存在添加一个键值对,存在则返回该键对应的值
然后执行try内部代码from django.core.management import execute_from_command_line
导入management
模块,最后执行该模块下的如下方法
execute_from_command_line(sys.argv)
我们来看下上述一行代码中的参数sys.argv
是什么东东?经过测试输出如下的结果
['E:/python/django_pro/manage.py', 'runserver', '8000']
参数意思就是manage.py
的绝对路径,命令,端口
接下来我们看下方法execute_from_command_line(sys.argv)
方法吧
def execute_from_command_line(argv=None):
utility = ManagementUtility(argv)
utility.execute()
上面代码实例了一个类ManagementUtility
对象,然后调用了一个对象方法execute
在构造对象时,在__init__
方法中初始化如下代码
def __init__(self, argv=None):
self.argv = argv or sys.argv[:]
self.prog_name = os.path.basename(self.argv[0])
self.settings_exception = None
赋值self.argv=argv
和self.prog_name="manage.py"
路径,接下来我们看下execute
方法吧!
代码有些长,我先把代码贴出来,然后在一点点看
def execute(self):
try:
subcommand = self.argv[1]
except IndexError:
subcommand = 'help' # Display help if no arguments were given.
parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*') # catch-all
try:
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
except CommandError:
pass # Ignore any option errors at this point.
try:
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
if settings.configured:
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
autoreload.check_errors(django.setup)()
except Exception:
apps.all_models = defaultdict(OrderedDict)
apps.app_configs = OrderedDict()
apps.apps_ready = apps.models_ready = apps.ready = True
_parser = self.fetch_command('runserver').create_parser('django', 'runserver')
_options, _args = _parser.parse_known_args(self.argv[2:])
for _arg in _args:
self.argv.remove(_arg)
else:
django.setup()
self.autocomplete()
if subcommand == 'help':
if '--commands' in args:
sys.stdout.write(self.main_help_text(commands_only=True) + '\\n')
elif len(options.args) < 1:
sys.stdout.write(self.main_help_text() + '\\n')
else:
self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
elif subcommand == 'version' or self.argv[1:] == ['--version']:
sys.stdout.write(django.get_version() + '\\n')
elif self.argv[1:] in (['--help'], ['-h']):
sys.stdout.write(self.main_help_text() + '\\n')
else:
self.fetch_command(subcommand).run_from_argv(self.argv)
首先parser
实例化一个CommandParser
类对象解析器,并且add_argument
添加如下的代码块
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*') # catch-all
我们具体也不知道到底是如何一步步解析的,也没有太大的必要去看CommandParser
类代码
我们继续看代码进入try语句,我么看到有如下一行代码
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
我们将options, args
打印出来,看下输出结果
Namespace(args=['8000'], pythonpath=None, settings=None) [] @@@@@@@@@@@@@@@
看到上述打印,我们大概猜出parser
对象,就是获取传递过来的['E:/python/django_pro/manage.py', 'runserver', '8000']
参数,然后自己在封装add_argument
需要的数据格式
接下来我们继续看代码,接下来继续执行如下代码
settings.INSTALLED_APPS
这里就熟悉了,这不是我们settings.py
文件中的如下配置么,是我们熟悉的admin
模块、用户认真auth
模块、sessions
模块等,获取了一个字符串的列表数据
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
"rbac.apps.RbacConfig"
]
我们知道subcommand = self.argv[1]
截取的字符串是runserver
接下来继续执行代码subcommand == 'runserver' and '--noreload' not in self.argv:
继而进入try语句代码autoreload.check_errors(django.setup)()
我们来看下check_errors
函数做了什么操作处理?
def check_errors(fn):
def wrapper(*args, **kwargs):
global _exception
try:
fn(*args, **kwargs)
except Exception:
_exception = sys.exc_info()
et, ev, tb = _exception
if getattr(ev, 'filename', None) is None:
# get the filename from the last item in the stack
filename = traceback.extract_tb(tb)[-1][0]
else:
filename = ev.filename
if filename not in _error_files:
_error_files.append(filename)
raise
return wrapper
哦原来check_errors
是一个装饰器函数,该方法接受一个参数,该参数类型是一个函数django.setup
,然后返回一个函数wrapper
,autoreload.check_errors(django.setup)()
代码意思就是最后执行了装饰器函数中的fn(*args, **kwargs)
,也就是传递的参数django.setup
,好我们稍后django.setup
做了什么操作?
基本上execute
函数也快执行完毕,我们看下最后的代码是做了什么收尾操作?
紧接着执行代码 self.autocomplete()
如下
def autocomplete(self):
、、、、
、、、、
、、、、
sys.exit(0)
大概意思就是sys
模块使用完毕,去退出该模块,从而做一些sys
模块的功能,这里就不去细看了
接下来继续执行execute
方法,也就是execute
方法的最后一行代码
self.fetch_command(subcommand).run_from_argv(self.argv)
上一行代码,我们通过代码run_from_argv
字面含义,大概能猜到应该是需要某个模块类对象,然后启动某些东西
ok我们在分析execute
方法时候,autoreload.check_errors(django.setup)()
接下来我们就回过头来,去分析它,看看它里面是如何进行加载配置的
如下是django.setup
代码部分
from __future__ import unicode_literals
from django.utils.version import get_version
VERSION = (1, 11, 9, 'final', 0)
__version__ = get_version(VERSION)
def setup(set_prefix=True):
from django.apps import apps
from django.conf import settings
from django.urls import set_script_prefix
from django.utils.encoding import force_text
from django.utils.log import configure_logging
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
if set_prefix:
set_script_prefix(
'/' if settings.FORCE_SCRIPT_NAME is None else force_text(settings.FORCE_SCRIPT_NAME)
)
apps.populate(settings.INSTALLED_APPS)
代码中configure_logging
大概是进行log配置,set_script_prefix
以及设置一些前缀补充完整,这两个方法直接过,我们看如下有价值的代码,是调用了模块from django.apps import apps
的类方法populate
apps.populate(settings.INSTALLED_APPS)
我也是先贴出populate
代码,然后在逐行分析
def populate(self, installed_apps=None):
if self.ready:
return
with self._lock:
if self.ready:
return
if self.app_configs:
raise RuntimeError("populate() isn't reentrant")
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
# Check for duplicate app names.
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
# Phase 2: import models modules.
for app_config in self.app_configs.values():
app_config.import_models()
self.clear_cache()
self.models_ready = True
# Phase 3: run ready() methods of app configs.
for app_config in self.get_app_configs():
app_config.ready()
self.ready = True
代码中通过with
函数开启了self._lock = threading.Lock()
锁,然后循环installed_apps
,如果是AppConfig
实例类型就返回,否则就去加载app和实例化该app的类对象app_config = AppConfig.create(entry)
@classmethod
def create(cls, entry):
try:
module = import_module(entry)
except ImportError:
module = None
mod_path, _, cls_name = entry.rpartition('.')
if not mod_path:
raise
else:
try:
entry = module.default_app_config
except AttributeError:
# Otherwise, it simply uses the default app config class.
return cls(entry, module)
else:
mod_path, _, cls_name = entry.rpartition('.')
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
if not issubclass(cls, AppConfig):
raise ImproperlyConfigured(
"'%s' isn't a subclass of AppConfig." % entry)
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,
)
)
# Entry is a path to an app config class.
return cls(app_name, app_module)
首先module = import_module(entry)
先加载模块,然后entry = module.default_app_config
获取各个app的配置,我就举一个admin
的例子把
class AdminConfig(SimpleAdminConfig):
"""The default AppConfig for admin which does autodiscovery."""
def ready(self):
super(AdminConfig, self).ready()
self.module.autodiscover()
接下来mod_path, _, cls_name = entry.rpartition('.')
进行分割,我们来看下分割后的结果集
mod_path django.contrib.admin.apps
mod_path django.contrib.auth.apps
mod_path django.contrib.contenttypes.apps
mod_path django.contrib.sessions.apps
mod_path django.contrib.messages.apps
mod_path django.contrib.staticfiles.apps
mod_path app01.apps
mod_path rbac.apps
mod_path django.contrib.admin.apps
mod_path django.contrib.auth.apps
mod_path django.contrib.contenttypes.apps
mod_path django.contrib.sessions.apps
mod_path django.contrib.messages.apps
mod_path django.contrib.staticfiles.apps
mod_path app01.apps
mod_path rbac.apps
、、、、
、、、、
最后就app_module = import_module(app_name)
加载,并且return cls(app_name, app_module)
返回实例对象
当加载完毕后,进行导入各个app
模块
for app_config in self.app_configs.values():
app_config.import_models()
然后修改其状态
for app_config in self.get_app_configs():
app_config.ready()
self.ready = True
最后看下构造的结果集
app_configs odict_values([<AdminConfig: admin>, <AuthConfig: auth>, <ContentTypesConfig: contenttypes>, <SessionsConfig: sessions>, <MessagesConfig: messages>, <StaticFilesConfig: staticfiles>, <App01Config: app01>, <RbacConfig: rbac>])
总结如下:
django调用python manage.py runserver
启动项目时候,利用CommandParser
构造并且解析数据,然后执行django.setup
方法,调用django.apps
模块来读取项目文件中的settings.py
拿到这面这几个app
,然后交给django.apps
的registry.py
和config.py
来进行统一的配置加载,然后最后self.fetch_command(subcommand).run_from_argv(self.argv)
加载
仓促完成,如有问题,评论留言,感谢~_~
以上是关于django---加载INSTALLED_APPS的源码分析的主要内容,如果未能解决你的问题,请参考以下文章
如何解决 Django 中的“请求设置 INSTALLED_APPS,但未配置设置。”错误?
加载 Django runserver 提供的静态文件时如何修复 cors 错误
django.core.exceptions.ImproperlyConfigured:请求设置 INSTALLED_APPS,但未配置设置。请帮帮我
Django1.10 错误:django.contrib.contenttypes.models.ContentType 未声明显式 app_label 且不在 INSTALLED_APPS 中的应用