使用Python Click library是否有惯用的方法来创建一个命令,其中一个选项取决于前一个选项设置的值?
一个具体的例子(我的用例)是命令采用类型click.File
的选项作为输入,但也是一个编码选项,它指定输入流的编码:
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
我想它可能需要使用可调用的某种延迟评估,但我不确定它是否可以在当前的Click API下使用。
我发现我可以按照以下几点做点什么:
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)
但它在某种程度上感觉不太可读/可维护将选项装饰器与适用于它的语义正确类型约束分离,并将str
放在那里而不是虚拟。所以,如果有办法将这两者放在一起,请赐教。
建议的解决方法:
我想我可以使用click.File
类型两次,使其在装饰器中变得懒惰,以便文件实际上不会被打开,第一次出现:
@click.option("--input", type=click.File("r", lazy=True), default="-")
这在语义上更令人满意,但也是多余的。
答案 0 :(得分:2)
可以从click.File
类继承并覆盖.convert()
方法,以允许它从上下文中收集编码值。
它应该类似于:
@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
应该允许用户为应该从中收集编码值的参数指定他们想要的名称,但是可以有合理的默认值,例如“encoding”。
此CustomFile类可与编码选项结合使用:
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