Django1.8.11版本manage.py源码解析

开始


最近一直在使用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.pymanage.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,包含一个填充好的命名空间和剩余的参数字符串列表。此处的optionsargs的结果(为了显示名明显,不是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源码阅读
感谢以上作者的输出,也许他们的文章更浅显易懂,但作为我的一篇自己的笔记,我还是写的比较详细,等有时间了,回过头来,争取在代码的源码中插入注释,方便查看。
有什么不足还望前辈们指出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容