Mam grupę podkomendy, które działają na liście adresów URL, które mogą być opcjonalnie przekazywane jako argument. Jak mogę przypisać ten argument do grupy, aby uniknąć powielenia definicji argumentu na każdym podkomendzie?

Aktualny kod:

from config import site_list

@click.group()
def cli():
    pass

@cli.command()
@cli.argument('sites', nargs=-1)
def subcommand_one():
    if sites:
        site_list = sites
    etc...

@cli.command()
@cli.argument('sites', nargs=-1)
def subcommand_two():
    if sites:
        site_list = sites
    etc...

Przykładowa inwokacja:

$ python sites.py subcommand_one www.example.com www.example2.com

Próbowałem przenieść downora argumentu do grupy takich jak:

@click.group()
@click.argument('sites', nargs=-1)
def cli(sites):
    if sites:
        site_list = sites

Ale potem otrzymałbym ten błąd:

$ python sites.py subcommand_one
Usage: sites.py [OPTIONS] [SITES] COMMAND [ARGS]...
Try "sites.py --help" for help.

Error: Missing command.
4
Erik Kallevig 21 luty 2019, 00:37

3 odpowiedzi

Najlepsza odpowiedź

Jeśli istnieje określony argument nargs = -1, który chcesz udekorować tylko na grupę, ale mieć zastosowanie Wszystkie polecenia w razie potrzeby, możesz to zrobić z dodatkowym hydraulicznym jak:

Ta odpowiedź jest inspirowana Ta odpowiedź.

Klasa niestandardowa

class GroupNArgsForCommands(click.Group):
    """Add special arguments on group"""

    def __init__(self, *args, **kwargs):
        super(GroupNArgsForCommands, self).__init__(*args, **kwargs)
        cls = GroupNArgsForCommands.CommandArgument

        # gather the special arguments for later
        self._cmd_args = {
            a.name: a for a in self.params if isinstance(a, cls)}

        # strip out the special arguments from self
        self.params = [a for a in self.params if not isinstance(a, cls)]

    class CommandArgument(click.Argument):
        """class to allow us to find our special arguments"""

    @staticmethod
    def command_argument(*param_decls, **attrs):
        """turn argument type into type we can find later"""

        assert 'cls' not in attrs, "Not designed for custom arguments"
        attrs['cls'] = GroupNArgsForCommands.CommandArgument

        def decorator(f):
            click.argument(*param_decls, **attrs)(f)
            return f

        return decorator

    def group(self, *args, **kwargs):
        # any derived groups need to be the same type
        kwargs['cls'] = GroupNArgsForCommands

        def decorator(f):
            grp = super(GroupNArgsForCommands, self).group(
                *args, **kwargs)(f)
            self.add_command(grp)

            # any sub commands need to hook the same special args
            grp._cmd_args = self._cmd_args

            return grp

        return decorator

    def add_command(self, cmd, name=None):

        # call original add_command
        super(GroupNArgsForCommands, self).add_command(cmd, name)

        # if this command's callback has desired parameters add them
        import inspect
        args = inspect.signature(cmd.callback)
        if len(args.parameters):
            for arg_name in reversed(list(args.parameters)):
                if arg_name in self._cmd_args:
                    cmd.params[:] = [self._cmd_args[arg_name]] + cmd.params

Korzystanie z klasy niestandardowej:

Aby użyć klasy niestandardowej, przekazać parametr cls do dekoratora click.group(), użyj @GroupNArgsForCommands.command_argument Decorator dla specjalnego argumentu, a następnie dodaj Parametr o tej samej nazwie jako specjalny argument do dowolnych poleceń w razie potrzeby.

@click.group(cls=GroupNArgsForCommands)
@GroupNArgsForCommands.command_argument('special', nargs=-1)
def a_group():
    """My project description"""

@a_group.command()
def a_command(special):
    """a command under the group"""

Jak to działa?

To działa, ponieważ click jest dobrze zaprojektowanym ramami OO. Dekorator @click.group() instancje A click.Group obiekt, ale pozwala na ponad parametr cls.
Jest więc stosunkowo łatwa sprawa do dziedziczenia od click.Group w naszej własnej klasie i nad jazdą pożądane metody.

W takim przypadku na przejażdżce click.Group.add_command() tak, że po dodaniu polecenia możemy zbadać Parametry polecenia, aby sprawdzić, czy mają taką samą nazwę jak każdy z naszych specjalnych argumentów. Jeśli pasują, argument jest dodawany do argumentów poleceń, tak jakby został zdobiony bezpośrednio.

Dodatkowo GroupNArgsForCommands implementuje metodę command_argument(). Ta metoda jest używana jako Dekorator podczas dodawania specjalnego argumentu zamiast używania click.argument()

Klasa testowa

import click

@click.group(cls=GroupNArgsForCommands)
@GroupNArgsForCommands.command_argument('sites', nargs=-1)
def cli():
    click.echo("cli group")

@cli.command()
def command_one(sites):
    click.echo("command_one: {}".format(sites))

@cli.group()
def subcommand():
    click.echo("subcommand group")

@subcommand.command()
def one():
    click.echo("subcommand_one")

@subcommand.command()
def two(sites):
    click.echo("subcommand_two: {}".format(sites))

if __name__ == "__main__":
    commands = (
        'command_one site1 site2',
        'command_one site1',
        'command_one',
        'subcommand',
        'subcommand one site1 site2',
        'subcommand one site1',
        'subcommand one',
        'subcommand two site1 site2',
        'subcommand two site1',
        'subcommand two',
        '--help',
        'command_one --help',
        'subcommand --help',
        'subcommand one --help',
        'subcommand two --help',
        '',
    )

    import sys, time

    time.sleep(1)
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    for command in commands:
        try:
            time.sleep(0.1)
            print('-----------')
            print('> ' + command)
            time.sleep(0.1)
            cli(command.split())

        except BaseException as exc:
            if str(exc) != '0' and \
                    not isinstance(exc, (click.ClickException, SystemExit)):
                raise

Wyniki:

Click Version: 6.7
Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
-----------
> command_one site1 site2
cli group
command_one: ('site1', 'site2')
-----------
> command_one site1
cli group
command_one: ('site1',)
-----------
> command_one
cli group
command_one: ()
-----------
> subcommand
cli group
Usage: test.py subcommand [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  one
  two
-----------
> subcommand one site1 site2
Usage: test.py subcommand one [OPTIONS]

Error: Got unexpected extra arguments (site1 site2)
cli group
subcommand group
-----------
> subcommand one site1
cli group
subcommand group
Usage: test.py subcommand one [OPTIONS]

Error: Got unexpected extra argument (site1)
-----------
> subcommand one
cli group
subcommand group
subcommand_one
-----------
> subcommand two site1 site2
cli group
subcommand group
subcommand_two: ('site1', 'site2')
-----------
> subcommand two site1
cli group
subcommand group
subcommand_two: ('site1',)
-----------
> subcommand two
cli group
subcommand group
subcommand_two: ()
-----------
> --help
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  command_one
  subcommand
-----------
> command_one --help
cli group
Usage: test.py command_one [OPTIONS] [SITES]...

Options:
  --help  Show this message and exit.
-----------
> subcommand --help
cli group
Usage: test.py subcommand [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  one
  two
-----------
> subcommand one --help
cli group
subcommand group
Usage: test.py subcommand one [OPTIONS]

Options:
  --help  Show this message and exit.
-----------
> subcommand two --help
cli group
subcommand group
Usage: test.py subcommand two [OPTIONS] [SITES]...

Options:
  --help  Show this message and exit.
-----------
> 
Usage: test.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  command_one
  subcommand
1
Stephen Rauch 21 luty 2019, 05:14

click.argument Po prostu zwraca ozdobę jak każdy inny, dzięki czemu możesz przypisać go do pewnej zmiennej:

import click

@click.group()
def cli():
    pass

sites_argument = click.argument('sites', nargs=-1)

@cli.command()
@sites_argument
def subcommand_one(sites):
    ...

@cli.command()
@sites_argument
def subcommand_two(sites):
    ...
3
L3viathan 20 luty 2019, 21:43

Myślę, że istnieje rzeczywiste rozwiązanie obsługiwane przez kliknięcie przy użyciu @click.pass_context.

Gdy chcesz zdefiniować grupę poleceń, które wszyscy udostępniają na przykład wspólny argument i wspólną opcję, możesz je zdefiniować na poziomie grupy i dodać je do obiektu kontekstowego, takiego jak Opisane w Dokumentacji Click.

@click.group(chain=True)
@click.argument("dataset_directory", type=click.Path(exists=True))
@click.option("-s", "--split-names", help="The splits to preprocess.", required=True,
              default=["trainset", "devset", "testset"], show_default=True)
@click.pass_context
def cli(ctx, dataset_directory, split_names):
    """
    Prepare the dataset for training

    DATASET_DIRECTORY The absolute path to the data directory.
    """
    ctx.ensure_object(dict)
    ctx.obj["DIRECTORY"] = dataset_directory
    ctx.obj["SPLITS"] = split_names

Następnie indywidualne polecenia tej grupy mogą zostać przekazane kontekst i użyć wartości z obiektu kontekstowego zamiast definiowania własnych argumentów i opcji.


@cli.command("create")
@click.pass_context
def create(ctx):
    create_semantics_json_from_csv(ctx.obj["DIRECTORY"], ctx.obj["SPLITS"])


@cli.command("tokenize")
@click.pass_context
def tokenize(ctx):
    preprocess_tokenize_semantics_json(ctx.obj["DIRECTORY"], ctx.obj["SPLITS"])

Wywołanie poleceń jest wtedy możliwe:

my-cli-app /path/to/data create tokenize

0
phi 28 sierpień 2020, 16:34