开始
最近一直在使用python开发django程序,当写到一个插件的时候有些步骤为了简化流程决定使用命令行模式。之前用python的自有库和第三方库写过一些命令行模式的代码,但感觉django实现的这个非常独特,有很大的魅力,因此决定研究一下源码。
此处使用的IDE为pycharm编辑器,因此提到的操作均为这里面的快捷操作,需要读者注意。
ok,let’s begin~
打开工程,找到manage.py文件,可以看到其代码如下:
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
可以看到这段代码非常的简洁,第一行os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
这句话绑定了DJANGO_SETTINGS_MODULE
的键值,然后将其加载。第二行导入了一个django模块中的一个叫execute_from_command_line
的函数方法,这个方法就是整个逻辑执行的关键。
按住‘Ctrl’点击execute_from_command_line
这个函数,代码会跳转到这个函数的实现部分。代码文件位于django/core/management/__init__.py
文件中。
def execute_from_command_line(argv=None):
"""
A simple method that runs a ManagementUtility.
"""
utility = ManagementUtility(argv)
utility.execute()
这个代码的中,声明了一个ManagementUtility的实例,这个实例调用了类中的execute方法。那么同样看这个类到底干了什么。进入ManagementUtility类。
step1
先初步看一下这个类的入口处注释和初始化部分。
class ManagementUtility(object):
"""
Encapsulates the logic of the django-admin and manage.py utilities.
A ManagementUtility has a number of commands, which can be manipulated
by editing the self.commands dictionary.
"""
def __init__(self, argv=None):
self.argv = argv or sys.argv[:]
self.prog_name = os.path.basename(self.argv[0])
self.settings_exception = None
通过官方给出的注释可以看出,说白了这就是一个管理命令的类,它封装了django-admin和manage两大功能文件的逻辑。里面的命令通过编辑self.command
的字典来进行维护。
类实例化的时候先初始化了几个参数:
- self.argv : 顾名思义,就是命令参数;
- self.prog_name: 调用这个类的组件名,区分是django-admin还是manage.py;
- self.settings_exception: 记录异常。
step2
接着看execute
方法,这部分是整个命令行执行的实际入口。
def execute(self):
"""
Given the command-line arguments, this figures out which subcommand is
being run, creates a parser appropriate to that command, and runs it.
"""
try:
# eg:python manage.py runserver 127.0.0.1:8000
# argv = ['your_project_path/your_project_name/manage.py', 'runserver', '8000']
subcommand = self.argv[1]
except IndexError:
subcommand = 'help' # Display help if no arguments were given.
# Preprocess options to extract --settings and --pythonpath.
# These options could affect the commands that are available, so they
# must be processed early.
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.
no_settings_commands = [
'help', 'version', '--help', '--version', '-h',
'compilemessages', 'makemessages',
'startapp', 'startproject',
]
try:
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
# A handful of built-in management commands work without settings.
# Load the default settings -- where INSTALLED_APPS is empty.
if subcommand in no_settings_commands:
settings.configure()
if settings.configured:
# Start the auto-reloading dev server even if the code is broken.
# The hardcoded condition is a code smell but we can't rely on a
# flag on the command class because we haven't located it yet.
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
autoreload.check_errors(django.setup)()
except Exception:
# The exception will be raised later in the child process
# started by the autoreloader. Pretend it didn't happen by
# loading an empty list of applications.
apps.all_models = defaultdict(OrderedDict)
apps.app_configs = OrderedDict()
apps.apps_ready = apps.models_ready = apps.ready = True
# In all other cases, django.setup() is required to succeed.
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])
# Special-cases: We want 'django-admin --version' and
# 'django-admin --help' to work, for backwards compatibility.
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)
这部分注释也写的非常明确,这个函数主要处理子命令的执行,这里的子命令是相对于django-admin.py
和manage.py
的,举个例子:python manage.py runserver
这里的runserver
就是子命令。
前面几行不难看懂是进行参数获取的,后面到了解析这块又用到了CommandParser
这个类,同理,通过进入该类查看实现过程,该类的路径为django/core/management/base.py
中,代码如下:
class CommandParser(ArgumentParser):
"""
Customized ArgumentParser class to improve some error messages and prevent
SystemExit in several occasions, as SystemExit is unacceptable when a
command is called programmatically.
"""
def __init__(self, cmd, **kwargs):
self.cmd = cmd
super(CommandParser, self).__init__(**kwargs)
def parse_args(self, args=None, namespace=None):
# Catch missing argument for a better error message
if (hasattr(self.cmd, 'missing_args_message') and
not (args or any(not arg.startswith('-') for arg in args))):
self.error(self.cmd.missing_args_message)
return super(CommandParser, self).parse_args(args, namespace)
def error(self, message):
if self.cmd._called_from_command_line:
super(CommandParser, self).error(message)
else:
raise CommandError("Error: %s" % message)
可以看出这个类是对ArgumentParser
的继承封装,而ArgumentParser
是python基础类包中的一个类,这个函数的目的是将ArgumentParser
封装成符合django内部调用的接口形式。不懂的自行参看其手册。ArgumentParser官方手册。
回过头来继续看execute
函数。接下来的一段代码围绕着CommandParser
进行。完成命令行控制的常规设置。运行到options, args = parser.parse_known_args(self.argv[2:])
才开始进入解析的关键之处。
下面给出了python的官方文档中该函数的说明:
Sometimes a script may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the parse_known_args()
method can be useful. It works much likeparse_args()
except that it does not produce an error when extra arguments are present. Instead, it returns a two item tuple containing the populated namespace and the list of remaining argument strings.
也就是说,这个函数将当前脚本需要命令参数和其他脚本所需的命令行进行分离,它的返回结果是一个tuple,包含一个填充好的命名空间和剩余的参数字符串列表。此处的options
和args
的结果(为了显示名明显,不是runserver指令的结果)形如:
{'files': [], 'settings': None, 'pythonpath': None, 'verbosity': u'1', 'traceback': None, 'extensions': ['py'], 'template': None}
和['testproject']
。
代码接着运行,将前面函数填充好的命名空间(此处为options参数)传入handle_default_options
这个方法去执行。handle_default_options
同样在django/core/management/base.py
文件中,代码如下:
def handle_default_options(options):
"""
Include any default options that all commands should accept here
so that ManagementUtility can handle them before searching for
user commands.
"""
# 上述例子运行解析后,options的传递过来的值:Namespace(args=['8000'], pythonpath=None, settings=None)
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
if options.pythonpath:
sys.path.insert(0, options.pythonpath)
它只是完成了两个目标,(1)options中包含setting
则配置环境变量;(2)options中包含pythonpath
则设置python模块的搜索路径。
ok,back to execute
。
接下来给出了一个no_settings_commands
。顾名思义,传入的命令行参数如果是这个列表中的,那么它不需要以来settings。这部分一般是一些内置的管理命令。
这里有个判定分支,只有settings.INSTALLED_APPS
装载成功或者子命令行是no settings commands才会去执行,这部分是一个django的启动分支,不是我们关注的重点,有兴趣的可以深入看。
这些准备工作完成后,这里调用了类中自有的一个方法autocomlete
,它的代码如下:
def autocomplete(self):
"""
Output completion suggestions for BASH.
The output of this function is passed to BASH's `COMREPLY` variable and
treated as completion suggestions. `COMREPLY` expects a space
separated string as the result.
The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used
to get information about the cli input. Please refer to the BASH
man-page for more information about this variables.
Subcommand options are saved as pairs. A pair consists of
the long option string (e.g. '--exclude') and a boolean
value indicating if the option requires arguments. When printing to
stdout, an equal sign is appended to options which require arguments.
Note: If debugging this function, it is recommended to write the debug
output in a separate file. Otherwise the debug output will be treated
and formatted as potential completion suggestions.
"""
# Don't complete if user hasn't sourced bash_completion file.
# os.environ是一个_Environ类型的类,是一个继承UserDict.IterableUserDict的字典类,说白了就是个特殊字典
if 'DJANGO_AUTO_COMPLETE' not in os.environ:
return
cwords = os.environ['COMP_WORDS'].split()[1:]
cword = int(os.environ['COMP_CWORD'])
try:
curr = cwords[cword - 1]
except IndexError:
curr = ''
subcommands = list(get_commands()) + ['help']
options = [('--help', False)]
# subcommand
if cword == 1:
print(' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands))))
# subcommand options
# special case: the 'help' subcommand has no options
elif cwords[0] in subcommands and cwords[0] != 'help':
# 这里也有一个self.fetch_command() ,因为执行runserver指令并未运行到这,而调用这个函数的外
# 层函数中也包含这个函数,因此,在后面解释这个作用。
subcommand_cls = self.fetch_command(cwords[0])
# special case: 'runfcgi' stores additional options as
# 'key=value' pairs
# 这部分是对一些指令的额外动作的一些准备工作
if cwords[0] == 'runfcgi':
from django.core.servers.fastcgi import FASTCGI_OPTIONS
options.extend((k, 1) for k in FASTCGI_OPTIONS)
# special case: add the names of installed apps to options
elif cwords[0] in ('dumpdata', 'sql', 'sqlall', 'sqlclear',
'sqlcustom', 'sqlindexes', 'sqlmigrate', 'sqlsequencereset', 'test'):
try:
app_configs = apps.get_app_configs()
# Get the last part of the dotted path as the app name.
options.extend((app_config.label, 0) for app_config in app_configs)
except ImportError:
# Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
# user will find out once they execute the command.
pass
# 这块是上面返回那个类的调用,调用类中的create_parser方法去执行指令
parser = subcommand_cls.create_parser('', cwords[0])
# 判断这个命令的是否存在options
if subcommand_cls.use_argparse:
options.extend((sorted(s_opt.option_strings)[0], s_opt.nargs != 0) for s_opt in
parser._actions if s_opt.option_strings)
else:
options.extend((s_opt.get_opt_string(), s_opt.nargs != 0) for s_opt in
parser.option_list)
# filter out previously specified options from available options
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
options = [opt for opt in options if opt[0] not in prev_opts]
# filter options by current input
options = sorted((k, v) for k, v in options if k.startswith(curr))
for option in options:
opt_label = option[0]
# append '=' to options which require args
if option[1]:
opt_label += '='
print(opt_label)
sys.exit(1)
这个函数主要的功能是通过BASH去输出执行建议。
继续运行,这里的一个if else判断分支,前面几个就是常规的帮助、版本的执行分支,最后一个else分支才是我们关注的重点。
ok,get是重点~
这里self.fetch_command(subcommand).run_from_argv(self.argv)
调用了该类中的fetch_command
方法,而它的返回对象又执行了run_from_argv
方法。一步一步来看。
step3
看看fetch_command
到底返回了个什么?进入代码:
def fetch_command(self, subcommand):
"""
Tries to fetch the given subcommand, printing a message with the
appropriate command called from the command line (usually
"django-admin" or "manage.py") if it can't be found.
"""
# Get commands outside of try block to prevent swallowing exceptions
# 这里执行完get_commands之后,返回的commands为:
# {'clearsessions': 'django.contrib.sessions', 'compilemessages': u'django.core', 'dumpdata': u'django.core', 'startproject': u'django.core', 'findstatic': 'django.contrib.staticfiles', 'sqldropindexes': u'django.core', 'sqlcustom': u'django.core', 'createcachetable': u'django.core', 'flush': u'django.core', 'squashmigrations': u'django.core', 'check': u'django.core', 'sqlmigrate': u'django.core', 'debugsqlshell': u'debug_toolbar', 'makemigrations': u'django.core', 'runserver': 'django.contrib.staticfiles', 'migrate': u'django.core', 'showmigrations': u'django.core', 'dbshell': u'django.core', 'runfcgi': u'django.core', 'test': u'django.core', 'sqlclear': u'django.core', 'changepassword': 'django.contrib.auth', 'shell': u'django.core', 'sqlsequencereset': u'django.core', 'testserver': u'django.core', 'makemessages': u'django.core', 'sql': u'django.core', 'validate': u'django.core', 'sqlall': u'django.core', 'collectstatic': 'django.contrib.staticfiles', 'diffsettings': u'django.core', 'syncdb': u'django.core', 'inspectdb': u'django.core', 'startapp': u'django.core', 'createsuperuser': 'django.contrib.auth', 'sqlindexes': u'django.core', 'loaddata': u'django.core', 'sqlflush': u'django.core'}
# 这里返回的是一个字典,字典的key是命令名称,value是这个命令实现所在的文件路径。
commands = get_commands()
try:
# 这里runserver指令对应的返回的app_name应该为'django.contrib.staticfiles'
app_name = commands[subcommand]
except KeyError:
# This might trigger ImproperlyConfigured (masked in get_commands)
settings.INSTALLED_APPS
sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" %
(subcommand, self.prog_name))
sys.exit(1)
if isinstance(app_name, BaseCommand):
# If the command is already loaded, use it directly.
klass = app_name
else:
# 没有装载的命令通过下面这个函数进行封装后返回
klass = load_command_class(app_name, subcommand)
return klass
这个函数完成的功能是,获取子命令,然后将该命令对应的模块进行装载,如果该命令已经被装载,则返回装载目录,如果没有装载,则调用load_command_class
装载。如果这个命令不是有效的,则输出提示信息。
load_command_class
的目录在django/core/management/__init__.py
中,代码如下:
def load_command_class(app_name, name):
"""
Given a command name and an application name, returns the Command
class instance. All errors raised by the import process
(ImportError, AttributeError) are allowed to propagate.
"""
# <module 'django.contrib.staticfiles.management.commands.runserver' from '/usr/local/lib/python2.7/dist-packages/django/contrib/staticfiles/management/commands/runserver.pyc'>
module = import_module('%s.management.commands.%s' % (app_name, name))
return module.Command()
这个方法调用python中importlib库中的import_module方法将模块动态载入,然后返回载入模块的Command()。参看management/commands
下的每个文件,发现都拥有一个Command类对应相应的命令。
综上所诉,之前这个fetch_command
返回了一个命令对象。
接着研究run_from_argv
函数,这个函数同样位于django/core/management/base.py
中,它是之前返回的BaseCommand
对象中的一个方法,代码如下:
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
# create_parser接受两个参数,第一个是prog_name,即谁执行这个指令,此处是'manage.py'
# 第二个是subcommand,即运行的是什么指令,此处是'runserver'
parser = self.create_parser(argv[0], argv[1])
if self.use_argparse:
# 这里执行完options的值为:
# Namespace(addrport='8000', insecure_serving=False, no_color=False, pythonpath=None, settings=None, traceback=False, use_ipv6=False, use_reloader=True, use_static_handler=True, use_threading=True, verbosity=1)
options = parser.parse_args(argv[2:])
# 这里执行完cmd_options的值为:
# {'use_static_handler': True, 'settings': None, 'pythonpath': None, 'verbosity': 1, 'traceback': False, 'addrport': '8000', 'no_color': False, 'use_ipv6': False, 'use_threading': True, 'use_reloader': True, 'insecure_serving': False}
cmd_options = vars(options)
# Move positional args out of options to mimic legacy optparse
args = cmd_options.pop('args', ())
else:
options, args = parser.parse_args(argv[2:])
cmd_options = vars(options)
handle_default_options(options)
try:
# 开始执行指令
self.execute(*args, **cmd_options)
except Exception as e:
if options.traceback or not isinstance(e, CommandError):
raise
# SystemCheckError takes care of its own formatting.
if isinstance(e, SystemCheckError):
self.stderr.write(str(e), lambda x: x)
else:
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
sys.exit(1)
finally:
connections.close_all()
这个函数的作用就是设置好环境变量,然后取运行指令。这个文件的结构有点类似于前面的execute
,在它里面同样封装了一个execute
函数取执行命令。
这部分的代码如下:
# 这里传递进来的options的值为:
# Namespace(addrport='8000', insecure_serving=False, no_color=False, pythonpath=None, settings=None, traceback=False, use_ipv6=False, use_reloader=True, use_static_handler=True, use_threading=True, verbosity=1)
def execute(self, *args, **options):
"""
Try to execute this command, performing system checks if needed (as
controlled by attributes ``self.requires_system_checks`` and
``self.requires_model_validation``, except if force-skipped).
"""
if options.get('no_color'):
self.style = no_style()
self.stderr.style_func = None
if options.get('stdout'):
self.stdout = OutputWrapper(options['stdout'])
if options.get('stderr'):
self.stderr = OutputWrapper(options.get('stderr'), self.stderr.style_func)
saved_locale = None
if not self.leave_locale_alone:
# Only mess with locales if we can assume we have a working
# settings file, because django.utils.translation requires settings
# (The final saying about whether the i18n machinery is active will be
# found in the value of the USE_I18N setting)
if not self.can_import_settings:
raise CommandError("Incompatible values of 'leave_locale_alone' "
"(%s) and 'can_import_settings' (%s) command "
"options." % (self.leave_locale_alone,
self.can_import_settings))
# Deactivate translations, because django-admin creates database
# content like permissions, and those shouldn't contain any
# translations.
from django.utils import translation
saved_locale = translation.get_language()
translation.deactivate_all()
try:
if (self.requires_system_checks and
not options.get('skip_validation') and # Remove at the end of deprecation for `skip_validation`.
not options.get('skip_checks')):
self.check()
output = self.handle(*args, **options)
if output:
if self.output_transaction:
# This needs to be imported here, because it relies on
# settings.
from django.db import connections, DEFAULT_DB_ALIAS
connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
if connection.ops.start_transaction_sql():
self.stdout.write(self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()))
self.stdout.write(output)
if self.output_transaction:
self.stdout.write('\n' + self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()))
finally:
if saved_locale is not None:
translation.activate(saved_locale)
这一大段代码之前就是一些设定以及一些国际化的东西,关键的核心在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')
也就说这里只是一个声明,每个实际的类都需要实现这个方法。这里找一个runserver命令行的实现(文件位置django/core/management/commands/runserver.py
)为例子,代码在下面给出:
def handle(self, *args, **options):
from django.conf import settings
if not settings.DEBUG and not settings.ALLOWED_HOSTS:
raise CommandError('You must set settings.ALLOWED_HOSTS if DEBUG is False.')
self.use_ipv6 = options.get('use_ipv6')
if self.use_ipv6 and not socket.has_ipv6:
raise CommandError('Your Python does not support IPv6.')
self._raw_ipv6 = False
if not options.get('addrport'):
self.addr = ''
self.port = DEFAULT_PORT
else:
m = re.match(naiveip_re, options['addrport'])
if m is None:
raise CommandError('"%s" is not a valid port number '
'or address:port pair.' % options['addrport'])
self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
if not self.port.isdigit():
raise CommandError("%r is not a valid port number." % self.port)
if self.addr:
if _ipv6:
self.addr = self.addr[1:-1]
self.use_ipv6 = True
self._raw_ipv6 = True
elif self.use_ipv6 and not _fqdn:
raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
if not self.addr:
self.addr = '::1' if self.use_ipv6 else '127.0.0.1'
self._raw_ipv6 = bool(self.use_ipv6)
self.run(**options)
前面的一大段就是运行runserver时候执行的一些参数准备,关键部分是最后一行的self.run(**options)
,继续深入,进入该函数,这里发现其又依赖一个inner_run
的函数,于是一块将代码贴出来了:
def run(self, **options):
"""
Runs the server, using the autoreloader if needed
"""
use_reloader = options.get('use_reloader')
if use_reloader:
autoreload.main(self.inner_run, None, options)
else:
self.inner_run(None, **options)
def inner_run(self, *args, **options):
from django.conf import settings
from django.utils import translation
# If an exception was silenced in ManagementUtility.execute in order
# to be raised in the child process, raise it now.
autoreload.raise_last_exception()
threading = options.get('use_threading')
shutdown_message = options.get('shutdown_message', '')
quit_command = 'CTRL-BREAK' if sys.platform == 'win32' else 'CONTROL-C'
self.stdout.write("Performing system checks...\n\n")
self.validate(display_num_errors=True)
try:
self.check_migrations()
except ImproperlyConfigured:
pass
now = datetime.now().strftime('%B %d, %Y - %X')
if six.PY2:
now = now.decode(get_system_encoding())
self.stdout.write((
"%(started_at)s\n"
"Django version %(version)s, using settings %(settings)r\n"
"Starting development server at http://%(addr)s:%(port)s/\n"
"Quit the server with %(quit_command)s.\n"
) % {
"started_at": now,
"version": self.get_version(),
"settings": settings.SETTINGS_MODULE,
"addr": '[%s]' % self.addr if self._raw_ipv6 else self.addr,
"port": self.port,
"quit_command": quit_command,
})
# django.core.management.base forces the locale to en-us. We should
# set it up correctly for the first request (particularly important
# in the "--noreload" case).
translation.activate(settings.LANGUAGE_CODE)
try:
handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading)
except socket.error as e:
# Use helpful error messages instead of ugly tracebacks.
ERRORS = {
errno.EACCES: "You don't have permission to access that port.",
errno.EADDRINUSE: "That port is already in use.",
errno.EADDRNOTAVAIL: "That IP address can't be assigned-to.",
}
try:
error_text = ERRORS[e.errno]
except KeyError:
error_text = force_text(e)
self.stderr.write("Error: %s" % error_text)
# Need to use an OS exit because sys.exit doesn't work in a thread
os._exit(1)
except KeyboardInterrupt:
if shutdown_message:
self.stdout.write(shutdown_message)
sys.exit(0)
这段代码的核心又在
try:
handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading)
except socket.error as e:
这个部分,这里使用了该类中的另一个方法self.get_handler
,进入查看代码:
def get_handler(self, *args, **options):
"""
Returns the default WSGI handler for the runner.
"""
return get_internal_wsgi_application()
因为runserver的任务就是启动一个django服务器,因此这里直接返回一个WSGI的处理服务,好吧,接着进入:
def get_internal_wsgi_application():
"""
Loads and returns the WSGI application as configured by the user in
``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
this will be the ``application`` object in ``projectname/wsgi.py``.
This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
for Django's internal servers (runserver, runfcgi); external WSGI servers
should just be configured to point to the correct application object
directly.
If settings.WSGI_APPLICATION is not set (is ``None``), we just return
whatever ``django.core.wsgi.get_wsgi_application`` returns.
"""
from django.conf import settings
app_path = getattr(settings, 'WSGI_APPLICATION')
if app_path is None:
return get_wsgi_application()
try:
return import_string(app_path)
except ImportError as e:
msg = (
"WSGI application '%(app_path)s' could not be loaded; "
"Error importing module: '%(exception)s'" % ({
'app_path': app_path,
'exception': e,
})
)
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
sys.exc_info()[2])
并没由什么太多的复杂东西,这里又返回了一个get_wsgi_application
方法的结果,继续深入:
def get_wsgi_application():
"""
The public interface to Django's WSGI support. Should return a WSGI
callable.
Allows us to avoid making django.core.handlers.WSGIHandler public API, in
case the internal WSGI implementation changes or moves in the future.
"""
django.setup()
return WSGIHandler()
这里就是调用了django的启动函数,然后返回一个WSGIHandler()
,OK,到这里,基本就进入到WSGI服务部分的实现了。
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest
def __call__(self, environ, start_response):
# Set up middleware if needed. We couldn't do this earlier, because
# settings weren't available.
if self._request_middleware is None:
with self.initLock:
try:
# Check that middleware is still uninitialized.
if self._request_middleware is None:
self.load_middleware()
except:
# Unload whatever middleware we got
self._request_middleware = None
raise
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
try:
request = self.request_class(environ)
except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError)',
exc_info=sys.exc_info(),
extra={
'status_code': 400,
}
)
response = http.HttpResponseBadRequest()
else:
response = self.get_response(request)
response._handler_class = self.__class__
status = '%s %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
start_response(force_str(status), response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response
再往后深入的就不研究了,因为此处原本研究的是django manage.py的调度过程的实现,主要是看其是如何实现这个命令行模式的实现,到这里基本也就看清了,如果是想研究如何runserver的实现过程,可以继续深入研究。
看了这么多的代码,层层深入,发现其实整个manage.py文件到了output = self.handle(*args, **options)
这块就基本实现它该实现的功能了,而handle
中的实现就是每个具体命令行的实现过程了。
总结
总的来说,django中manage.py中的实现还是不算太过于繁杂的(相比较它的工程量),但是各种封装的实现想要很快理清还是比较耗时间的,需要下一定的功夫。也许看一次不足以接受,那就两次三次吧。
最后想说的一点是,第一次尝试去这么分析源码,分析过程中分析不下去了也在谷歌上搜索了以下前辈们的输出,在这列出:
从零开始分析Django源码1
Django源码解析(二)manage.py
django源码阅读
感谢以上作者的输出,也许他们的文章更浅显易懂,但作为我的一篇自己的笔记,我还是写的比较详细,等有时间了,回过头来,争取在代码的源码中插入注释,方便查看。
有什么不足还望前辈们指出。