fastapi / typer

Typer, build great CLIs. Easy to code. Based on Python type hints.
https://typer.tiangolo.com/
MIT License
15.57k stars 663 forks source link

[FEATURE] Handling Mutliple Variable Length Arguments #110

Closed rmorshea closed 4 years ago

rmorshea commented 4 years ago

I'm not familiar with the internals of typer and whether this is feasible, but it would be nice if it were possible to implement a command like the following:

from typing import Optional, List

import typer

app = typer.Typer()

@app.command()
def main(first_list: List[str], option: bool = False, second_list: List[str] = ()):
    print(first_list, second_list)

Where the expected behavior is:

$ python main.py a b c --option d e f
('a', 'b', 'c') ('d', 'e', 'f')
$ python main.py a b c d e f --option
('a', 'b', 'c', 'd', 'e', 'f') ()
$ python main.py a b c d e f
('a', 'b', 'c', 'd', 'e', 'f') ()

However the actual behavior in this case does not adhere to the above:

$ python main.py a b c --option d e f
('a', 'b', 'c', 'd', 'e', 'f') True ()

This can presently be achieved in argparse quite concisely:

import argparse

def main():
    cli = argparse.ArgumentParser()
    cli.add_argument(
        "first_list", nargs="*", type=str,
    )
    cli.add_argument(
        "--option", nargs="*", type=str, default=[],
    )
    args = cli.parse_args()
    print(args.first_list, args.option)
IamCathal commented 4 years ago

Hey,

In the docs it gives an example as to how you can use multiple values for one argument (such as a list). It involves using the argument name as a flag before each new element like this:

python main.py --first John --first Mary --second Liam --second Sean

Without the flag before each one it won't pick it up. I wasn't able to properly run the call that gave your actual output which was:

python main.py a b c --option d e f

as it shouldn't be valid due to the reason listed above.

Calling the script pasted below python main.py --first Michael --first Liam with and without the --option flag gives me the output ('Michael', 'Liam') () which seems to be what you're trying to achieve. Is this the problem you were encountering or am I misinterpreting you?

from typing import Optional, List

import typer

option = False

def main(first: List[str] = typer.Option(None), 
        option: bool = False, 
        second: List[str] = typer.Option(None)):
    print(first, second)

if __name__ == "__main__":
    typer.run(main)
tiangolo commented 4 years ago

Thanks for the help here @IamCathal ! :rocket: :cake:

Yeah, it's a constraint in Click: https://click.palletsprojects.com/en/7.x/options/#multi-value-options

For options, only a fixed number of arguments is supported.

To declare a fixed number of arguments for a CLI option you can use tuples: https://typer.tiangolo.com/tutorial/multiple-values/options-with-multiple-values/

github-actions[bot] commented 4 years ago

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

alsimoneau commented 3 years ago

I feel like this issue isn't solved.

The original post mention that this would be an appreciated feature that is supported by others. Stating that it is not supported is in no way an answer as that is the point of the issue.

I also have a use case where that feature would be useful. When passing wildcards to the option, bash automatically expands them. This makes the use of the program unintuitive since the user must pass the parameter as a string and the software then needs to reexpand it.

Yes, one could use an argument, but when multiple such file lists are needed, it is impossible. Supporting nargs=-1 on options completely solves this issue.

schuhschuh commented 3 years ago

I second @alsimoneau. I recently discovered and switched from argparse to Typer and enjoy writing CLIs using it. However, the lack of support for multiple arguments of non-fixed length (for which fixed length Tuple annotation can be used) without need to prepend each value with an option key is a limitation which eventually may make me move back to argparse rather.

EDIT: However, checking the latest documentation of Click, it seems this limitation remains and unsure how Typer could work around this limitation. "For options, only a fixed number of arguments is supported."

fabio-porcedda commented 2 years ago

The lack of this feature prevents me to pass from argparse to Typer for my script.

riyadparvez commented 2 years ago

Is there any progress on this issue? This is the only thing that's been stopping me from using typer

miska-ship-it commented 2 years ago

Seconding (thirding?) the need/want for this

gustavorps commented 2 years ago

+1

dromer commented 2 years ago

Oof this is actually a big reason to stick to argparse for me. Was looking at migrating https://github.com/Wasted-Audio/hvcc to Typer but this really is inconvenient.

Rocamonde commented 2 years ago

If this is not going to be fixed / supported, a maintainer should explain why and label the issue accordingly, so that users can decide what tool works best for their needs. Otherwise, it should be reopened.

alsimoneau commented 2 years ago

I feel that the maintainers are not looking at closed issues. It may be a good idea to open a new one referencing this to bring their attention to the issue.

rmorshea commented 2 years ago

The reason this isn't going to be fixed is explained here. What would be more useful is if someone linked to an issue in Click which explains the reason for this, apparently long-standing, limitation.

Rocamonde commented 2 years ago

The reason this isn't going to be fixed is explained here. What would be more useful is if someone linked to an issue in Click which explains the reason for this, apparently long-standing, limitation.

As far as I can see, that comment only says that the requested feature is not supported in another similar library. That in no way answers the question of whether the maintainers will consider adding the feature to this library and why. The reasonable way to proceed when a feature is requested IMO is:

Closing a feature request because another library does not support it feels pretty unconvincing, especially when users are commenting that this is singlehandedly preventing them from using the library.

dromer commented 2 years ago

@Rocamonde Typer actually depends on Click internally. So yes it very much means that such functionality needs to go into Click first.

Rocamonde commented 2 years ago

@Rocamonde Typer actually depends on Click internally. So yes it very much means that such functionality needs to go into Click first.

That's a totally valid reason, but I don't think users should be expected to know this limitation a priori (or even after reading the comment, that is not at all clear).

Even if there is this dependency, it is not clear either that an extension/workaround would not be viable. The README.md says

Typer uses Click internally.

That does not mean that typer will only implement click functionality, or clarify what features can be extended without going through click.

(I know there are good answers to these questions/points, but that is why I commented that this should be explained or linked to by maintainers.)

EDIT: based on this conversation, it is my impression that typer just wraps click through type hints, which justifies the response. Maybe this should be clarified somewhere in the docs.

tiangolo commented 2 years ago

Thanks for the discussion everyone. Yes, Typer is based on Click, it does all the work underneath. It is mentioned in several places, for example here: https://typer.tiangolo.com/alternatives/#click

Typer mainly adds a layer on top of Click, making the code simpler and easier to use, with autocompletion everywhere, etc, but providing all the powerful features of Click underneath.

If anyone wants to add support for this, it would probably be with a PR on Click directly.