2、命令行源码追踪-1-公共命令参数入口

1、以 django-admin startproject xxx 为例

1、django-admin实际上在django下载的时候,是作为一个.exe的可执行文件,并被配置进了环境变量,因此可以直接使用django-admin执行命令,创建项目

 2、django版本在2.2的时候,在django的bin目录下存在一个django-admin.py的文件,内部代码如下

3、当前django4.0已经取消了bin目录,但是实际执行的代码逻辑是一样的,均是执行了 execute_from_command_line()  这个方法 ,此处新建一个tmp文件夹并 cd 切换进目录

 2、python manage.py xxx

 1、manage.py 为django项目创建成功后自动生成,其中代码如下,也是执行了execute_from_command_line() 这个方法

 3、小结

综上,不管是django-admin还是python manage.py ,最终都会execute_from_command_line() 这个方法

4、execute_from_command_line()

1、PyCharm点击该函数实际显示如下,该函数做了两件事

        1、实例化

        2、执行

def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv)  # 1. 实例化
    utility.execute()  # 2. 执行

4.1、实例化 

1、实例化比较简单,主要体现在绑定 self 的命令行参数

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

4.2、execute() 函数

1、主要关注 execute 函数

2、只要关注中文注释部分即可

3、原先的英文注释已被删除

4、当前流程走完之后最后会走fetch_command()这个函数

def execute(self):
    try:
        # 1、
        # 如果当前的命令为 python manage.py startapp app_xx
        # 那么此处的 subcommand 就是 startapp
        subcommand = self.argv[1]
    except IndexError:
        subcommand = 'help'  # Display help if no arguments were given.

    parser = CommandParser(
        prog=self.prog_name,
        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:
        # 2、拿到其他的参数 比如
        # 当命令为 python manage.py startapp app_xx
        # options: Namespace(settings=None, pythonpath=None, args=['app_xx'])
        # args: []
        # 当命令为 python manage.py startapp --app_xx
        # options: Namespace(settings=None, pythonpath=None, args=[])
        # args: ['--app_xx']
        options, args = parser.parse_known_args(self.argv[2:])
        handle_default_options(options)
    except CommandError:
        pass  # Ignore any option errors at this point.

    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(dict)
                apps.app_configs = {}
                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':
        # 3、python manage.py help --command
        if '--commands' in args:
            sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
        # 4、python manage.py help
        elif not options.args:
            sys.stdout.write(self.main_help_text() + '\n')
        # 5、python manage.py help makemigrations<除了 --commands 的其他二级命令>
        else:
            self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
    # 6、python manage.py version
    elif subcommand == 'version' or self.argv[1:] == ['--version']:
        sys.stdout.write(django.get_version() + '\n')
    # 7、python manage.py --help  或者 python manage.py -h
    elif self.argv[1:] in (['--help'], ['-h']):
        sys.stdout.write(self.main_help_text() + '\n')
    else:
        # 8.除了 help、version
        # 比如 python manage.py migrate, subcommand: migrate
        self.fetch_command(subcommand).run_from_argv(self.argv)

5、fetch_command()

1、上述流程完成之后,代码会走到当前函数

2、注意,进入到当前函数,会先执行 get_commonds() 函数,这很重要

def fetch_command(self, subcommand):
    # 1、 先执行 get_commands(),这很重要
    commands = get_commands()
    try:
        app_name = commands[subcommand]
    except KeyError:
        if os.environ.get('DJANGO_SETTINGS_MODULE'):
            settings.INSTALLED_APPS
        elif not settings.configured:
            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

5.1.get_commands()

1、当走进 get_commonds() 的时候,又出现一个 find_commonds(),看看他返回了什么

@functools.lru_cache(maxsize=None)
def get_commands():

    # __path__: 存在于__init__文件中,被导入时,返回一个列表,列表的第一个元素是当前文件(当前包)所在的目录的绝对路径
    # __path__[0]: 此处为 'D:\\pys\\django_test\\django\\core\\management'
    commands = {name: 'django.core' for name in find_commands(__path__[0])}

    if not settings.configured:
        return commands

    for app_config in reversed(list(apps.get_app_configs())):
        path = os.path.join(app_config.path, 'management')
        commands.update({name: app_config.name for name in find_commands(path)})

    return commands

2、find_commands()

该函数返回commands目录下的文件名列表

 ['check', 'compilemessages', 'createcachetable', 'dbshell', 'diffsettings', 'dumpdata', 'flush', 'inspectdb', 'loaddata', 'makemessages', 'makemigrations', 'migrate', 'runserver', 'sendtestemail', 'shell', 'showmigrations', 'sqlflush', 'sqlmigrate', 'sqlsequencereset', 'squashmigrations', 'startapp', 'startproject', 'test', 'testserver']

3、再次回到 get_commonds() 函数

commands根据上一步返回的文件名列表组装,最后返回值均为为 django.core  的字典

@functools.lru_cache(maxsize=None)
def get_commands():
    # 此时的 commands 是这样的
    # {'check': 'django.core',
    # 'compilemessages': 'django.core',
    # 'createcachetable': 'django.core',
    # 'dbshell': 'django.core',
    # ...
    commands = {name: 'django.core' for name in find_commands(__path__[0])}
   
    if not settings.configured:
        return commands

    for app_config in reversed(list(apps.get_app_configs())):
        path = os.path.join(app_config.path, 'management')
        commands.update({name: app_config.name for name in find_commands(path)})

    return commands

5.2.返回到 get_commands() 函数

def fetch_command(self, subcommand):
    # 1、 先执行 get_commands(),这很重要
    # 2、最后 commands 是一个字典  {'makemigrations': 'django.core','migrate': 'django.core',...
    commands = get_commands()
    try:
        # 3、一个合法的命令,此处的 app_name 就是 "django.core"
        app_name = commands[subcommand]
    except KeyError:
        if os.environ.get('DJANGO_SETTINGS_MODULE'):
            settings.INSTALLED_APPS
        elif not settings.configured:
            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
    # 4、当前的 app_name 是一个字符串对象,因此会走这里
    else:
        # app_name: django.core
        # subcommand: migrate
        klass = load_command_class(app_name, subcommand)
    return klass

5.3.load_command_class()函数

1、上述最后会走到load_command_class(),传入 django.core 和 migrate # 示例命令

def load_command_class(app_name, name):
    # app_name: django.core
    # subcommand: migrate
    # module = django.management.commands.migrate
    module = import_module('%s.management.commands.%s' % (app_name, name))
    # 最后返回的是 migrate.py 中的 Command 对象
    return module.Command()

5.4.执行完毕

1、执行完 load_command_class() ,整个 fetch_command() 执行结束,返回一个 Command 对象

6、excute() 最后一步

1、4.2的时候已经执行到  下方

...    
self.fetch_command(subcommand).run_from_argv(self.argv)

2、fetch_command() 返回的是一个 Command 对象

3、Command对象位于django.core.management.commands目录下

4、此处还是找 migrate.py<示例> 下的 Command类,并找到其父类的 run_from_argv() 方法

5、其中最终执行的就是里面的 excute 方法

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)
    args = cmd_options.pop('args', ())
    handle_default_options(options)
    try:
        # 其中最核心的就是这个 execute() 方法,最终执行的也是这个方法
        self.execute(*args, **cmd_options)
    except CommandError as e:
        if options.traceback:
            raise
        if isinstance(e, SystemCheckError):
            self.stderr.write(str(e), lambda x: x)
        else:
            self.stderr.write('%s: %s' % (e.__class__.__name__, e))
        sys.exit(e.returncode)
    finally:
        try:
            connections.close_all()
        except ImproperlyConfigured:

            pass

6、当点击excute()方法,里面最核心的是 handle 方法

def execute(self, *args, **options):
    # 处理一些公共的逻辑
    if options['force_color'] and options['no_color']:
        raise CommandError("The --no-color and --force-color options can't be used together.")
    if options['force_color']:
        self.style = color_style(force_color=True)
    elif 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'])

    if self.requires_system_checks and not options['skip_checks']:
        if self.requires_system_checks == ALL_CHECKS:
            self.check()
        else:
            self.check(tags=self.requires_system_checks)
    if self.requires_migrations_checks:
        self.check_migrations()
    # 最核心的即是 handle 方法
    output = self.handle(*args, **options)
    if output:
        if self.output_transaction:
            connection = connections[options.get('database', DEFAULT_DB_ALIAS)]
            output = '%s\n%s\n%s' % (
                self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()),
                output,
                self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
            )
        self.stdout.write(output)
    return output

7、excute() 方法存在于 BaseCommand 这个类中

8、BaseCommand 被 django.core.management.commands 下的每一个py中的Command类继承

9、excute()方法中的handle方法实际存在于每一个子类中

10、实际最后我们将执行某个 Command 类中的 handle 方法

7、总结 

1、如果当前命令为 python manage.py startapp app_xx

2、则实际最后执行的是 django.core.management.commands文件夹下的 startapp.py,实例化里面的 Command 这里类,并且调用 handle 方法,下一节将讨论 handle 方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值