【问题标题】:Is it possible to create subparsers in a django management command?是否可以在 django 管理命令中创建子解析器?
【发布时间】:2016-08-10 21:55:30
【问题描述】:

标题确实说明了一切,但我目前有这个,但它不起作用:

class Command(BaseCommand):
    help = ("Functions related to downloading, parsing, and indexing the  "
            "content")

    def add_arguments(self, parser):
        subparsers = parser.add_subparsers()

        download_parser = subparsers.add_parser(
            'download',
            help='Using a local CSV, download the XML data for content. '
                 'Output is sent to the log.'
        )
        download_parser.add_argument(
            '--start_line',
            type=int,
            default=0,
            help='The line in the file where you wish to start processing.'
        )

        # Add an argparse parser for parsing the content. Yes, this is
        # a bit confusing.
        content_parser_parser = subparsers.add_parser(
            'parse',
            help="Look at the file system and parse everything you see so that "
                 "we have content in the databse."
        )
        content_parser_parser.add_argument(
            '--start_item',
            type=int,
            default=0,
            help="Assuming the content is sorted by file name, this item is "
                 "the one to start on."
        )

我的具体想法是创建一个包含子命令的命令,用于下载 XML 内容或将其解析到数据库中。

【问题讨论】:

  • 不知道parser 中已经包含了什么,或者django 以后用它做什么,我不能说。您的子解析器定义看起来不错。但是正如您从其他 SO 问题中看到的那样,使子解析器与其他参数、位置和/或可选参数一起工作可能会很棘手。就像诊断一样,在函数的开头添加print parser._actions
  • stackoverflow.com/questions/31919101/…,是涉及 argparse 和 django 的先前问题。看起来 django 曾经使用 optparse,但最近添加了 argparse 替代方案。

标签: django argparse django-manage.py subparsers


【解决方案1】:

Django 2.1 及以上版本

在 Django 2.1 及更高版本中,添加子命令很简单:

from django.core.management.base import BaseCommand

class Command(BaseCommand):

    def add_arguments(self, parser):
        subparsers = parser.add_subparsers(title="subcommands",
                                           dest="subcommand",
                                           required=True)

然后你使用subparser,就像你编写一个使用argparse的非Django应用程序一样。例如,如果您想要一个名为 foo 的子命令,它可能采用 --bar 参数:

foo = subparsers.add_parser("foo")
foo.set_defaults(subcommand=fooVal)
foo.add_argument("--bar")

fooVal 是您决定在用户​​指定foo 子命令时应将subcommand 选项设置为的值。我经常将其设置为可调用对象。

Django 的旧版本

这是可能的,但需要一些工作:

from django.core.management.base import BaseCommand, CommandParser

class Command(BaseCommand):

    [...]

    def add_arguments(self, parser):
        cmd = self

        class SubParser(CommandParser):

            def __init__(self, **kwargs):
                super(SubParser, self).__init__(cmd, **kwargs)

        subparsers = parser.add_subparsers(title="subcommands",
                                           dest="subcommand",
                                           required=True,
                                           parser_class=SubParser)

当您默认调用add_subparsers 时,argparse 会创建一个新解析器,该解析器与您调用add_subparser 的解析器属于同一类。碰巧您在parser 中获得的解析器是CommandParser 实例(在django.core.management.base 中定义)。 CommandParser需要**kwargs 之前有一个cmd 参数(而argparse 提供的默认解析器类只需要**kwargs):

def __init__(self, cmd, **kwargs):

因此,当您尝试添加子解析器时,它会失败,因为仅使用 **kwargs 调用构造函数并且缺少 cmd 参数。

上面的代码通过在parser_class 参数中传递一个添加缺失参数的类来解决这个问题。

需要考虑的事项:

  1. 在上面的代码中,我创建了一个新类,因为名称parser_class 表明应该在那里传递的是一个真正的类。但是,这也有效:

    def add_arguments(self, parser):
        cmd = self
        subparsers = parser.add_subparsers(
            title="subcommands",
            dest="subcommand",
            required=True,
            parser_class=lambda **kw: CommandParser(cmd, **kw))
    

    目前我还没有遇到任何问题,但未来对argparse 的更改可能会导致使用 lambda 而不是真正的类失败。由于该参数称为parser_class 而不是parser_makerparser_manufacture 这样的更改,因此我认为这样的更改是公平的游戏。

  2. 难道我们不能只传递一个现有的argparse 类,而不是传递parser_class 中的一个自定义类吗?不会有直接问题,但会有意想不到的后果。 CommandParser 中的 cmets 表明 argparse 的棒解析器的行为对于 Django 命令来说是不可取的。特别是,docstring 用于类状态:

    """
    Customized ArgumentParser class to improve some error messages and prevent
    SystemExit in several occasions, as SystemExit is unacceptable when a
    command is called programmatically.
    """
    

    这是Jerzyk's answer 遇到的问题。这里的解决方案通过从CommandParser 派生来避免该问题,从而提供 Django 所需的正确行为。

【讨论】:

  • 看起来已经进行了更改,但这对 Django 2.1/Python3.6 不再起作用:parser_class=lambda **kw: CommandParser(self, **kw): TypeError: __init__() takes 1 positional argument but 2 were given
  • @rcoup 我的网站仍在 1.11 上。我查看了相关更改,在我看来,在 2.1 中所做的更改完全消除了传递cmd 参数的变通方法的需要。换句话说,您应该能够通过调用parser.add_subparsers 添加一个子解析器,而无需使用parser_class 参数。你试过了吗?
  • 效果很好。那里的树木我看不到森林 :) subparsers.required=True 似乎也是需要的,也许以前是默认的?
  • 似乎需要显式设置subparsers.required=True 出现在 Python 3.x 系列的某个地方,但不影响 2.7.x 系列。由于 3rd 方库仍未转换为 3.x,因此我仍在使用 2.7,因此该问题不会影响我的代码。有关解释,请参阅this answer,有关血腥细节,请参阅答案所指的bug report
  • 当子解析器参数(args 的数量、预期数量时的字符串等)中存在错误时,为防止引发 CommandError 并显示来自 argparse 的有用消息,请将 called_from_command_line=True 添加到add_parser() 的参数。
【解决方案2】:

你可以添加它,这很简单:

class Command(BaseCommand):
    help = 'dump/restore/diff'

    def add_arguments(self, parser):
        parser.add_argument('-s', '--server', metavar='server', type=str, 
                            help='server address')
        parser.add_argument('-d', '--debug', help='Print lots of debugging') 

        subparsers = parser.add_subparsers(metavar='command',
                                           dest='command',
                                           help='sub-command help')
        subparsers.required = True

        parent_parser = argparse.ArgumentParser(add_help=False)
        parent_parser.add_argument('machine', metavar='device', type=str)
        parent_parser.add_argument('-e', '--errors', action='store_true')

        parser_dump = subparsers.add_parser('dump', parents=[parent_parser],
                                            cmd=self)
        parser_dump.add_argument('-i', '--indent', metavar='indent', type=int,                                   
                                  default=None, help='file indentation')

        parser_restore = subparsers.add_parser('restore',             
                                               parents=[parent_parser],
                                               cmd=self)
        parser_restore.add_argument('infile', nargs='?', 
                                    type=argparse.FileType('r'), 
                                    default=sys.stdin)

        parser_diff = subparsers.add_parser('diff', parents=[parent_parser], 
                                            cmd=self)
        parser_diff.add_argument('infile', nargs='?', 
                                 type=argparse.FileType('r'),
                                 default=sys.stdin)

【讨论】:

  • 您在此处使用 vanilla argparse,它将执行系统退出。这是不可取的,所以这就是 Django 继承该类并摆脱该行为的原因。因此,如果您关心保持与 Django 项目相同的标准,则不推荐这样做
猜你喜欢
  • 2012-05-01
  • 1970-01-01
  • 2016-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
  • 2021-09-23
相关资源
最近更新 更多