Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Cannot create optional multi-valued positional argument #108

Closed
rrei opened this issue May 26, 2020 · 3 comments
Closed

[BUG] Cannot create optional multi-valued positional argument #108

rrei opened this issue May 26, 2020 · 3 comments
Labels
bug Something isn't working

Comments

@rrei
Copy link

rrei commented May 26, 2020

Describe the bug

First off, let me thank you for creating typer 😄 I absolutely love it, especially because click was my favorite CLI library and you went and made it even nicer 👍

Soooo, I was attempting to create a CLI with an optional multi-valued positional argument (i.e. taking zero or more values). I made two attempts to no avail. The first one didn't even run, and the second one didn't allow me to omit the argument (the "optional" part).

To Reproduce

Here is a minimal example that shows my first attempt at using a list-valued typer.Argument(). According to the docs, providing a default value makes the argument optional, so I provided an empty list.

import typer
import typing

app = typer.Typer()

@app.command()
def test(
    patterns: typing.List[str] = typer.Argument([]),
    untracked: bool = typer.Option(False, "-u", "--untracked"),
):
    typer.echo(locals())

if __name__ == "__main__":
    app()

Running this with python test.py gives me the following stack trace:

% python test.py
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    app()
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 213, in __call__
    return get_command(self)()
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 238, in get_command
    click_command = get_command_from_info(typer_instance.registered_commands[0])
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 422, in get_command_from_info
    ) = get_params_convertors_ctx_param_name_from_function(command_info.callback)
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 403, in get_params_convertors_ctx_param_name_from_function
    click_param, convertor = get_click_param(param)
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/typer/main.py", line 722, in get_click_param
    click.Argument(
  File "/home/rui/miniconda3/envs/test/lib/python3.8/site-packages/click/core.py", line 1986, in __init__
    raise TypeError(
TypeError: nargs=-1 in combination with a default value is not supported.

So in an attempt to fix this error, I replaced with argument's default value with ellipsis ..., which allows the program to run but requires at least one value for the argument.

% python test.py
Usage: test.py [OPTIONS] PATTERNS...
Try 'test.py --help' for help.

Error: Missing argument 'PATTERNS...'.

Expected behavior

I believe the error is due to typer marking the argument as required despite it being a list-valued argument. I would expect it to be possible (and even the default behavior!) to create list-valued arguments that take zero or more values, instead of one or more. Even better (if possible) would be a generalization where minimum and maximum number of values consumed by the argument are specified, defaulting to 0 and infinity, respectively.

Environment

  • OS: Debian 10
  • Python 3.8.2
  • Typer 0.2.1
  • Click 7.1.2
@rrei rrei added the bug Something isn't working label May 26, 2020
@IamCathal
Copy link
Contributor

Hi,

To enable you to take in a list with zero or more values instead of using typer.Argument(...) or typer.Argument([]) you can use typer.Argument(None).

Running the script below withpython3 main.py doesn't raise any errors.

import typer
import typing

app = typer.Typer()
app.command()

def test(
    patterns: typing.List[str] = typer.Argument(None),
    untracked: bool = typer.Option(False, "-u", "--untracked"),
):
    if patterns:
        typer.echo("Arguments for patterns provided")
    else:
        typer.echo("No arguments for patterns provided")

if __name__ == "__main__":
    app()

@rrei
Copy link
Author

rrei commented Jun 11, 2020

@IamCathal thank you for the response. I am very happy to know that it is possible to have optional multi-valued arguments in typer! 🥳

I seem to have missed the part of the docs where a similar example is given... 😅
I was probably misguided by this other example (https://typer.tiangolo.com/tutorial/multiple-values/multiple-options/#multiple-float) where an empty list is used as a default for a list-valued Option. Seeing this, I expected to be able to do the same with Argument.
Besides, to me at least, it is unintuitive to use None as a default for a list-valued argument, only to have it magically converted into an empty tuple before reaching the command.

Perhaps this should be fixed for consistency across Argument and Option. If not, at least the docs should mention this limitation/difference.

I am closing the issue since it is not a bug after all. Thanks again!

@rrei rrei closed this as completed Jun 11, 2020
@tiangolo
Copy link
Member

Thanks a lot for the help @IamCathal ! 🍰 🙇

Yeah, I agree @rrei it is not great. 😅 It is actually a bug in Click itself if I remember correctly.

Anyway, thanks for reporting back and closing the issue!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants