python: haga clic en Interfaces de línea de comandos: haga que las opciones sean necesarias si no se ha configurado otra opción opcional

Al escribir una interfaz de línea de comandos (CLI) con Python click library, ¿es posible definir, por ejemplo? tres opciones donde la segunda y la tercera solo son necesarias si la primera (opcional) se dejó sin configurar?

Mi caso de uso es un sistema de inicio de sesión que me permite autenticarme a través de un token de autenticación (opción 1) o, alternativamente, a través del nombre de usuario (opción 2) y la contraseña (opción 3).

Si se entregó el token, no es necesario verificar el nombre de usuario y la contraseña que se están definiendo o solicitando. De lo contrario, si se omitía el token, entonces se requieren un nombre de usuario y una contraseña, que se deben proporcionar.

¿Se puede hacer esto de alguna manera usando devoluciones de llamada?

Mi código para comenzar, que por supuesto no refleja el patrón deseado:

@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()
Mejor respuesta
Esto se puede hacer construyendo una clase personalizada derivada de click.Option, y en esa clase sobre el método click.Option.handle_parse_result () como:

Clase personalizada:

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)

Usando la clase personalizada:

Para usar la clase personalizada, pase el parámetro cls a click.option decorator como:

@click.option('--username', prompt=True, cls=NotRequiredIf,
              not_required_if='authentication_token')

¿Como funciona esto?

Esto funciona porque hacer clic es un marco OO bien diseñado. El decorador @ click.option () generalmente crea una instancia de un objeto click.Option, pero permite que este comportamiento se anule con el parámetro cls. Por lo tanto, es un asunto relativamente fácil de heredar del clic. Opción en nuestra propia clase y sobrepasar los métodos deseados.

En este caso, sobrepasamos click.Option.handle_parse_result () y deshabilitamos la necesidad de usuario / contraseña si el token de autenticación-token está presente, y nos quejamos si tanto el usuario como la contraseña son autenticación.

Nota: Esta respuesta fue inspirada por this answer.

Código de prueba:

@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())

Resultados:

desde el inicio de sesión (‘- nombre de usuario – contraseña pword’.split ()):

t:None  u:name  p:pword

desde el inicio de sesión (‘- 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.

Por favor indique la dirección original:python: haga clic en Interfaces de línea de comandos: haga que las opciones sean necesarias si no se ha configurado otra opción opcional - Código de registro