Python Click: hacer que la opción dependa de la opción anterior

¿Hay una forma idiomática, utilizando el Python Click library, para crear un comando donde una opción depende de un valor establecido por una opción anterior?

Un ejemplo concreto (mi caso de uso) sería que un comando toma una opción de tipo click.File como entrada, pero también una opción de codificación que especifica la codificación de la secuencia de entrada:

import click

@click.command()
@click.option("--encoding", type=str, default="utf-8")
@click.option("--input",
              type=click.File("r", encoding="CAN I SET THIS DYNAMICALLY BASED ON --encoding?"))
def cli(encoding, input):
    pass

Supongo que tendría que involucrar algún tipo de evaluación diferida utilizando un llamable, pero no estoy seguro de si es posible dado el actual Click API.

Me he dado cuenta de que puedo hacer algo en las siguientes líneas:

import click

@click.command()
@click.pass_context
@click.option("--encoding", type=str, default="utf-8")
@click.option("--input", type=str, default="-")
def cli(ctx, encoding, input):
    input = click.File("r", encoding=encoding)(input, ctx=ctx)

Pero de alguna manera se siente menos legible / mantenible al desacoplar el decorador de opciones de la restricción de tipo semánticamente correcta que se aplica a él, y poner str allí como un dummy. Así que si hay una manera de mantener a estos dos juntos, por favor, ilumíname.

Una solución propuesta:

Supongo que podría usar el clic. Tipo de archivo dos veces, lo que hace que sea perezoso en el decorador para que el archivo no quede abierto, la primera vez:

@click.option("--input", type=click.File("r", lazy=True), default="-")

Esto se siente semánticamente más satisfactorio, pero también redundante.

Mejor respuesta
Es posible heredar de la clase click.File y anular el método .convert () para permitirle recopilar el valor de codificación del contexto.

Usando una clase personalizada

Debería verse algo como:

@click.command()
@click.option("--my_encoding", type=str, default="utf-8")
@click.option("--in_file", type=CustomFile("r", encoding_option_name="my_encoding"))
def cli(my_encoding, in_file):
    ....

CustomFile debe permitir al usuario especificar el nombre que desee para el parámetro desde el cual se debe recopilar el valor de codificación, pero puede haber un valor predeterminado razonable como “codificación”.

Clase de archivo personalizado

Esta clase CustomFile se puede usar en asociación con una opción de codificación:

import click

class CustomFile(click.File):
    """
    A custom `click.File` class which will set its encoding to
    a parameter.

    :param encoding_option_name: The 'name' of the encoding parameter
    """
    def __init__(self, *args, encoding_option_name="encoding", **kwargs):
        # enforce a lazy file, so that opening the file is deferred until after
        # all of the command line parameters have been processed (--encoding
        # might be specified after --in_file)
        kwargs['lazy'] = True
        # Python 3 can use just super()
        super(CustomFile, self).__init__(*args, **kwargs)
        self.lazy_file = None
        self.encoding_option_name = encoding_option_name

    def convert(self, value, param, ctx):
        """During convert, get the encoding from the context."""
        if self.encoding_option_name not in ctx.params:
            # if the encoding option has not been processed yet, wrap its
            # convert hook so that it also retroactively modifies the encoding
            # attribute on self and self.lazy_file
            encoding_opt = [
                c for c in ctx.command.params
                if self.encoding_option_name == c.human_readable_name]
            assert encoding_opt, \
                "option '{}' not found for encoded_file".format(
                    self.encoding_option_name)

            encoding_type = encoding_opt[0].type
            encoding_convert = encoding_type.convert

            def encoding_convert_hook(*convert_args):
                encoding_type.convert = encoding_convert
                self.encoding = encoding_type.convert(*convert_args)
                self.lazy_file.encoding = self.encoding
                return self.encoding

            encoding_type.convert = encoding_convert_hook
        else:
            # if it has already been processed, just use the value
            self.encoding = ctx.params[self.encoding_option_name]

        # Python 3 can use just super()
        self.lazy_file = super(CustomFile, self).convert(value, param, ctx)
        return self.lazy_file

Por favor indique la dirección original:Python Click: hacer que la opción dependa de la opción anterior - Código de registro