I have this line of code that is expected to grab all the file names passed to my Python script:
@click.argument("logs", nargs=-1, type=click.File('r'), required=1)
When no file names are passed, I want to default to -
, that is, the standard input. So, if I try:
@click.argument("logs", nargs=-1, type=click.File('r'), required=1, default="-")
click becomes unhappy and throws this error:
TypeError: nargs=-1 in combination with a default value is not supported.
Is there a workaround for this? I tried setting nargs=0
but that throws a different error:
IndexError: tuple index out of range
Since click
had explicitly stated that they disabled this particular feature needed by the question, as reported on this issue on their project, a workaround will be needed and that can be easily implemented as part of the Python function (instead of some more pile on bash code as suggested by the comment on the other answer).
The workaround is simply drop the required argument and handle the missing logs using a statement (similar to what this commit did which referenced the linked issue):
import sys
import click
@click.command()
@click.argument("logs", nargs=-1, type=click.File('r'))
def main(logs):
if not logs:
logs = (sys.stdin,)
print(logs)
# do stuff with logs
if __name__ == '__main__':
main()
Example execution
$ python fail.py
(<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>,)
$ python fail.py -
(<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>,)
$ python fail.py foo bar
(<_io.TextIOWrapper name='foo' mode='r' encoding='UTF-8'>, <_io.TextIOWrapper name='bar' mode='r' encoding='UTF-8'>)
To default to stdin
for a potentially empty list of files, you can define a custom argument class like:
class FilesDefaultToStdin(click.Argument):
def __init__(self, *args, **kwargs):
kwargs['nargs'] = -1
kwargs['type'] = click.File('r')
super().__init__(*args, **kwargs)
def full_process_value(self, ctx, value):
return super().process_value(ctx, value or ('-', ))
Defining this behavior as a class provides for easy reuse.
@click.command()
@click.argument("logs", cls=FilesDefaultToStdin)
def main(logs):
...
This works because click is a well designed OO framework. The @click.argument()
decorator usually instantiates a click.Argument
object but allows this behavior to be over ridden with the cls
parameter. So it is a relatively easy matter to inherit from click.Argument
in our own class and over-ride the desired methods.
In this case, we override click.Argument.full_process_value()
. In our full_process_value()
we look for an empty argument list, and if empty, we add the -
(stdin) argument to the list.
In addition, we auto assign the nargs=-1
and the type=click.File('r')
arguments.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With