入口manage.py
文件
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
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?"
) from exc
execute_from_command_line(sys.argv)
raise A from B
是一种很Pythonic的关于异常处理的写法,这篇文章讲的很清楚。设置环境变量之后,命令参数的列表传到了 execute_from_command_line
中。
命令管理工具
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
utility = ManagementUtility(argv)
utility.execute()
class ManagementUtility:
"""
Encapsulate the logic of the django-admin and manage.py utilities.
"""
def __init__(self, argv=None):
self.argv = argv or sys.argv[:]
self.prog_name = os.path.basename(self.argv[0])
if self.prog_name == '__main__.py':
self.prog_name = 'python -m django'
self.settings_exception = None
- 初始化时对命令参数第一次判断(
prog_name
是 manage.py
或者django-admin
) ,实例化ManagementUtility类后调用了 execute() 方法,在这个方法中,会对命令参数进行处理。
execute() 方法
def execute(self):
"""
Given the command-line arguments, figure out which subcommand is being
run, create a parser appropriate to that command, and run it.
"""
try:
subcommand = self.argv[1]
except IndexError:
subcommand = 'help'
parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
parser.add_argument('--settings')
parser.add_argument('--pythonpath')
parser.add_argument('args', nargs='*')
try:
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
except CommandError:
pass
try:
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
except ImportError 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 not options.args:
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)
- 将options, args打印出来,看下输出结果
Namespace(args=[], pythonpath=None, settings=None) []
,可以看作parser对象自己封装参数的格式。接着获取了一个字符串列表,即在settings.py文件中的 INSTALLED_APPS
配置(admin模块、用户认证auth模块、sessions模块等)。
- 当解析的的命令是 runserver 时,两个选择,第一个是自动重载的路线,通过 autoreload.check_errors(django.setup)() 。 check_errors是一个装饰器函数,该方法接受一个参数,然后返回一个函数wrapper,autoreload.check_errors(django.setup)()实际上最后执行了装饰器函数中的fn(*args, **kwargs),也就是传递的参数django.setup。
def check_errors(fn):
@functools.wraps(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:
filename = traceback.extract_tb(tb)[-1][0]
else:
filename = ev.filename
if filename not in _error_files:
_error_files.append(filename)
raise
return wrapper
- 另一个路线是参数中有 --noreload 时,就用 django.setup() 来启动服务。
- 接着执行
self.autocomplete()
,该函数最后一句显然是退出sys模块,所以这个函数封装的关于sys模块的功能,不是研究的重点,略过?
def autocomplete(self):
...
sys.exit(0)
动态加载模块
- 如果不是 runserver 而是其他命令,那么会对命令参数 self.argv[1] 进行判断,包括错误处理,是否是 help ,是否是 version ,根据不同的情况展示不同的信息。
- 最重要的是最后一句,即前面的情况都不是,就进入
self.fetch_command(subcommand).run_from_argv(self.argv)
,分两步,一步是获取执行命令所需要的类,其次是将命令参数作为参数传递给执行函数执行:
def fetch_command(self, subcommand):
commands = get_commands()
try:
app_name = commands[subcommand]
except KeyError:
if os.environ.get('DJANGO_SETTINGS_MODULE'):
settings.INSTALLED_APPS
else:
sys.stderr.write("No Django settings specified.\n")
possible_matches = get_close_matches(subcommand, commands)
sys.stderr.write('Unknown command: %r' % subcommand)
if possible_matches:
sys.stderr.write('. Did you mean %s?' % possible_matches[0])
sys.stderr.write("\nType '%s help' for usage.\n" % self.prog_name)
sys.exit(1)
if isinstance(app_name, BaseCommand):
klass = app_name
else:
klass = load_command_class(app_name, subcommand)
return klass
- get_commands() 是返回是一个命令与模块映射作用的字典:
{ 'check': 'django.core',
'compilemessages': 'django.core',
'createcachetable': 'django.core',
'dbshell': 'django.core',
...
'runserver': 'django.contrib.staticfiles',
'sendtestemail': 'django.core',
...}
- 模块是通过 load_command_class 来动态加载的:
def load_command_class(app_name, name):
"""
Given a command name and an application name, return the Command
class instance. Allow all errors raised by the import process
(ImportError, AttributeError) to propagate.
"""
module = import_module('%s.management.commands.%s' % (app_name, name))
return module.Command()
- runserver 命令的模块实际上是在执行
django.contrib.staticfiles.management.commands.runserver
,然后返回该模块中定义的 Command 类的实例。获得实例后调用了 run_from_argv(self.argv)(直接定义在
基类BaseCommand中) ,函数最终执行的是execute函数。而在execute中最终执行却又是self.handle(*args, **options)
,从注释可以知道,这个方法需要由子类实现的,即执行的 runserver
中 handle
函数。
def run_from_argv(self, argv):
"""
Set up any environment changes requested (e.g., Python path
and Django settings), then run this command. If the
command raises a ``CommandError``, intercept it and print it sensibly
to stderr. If the ``--traceback`` option is present or the raised
``Exception`` is not ``CommandError``, raise it.
"""
self._called_from_command_line = True
parser = self.create_parser(argv[0], argv[1])
options = parser.parse_args(argv[2:])
cmd_options = vars(options)
args = cmd_options.pop('args', ())
handle_default_options(options)
try:
self.execute(*args, **cmd_options)
except Exception as e:
...
def execute(self, *args, **options):
"""
Try to execute this command, performing system checks if needed (as
controlled by the ``requires_system_checks`` attribute, except if
force-skipped).
"""
...
if self.requires_system_checks and not options.get('skip_checks'):
self.check()
if self.requires_migrations_checks:
self.check_migrations()
output = self.handle(*args, **options)
...
def handle(self, *args, **options):
"""
The actual logic of the command. Subclasses must implement
this method.
"""
raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')