J'ai un groupe de sous-commandes qui fonctionnent toutes sur une liste d'URL qui peuvent éventuellement être passées en argument. Comment puis-je attribuer cet argument au groupe à la place pour éviter de dupliquer la définition de l'argument sur chaque sous-commande?
Code actuel:
$ python sites.py subcommand_one Usage: sites.py [OPTIONS] [SITES] COMMAND [ARGS]... Try "sites.py --help" for help. Error: Missing command.
Exemple d'invocation:
@click.group() @click.argument('sites', nargs=-1) def cli(sites): if sites: site_list = sites
J'ai essayé de déplacer le décorateur d'arguments vers le groupe comme ceci:
$ python sites.py subcommand_one www.example.com www.example2.com
Mais alors j'obtiendrais cette erreur:
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...
3 Réponses :
click.argument
renvoie simplement un décorateur comme n'importe quel autre, vous pouvez donc l'assigner à une variable:
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): ...
Je veux rendre cet argument facultatif et fournir une valeur par défaut, mais lorsque j'essaye sites_argument = click.argument ('sites', nargs = -1, default = site_list)
j'obtiens cette erreur TypeError : nargs = -1 en combinaison avec une valeur par défaut n'est pas pris en charge.
Quelle est la meilleure façon d'attribuer une valeur par défaut ici?
Vous pouvez soit ne pas avoir de valeur par défaut et tester à l'intérieur de votre fonction une liste vide, soit utiliser un rappel.
S'il y a un argument nargs = -1
spécifique que vous souhaitez décorer uniquement sur le groupe, mais applicable à
toutes les commandes au besoin, vous pouvez le faire avec une plomberie supplémentaire comme:
Cette réponse est inspirée de ceci une> réponse.
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
Pour utiliser la classe personnalisée, transmettez le cls
au décorateur click.group ()
, utilisez le
@ GroupNArgsForCommands.command_argument
pour l'argument spécial, puis ajoutez un
paramètre du même nom que l'argument spécial de toute commande selon les besoins.
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
Cela fonctionne car cliquez sur est un framework OO bien conçu. Le décorateur
@ click.group ()
généralement
instancie un objet click.Group
mais permet de surcharger ce comportement avec le paramètre cls
.
Il est donc relativement facile d’hériter de click.Group
dans notre propre classe et de remplacer les méthodes souhaitées.
Dans ce cas, nous surpassons click.Group.add_command ()
de sorte que lorsqu'une commande est ajoutée, nous pouvons examiner
les paramètres de rappel de commande pour voir s'ils ont le même nom que l'un de nos arguments spéciaux.
S'ils correspondent, l'argument est ajouté aux arguments de la commande comme s'il avait été décoré directement.
De plus, GroupNArgsForCommands
implémente un command_argument ()
méthode. Cette méthode est utilisée comme
un décorateur lors de l'ajout de l'argument spécial au lieu d'utiliser click.argument()
@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"""
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
C'est la meilleure réponse à ce que j'ai demandé. Cependant, la réponse de L3viathan pourrait en fait être une meilleure solution pour moi. J'essaie de comprendre comment je pourrais fournir une valeur par défaut pour l'argument dans sa solution.
Je pense qu'il existe une solution réelle prise en charge par Click en utilisant le @ click.pass_context
.
Lorsque vous souhaitez définir un groupe de commandes qui partagent toutes par exemple un argument commun et un commun option, vous pouvez les définir au niveau du groupe et les ajouter à un objet de contexte comme décrit dans la documentation Click .
@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"])
Ensuite, les commandes individuelles de ce groupe peuvent obtenir le contexte et utiliser les valeurs de l'objet de contexte au lieu de définir ses propres arguments et options.
@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
L'appel des commandes est alors possible comme:
my-cli -app / chemin / vers / data crée tokenize