是否可以在django管理命令中创建子分析器?

时间:2016-04-18 23:54:24

标签: django argparse django-manage.py subparsers

标题真的说明了一切,但我现在有这个,但它不起作用:

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内容或将其解析为数据库的子命令。

2 个答案:

答案 0 :(得分:9)

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是您决定在用户​​指定subcommand子命令时应将foo选项设置为的任何值。我经常将它设置为可调用。

旧版本的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之前,cmd需要 **kwargs参数(而argparse提供的默认解析器类只需要**kwargs) :

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

因此,当您尝试添加subparser时,它会失败,因为仅使用**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中的评论表明,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所需的正确行为。

答案 1 :(得分: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)