Django源碼分析(六):命令解析

起步

正如咱們所知,django 根據 manage.py 這個文件來提供不少命令來執行。html

例如:runserverstartappmakemigrationsmigrate ...python

那麼這些命令是如何對應執行的(這裏咱們不具體敘述具體實現的功能項)。數據庫

開始

咱們先看下 manage.py 這個文件(本質上執行這些命令就是執行的是 manage.py 這個文件)django

# manage.py

import os
import sys

if __name__ == "__main__":
    # 設置環境變量(主要是配置文件的)
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "proj.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError:
        # 這裏主要就是導入不成功,拋出 django 未安裝的錯誤啥的
        ...

    # 執行這個函數,命令行參數做爲參數傳入
    execute_from_command_line(sys.argv)

# django/core/management

def execute_from_command_line(argv=None):
    """執行命令"""

    # 實例化
    utility = ManagementUtility(argv)
    # 執行命令
    utility.execute()

# django/core/management

class ManagementUtility:
    """封裝了 django-admin 和 manage.py 的邏輯 """
    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

    def execute(self):
        """給定命令行參數,找出命令,給這個命令建立一個解析器並運行它. """

        # 若是沒有命令參數,則默認展現幫助
        try:
            subcommand = self.argv[1]
        except IndexError:
            subcommand = 'help'  

        # 預處理提取 --settings 和 --pythonpath 這倆項配置.
        # 這些參數會影響命令的可用性,所以它們必需要提早處理,固然了你不傳也沒任何影響.
        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='*')  # catch-all
        try:
            # 處理已知的參數
            options, args = parser.parse_known_args(self.argv[2:])
            # 處理默認參數
            handle_default_options(options)
        except CommandError:
            pass  # 此時忽略任何選項錯誤.

        try:
            # 檢測 INSTALLED_APPS 是否訪問正常
            settings.INSTALLED_APPS
        except ImproperlyConfigured as exc:
            self.settings_exception = exc
        except ImportError as exc:
            self.settings_exception = exc

        # 這一項配置什麼時候爲 true 的彷佛不得而知, 忽略如下代碼
        if settings.configured:
            ...


        self.autocomplete()

        # 若是子命令爲 help
        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])
        # 特殊狀況,指望 django-admin 相關命令向後兼容
        elif subcommand == 'version' or self.argv[1:] == ['--version']:
            sys.stdout.write(django.get_version() + '\n')
        # 若是涵蓋有 --help -h 等相關查看幫助信息的
        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)

複製代碼

以上主要經過執行 manage.py 文件並執行 execute_from_command_line 函數,把 sys.argv 上得到的參數做爲參數傳入該函數,execute_from_command_line 下,ManagementUtility 實例化,並調用 execute 函數,該功能內部針對一些特殊的狀況進行了預處理,最後解析命令並執行。app

相關邏輯繼續向下看。函數

深究

上面除了針對特殊狀況的處理以外,核心主要在 self.fetch_command(subcommand).run_from_argv(self.argv) 這裏。fetch

這是一個鏈式操做,接連調用了倆個方法。ui

咱們先從 fetch_command 開始。spa

def fetch_command(self, subcommand):
    # 獲取全部相關的命令
    # 主要是經過文件查找獲取每一個 app 下的 management 文件夾下全部的文件
    # 是一個字典集合,key 爲子命令名稱,value 爲 app 的名稱
    commands = get_commands()
    try:
        # 經過字典映射到值
        app_name = commands[subcommand]
    except KeyError:
        # 若是發生異常,退出並進行提示 (並根據當前錯誤命令,提示與其相關的命令)
        # 這裏用到名爲 difflib 模塊(標準庫下的)
        ...

    # 若是命令已經加載,則直接使用它
    if isinstance(app_name, BaseCommand):
        klass = app_name
    else:
        # 通常會走這裏,加載命令類
        # 傳入倆個參數,app 名稱 和 命令名稱
        klass = load_command_class(app_name, subcommand)
    # 這裏響應獲取到一個 Command 實例,實則繼承於 BaseCommand
    return klass

# 加載命令類
def load_command_class(app_name, name):
    # 從這裏咱們得知,若是咱們須要自定義一些命令,須要在 app 下建立 management/commands 文件夾,命令名稱爲文件名
    module = import_module('%s.management.commands.%s' % (app_name, name))
    # 動態導入該模塊後,調用模塊下的 Command 類並實例化(這裏有也說明這個類的名稱只能叫做 Command)
    return module.Command()

複製代碼

django/core/management/base 下的 BaseCommand命令行

接下來,鏈式調用到了 run_from_argv,調用者的基類爲 BaseCommand

咱們找到 BaseCommand 下的 run_from_argv 函數,並把命令行參數傳遞給該函數。

def run_from_argv(self, argv):
    # 如下爲參數解析
    self._called_from_command_line = True
    parser = self.create_parser(argv[0], argv[1])

    options = parser.parse_args(argv[2:])
    cmd_options = vars(options)
    # Move positional args out of options to mimic legacy optparse
    args = cmd_options.pop('args', ())
    # 處理默認參數
    handle_default_options(options)
    try:
        # 執行
        self.execute(*args, **cmd_options)
    except Exception as e:
        # 執行出現異常打印相關信息
        ...
    finally:
        try:
            # 關閉數據庫鏈接
            connections.close_all()
        except ImproperlyConfigured:
            ...

def execute(self, *args, **options):
    # 設置顏色等等相關
    if options['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['stderr'], self.stderr.style_func)

    # 檢查一些相關選項
    if self.requires_system_checks and not options.get('skip_checks'):
        self.check()
    if self.requires_migrations_checks:
        self.check_migrations()

    # 這裏調用了 handle 方法
    # 也就是說本身定義的命令,須要實現 handle 方法
    # 這個方法內實現你本身的邏輯
    output = self.handle(*args, **options)

    if output:
        ...

    return output
複製代碼

總結

經過上述表述,咱們知曉了 runserver, startapp 等命令是如何進行執行的。

首先從 manage.py 這個文件開始,執行 execute_from_command_line 函數(sys.argv 做爲參數),而後調用 ManagementUtility 類進行實例化,該實例調用 execute 方法。

這裏主要進行了一些參數的預處理,而後調用 fetch_command(subcommand) 方法,這個先找到全部可用的命令,根據 subcommand 參數獲取到一個基類爲 BaseCommand 的一個實例,而後這個實例調用 run_from_argv 方法(該方法位於 BaseCommand 中),而後調用 execute 方法,該方法下又調用 handle 方法來實現本身的邏輯。

以上就是 django 所實現的命令調用。

擴展

  • django 自定義命令實現

經過上述源碼解析,大概咱們瞭解到如何實現本身定義的命令。

app 下建立 management/commands 文件夾(這個是必須的),文件名稱你能夠隨便定義,例如 custom_command.py,那麼調用的時候就是 python manage.py custom_command

而後在 custom_command.py 文件下定義一個 Command 類,並繼承 BaseCommand(處於 django/core/management/base 下),而後類下實現一個 handle 方法(必須的)(實現你本身的邏輯)

  • difflib 模塊

在咱們輸入錯誤的命令的時候,好比執行 python manage.py runserver ,假設 runserver 打成了 runservev,這裏會提示你是不是打出 runserver 這個單詞。

這裏 django 使用的是 python 標準庫下的 difflib 模塊來達到這個目的。

該模塊主要用於文本之間的對比等操做。例如咱們舉個例子。

>>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"])
 ['apple', 'ape']
 >>> import keyword as _keyword
 >>> get_close_matches("wheel", _keyword.kwlist)
 ['while']
 >>> get_close_matches("Apple", _keyword.kwlist)
 []
 >>> get_close_matches("accept", _keyword.kwlist)
 ['except']
複製代碼

這個只是這個模塊下的其中的一個方法(django 也是使用的它)。

更多咱們能夠了解官方文檔,詳情 -> 請戳這裏

相關文章
相關標籤/搜索