Lors de l'écriture d'une interface de ligne de commande (CLI) avec le Python bibliothèque de clic , est-il possible de définir par exemple trois options où la deuxième et la troisième ne sont nécessaires que si le premier (facultatif) n'a pas été réglé?
Mon cas d'utilisation est un système de connexion qui me permet de m'authentifier via un authentication token
(option 1), ou, alternativement, via username
(option 2) et password
(option 3).
Si le jeton a été donné, il n'est pas nécessaire de vérifier que username
et password
sont définis ou les invitent. Sinon, si le jeton a été omis, username
et password
deviennent obligatoires et doivent être fournis.
Cela peut-il être fait d'une manière ou d'une autre en utilisant des rappels?
Mon code pour commencer qui bien sûr ne reflète pas le modèle prévu:
@click.command()
@click.option('--authentication-token', Prompt=True, required=True)
@click.option('--username', Prompt=True, required=True)
@click.option('--password', hide_input=True, Prompt=True, required=True)
def login(authentication_token, username, password):
print(authentication_token, username, password)
if __name__ == '__main__':
login()
Cela peut être fait en créant une classe personnalisée dérivée de click.Option
, Et dans cette classe en chevauchant la méthode click.Option.handle_parse_result()
comme:
import click
class NotRequiredIf(click.Option):
def __init__(self, *args, **kwargs):
self.not_required_if = kwargs.pop('not_required_if')
assert self.not_required_if, "'not_required_if' parameter required"
kwargs['help'] = (kwargs.get('help', '') +
' NOTE: This argument is mutually exclusive with %s' %
self.not_required_if
).strip()
super(NotRequiredIf, self).__init__(*args, **kwargs)
def handle_parse_result(self, ctx, opts, args):
we_are_present = self.name in opts
other_present = self.not_required_if in opts
if other_present:
if we_are_present:
raise click.UsageError(
"Illegal usage: `%s` is mutually exclusive with `%s`" % (
self.name, self.not_required_if))
else:
self.Prompt = None
return super(NotRequiredIf, self).handle_parse_result(
ctx, opts, args)
Pour utiliser la classe personnalisée, passez le paramètre cls
à click.option
Décorateur comme:
@click.option('--username', Prompt=True, cls=NotRequiredIf,
not_required_if='authentication_token')
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 remplacer ce comportement par le cls
paramètre. Il est donc relativement facile d'hériter de click.Option
dans notre propre classe et de contourner les méthodes souhaitées.
Dans ce cas, nous roulons sur click.Option.handle_parse_result()
et désactivons la nécessité de user/password
Si le jeton authentication-token
Est présent, et nous nous plaignons si les deux user/password
Sont authentication-token
sont présents.
Remarque: Cette réponse a été inspirée par cette réponse
@click.command()
@click.option('--authentication-token')
@click.option('--username', Prompt=True, cls=NotRequiredIf,
not_required_if='authentication_token')
@click.option('--password', Prompt=True, hide_input=True, cls=NotRequiredIf,
not_required_if='authentication_token')
def login(authentication_token, username, password):
click.echo('t:%s u:%s p:%s' % (
authentication_token, username, password))
if __name__ == '__main__':
login('--username name --password pword'.split())
login('--help'.split())
login(''.split())
login('--username name'.split())
login('--authentication-token token'.split())
de login('--username name --password pword'.split())
:
t:None u:name p:pword
de login('--help'.split())
:
Usage: test.py [OPTIONS]
Options:
--authentication-token TEXT
--username TEXT NOTE: This argument is mutually exclusive with
authentication_token
--password TEXT NOTE: This argument is mutually exclusive with
authentication_token
--help Show this message and exit.
Légèrement amélioré réponse de Stephen Rauch pour avoir plusieurs paramètres mutex.
import click
class Mutex(click.Option):
def __init__(self, *args, **kwargs):
self.not_required_if:list = kwargs.pop("not_required_if")
assert self.not_required_if, "'not_required_if' parameter required"
kwargs["help"] = (kwargs.get("help", "") + "Option is mutually exclusive with " + ", ".join(self.not_required_if) + ".").strip()
super(Mutex, self).__init__(*args, **kwargs)
def handle_parse_result(self, ctx, opts, args):
current_opt:bool = self.name in opts
for mutex_opt in self.not_required_if:
if mutex_opt in opts:
if current_opt:
raise click.UsageError("Illegal usage: '" + str(self.name) + "' is mutually exclusive with " + str(mutex_opt) + ".")
else:
self.Prompt = None
return super(Mutex, self).handle_parse_result(ctx, opts, args)
utiliser comme ceci:
@click.group()
@click.option("--username", Prompt=True, cls=Mutex, not_required_if=["token"])
@click.option("--password", Prompt=True, hide_input=True, cls=Mutex, not_required_if=["token"])
@click.option("--token", cls=Mutex, not_required_if=["username","password"])
def login(ctx=None, username:str=None, password:str=None, token:str=None) -> None:
print("...do what you like with the params you got...")