web-dev-qa-db-fra.com

Comment passer plusieurs listes d'arguments à @ click.option

Je souhaite appeler un script python via la ligne de commande avec ce type de paramètre (la liste peut avoir n'importe quelle taille, par exemple avec 3):

python test.py --option1 ["o11", "o12", "o13"] --option2 ["o21", "o22", "o23"]

en utilisant click. Dans la documentation, il n’est indiqué nulle part que nous pouvons utiliser une liste en tant que paramètre pour @ click.option

Et quand j'essaie de faire ça:

#!/usr/bin/env python
import click

@click.command(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('--option', default=[])
def do_stuff(option):

    return

# do stuff
if __== '__main__':
    do_stuff()

dans mon test.py, en l'appelant à partir de la ligne de commande:

python test.py --option ["some option", "some option 2"]

Je reçois une erreur:

Erreur: Vous avez un argument supplémentaire inattendu (option 2])

Je ne peux pas vraiment utiliser d'arguments variadiques car un seul argument par commande est autorisé ( http://click.pocoo.org/5/arguments/#variadic-arguments )

Donc, si quelqu'un peut m'indiquer la bonne direction (en utilisant click de préférence), ce serait très apprécié.

7
downstroy

Vous pouvez forcer le clic à prendre plusieurs arguments de liste si les listes sont formatées comme des chaînes littérales de listes python en utilisant une classe d'options personnalisée comme:

Classe personnalisée:

import click
import ast

class PythonLiteralOption(click.Option):

    def type_cast_value(self, ctx, value):
        try:
            return ast.literal_eval(value)
        except:
            raise click.BadParameter(value)

Cette classe utilisera le module Abstract Syntax Tree de Python pour analyser le paramètre en tant que littéral python.

Usage de classe personnalisé:

Pour utiliser la classe personnalisée, transmettez le paramètre cls au décorateur @click.option(), comme suit:

@click.option('--option1', cls=PythonLiteralOption, default=[])

Comment cela marche-t-il?

Cela fonctionne car click est un framework OO bien conçu. Le décorateur @click.option() instancie généralement un objet click.Option mais permet de surcharger ce comportement avec le paramètre cls. Il est donc relativement facile d'hériter de click.Option dans notre propre classe et d'utiliser les méthodes souhaitées.

Dans ce cas, nous dépassons click.Option.type_cast_value(), puis appelons ast.literal_eval() pour analyser la liste.

Code de test:

@click.command(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('--option1', cls=PythonLiteralOption, default=[])
@click.option('--option2', cls=PythonLiteralOption, default=[])
def cli(option1, option2):
    click.echo("Option 1, type: {}  value: {}".format(
        type(option1), option1))
    click.echo("Option 2, type: {}  value: {}".format(
        type(option2), option2))

# do stuff
if __== '__main__':
    import shlex
    cli(shlex.split(
        '''--option1 '["o11", "o12", "o13"]' 
        --option2 '["o21", "o22", "o23"]' '''))

Résultats de test:

Option 1, type: <type 'list'>  value: ['o11', 'o12', 'o13']
Option 2, type: <type 'list'>  value: ['o21', 'o22', 'o23']
7
Stephen Rauch

Ce qui suit peut être un correctif de piratage plus facile:

#!/usr/bin/env python
import click
import json

@click.command(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('--option', help='Whatever')
def do_stuff(option):
    try:
        option = json.loads(option)    
    except ValueError:
        pass

# do stuff
if __== '__main__':
    do_stuff()

Cela peut vous aider à utiliser 'option' en tant que list ou str.

1
Murphy

Le "hack facile" de @ Murphy a presque fonctionné pour moi, mais le fait est que option sera un string à moins que vous n'indiquiez les options en une seule fois. J'ai donc dû le faire pour recomposer la liste:

#!/usr/bin/env python
import click
import json

@click.command(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('--option', help='Whatever')
def do_stuff(option):
    try:
        option = json.loads(option)
        # option = str(option)  # this also works
    except ValueError:
        pass

    option = option[1:-1]  # trim '[' and ']'

    options = option.split(',')

    for i, value in enumerate(options):
        # catch integers
        try:
            int(value)
        except ValueError:
            options[i] = value
        else:
            options[i] = int(value)

    # Here use options as you need

# do stuff
if __== '__main__':
    do_stuff()

Vous pourriez attraper d'autres types

Pour l'utiliser, mettez la liste entre guillemets:

python test.py --option "[o11, o12, o13]"

Ou vous pouvez éviter les citations en ne laissant pas d’espace:

python test.py --option [o11,o12,o13]
0