正如咱們所知,django
根據 manage.py
這個文件來提供不少命令來執行。html
例如:runserver
,startapp
,makemigrations
,migrate
...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
也是使用的它)。
更多咱們能夠了解官方文檔,詳情 -> 請戳這裏。