Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify a default value for argument list processed by click?

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

like image 746
codeforester Avatar asked Oct 19 '25 04:10

codeforester


2 Answers

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'>)
like image 116
metatoaster Avatar answered Oct 21 '25 18:10

metatoaster


To default to stdin for a potentially empty list of files, you can define a custom argument class like:

Custom Class:

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.

To use the Custom Class:

@click.command()
@click.argument("logs", cls=FilesDefaultToStdin)
def main(logs):
    ...

How does this work?

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.

like image 26
Stephen Rauch Avatar answered Oct 21 '25 18:10

Stephen Rauch



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!