typer icon indicating copy to clipboard operation
typer copied to clipboard

Default value for optional multi-value argument

Open mathiazom opened this issue 2 years ago • 6 comments

First Check

  • [X] I added a very descriptive title to this issue.
  • [X] I used the GitHub search to find a similar issue and didn't find it.
  • [X] I searched the Typer documentation, with the integrated search.
  • [X] I already searched in Google "How to X in Typer" and didn't find any information.
  • [X] I already read and followed all the tutorial in the docs and didn't find an answer.
  • [X] I already checked if it is not related to Typer but to Click.

Commit to Help

  • [X] I commit to help with one of those options 👆

Example Code

import typer

def main(names: list[str] = typer.Argument(["Peter", "Paul", "Mary"])):
    for name in names:
        typer.echo(f"Hi, {name}")

if __name__ == '__main__':
    typer.run(main)

Description

The provided example raises the following error:

Traceback (most recent call last):

  File "[...]\main.py", line 8, in <module>
    typer.run(main)

TypeError: 'default' is not supported for nargs=-1.

Looking at the Click docs, my understanding is that nargs=-1 is used to denote Variadic arguments, i.e. multi-value arguments.

So if default is not supported for nargs=-1, is there any way to have default values for multi-value arguments in Typer?

Desired behaviour: the default values are used if no argument values are provided, with a help message that looks something like this:

Usage: main.py [OPTIONS] [NAMES]...

Arguments:
  [NAMES]...  [default: [Peter, Paul, Mary]]

Options:
  --help  Show this message and exit.

Operating System

Windows

Operating System Details

No response

Typer Version

0.7.0

Python Version

3.10.9

Additional Context

I believe this is related to #108, where the conclusion was to use None as default to achieve an optional multi-value argument. However, it would be useful if other default values were supported as well. But this might just be a limitation of Click?

Love the project ❤️

mathiazom avatar Dec 18 '22 12:12 mathiazom

This may or may not help but I got around this issue by manually creating a callback that detects if the given parameter is None and setting it to my desired default value.

I also set show_default=False in foo: List[bar] = typer.Argument(...) and added "[black]\[default: default][/black]" to the end of my help="parameter help string" in order to display an accurate default message when running my-command --help.

I can post the complete code if anyone needs it but I feel it's easy enough to understand from this.

Note Rich is required to use [color][/color] tags inside strings

N4D1K-lgtm avatar Jan 24 '23 18:01 N4D1K-lgtm

Edit: nvm it works for:

def stdin_callback(value: Optional[Path]) -> Path:
    return value if value else [Path('/dev/stdin')]
I'm trying to make CLI which accepts multiple file paths (and stdin if no paths provided) and I get the same error.

def stdin_callback(value: Optional[Path]) -> Path:
    return value if value else Path('/dev/stdin')

def main(
    files: List[Path] = typer.Argument(
        default=[None].
        allow_dash=True,
        exists=True,
        dir_okay=False,
        readable=True,
        callback=stdin_callback)

TypeError: 'default' is not supported for nargs=-1.

Introducing a callback doesn't solve the issue unfortunately.

InCogNiTo124 avatar Jan 28 '23 21:01 InCogNiTo124

Using a callback worked for me

def callback(value: Optional[list[int]]) -> list[int]:
    return value if value else []

JP-Globality avatar Apr 26 '23 12:04 JP-Globality

Trying to do something similar (a command that optionally takes any number of string arguments), I tried many variations, before I discovered that this is the only way that works:

names: List[str] = Argument( default = None )

However, this doesn't, even though it should mean the same thing, it still makes it required.

names: Annotated[List[str], Argument( default = None )]

These similar variations also still end up required:

names: Annotated[List[str], Argument( default = None )]
names: Annotated[List[str], Argument( default_factory = list )]

This one throws the "'default' is not supported for nargs=-1" error:

names: List[str] = Argument( default_factory = list )

And these variations do make it optional, but also turn it into a --names option instead of keeping it as an argument:

names: Annotated[List[str], Argument()] = None
names: Annotated[List[str], Argument()] = []
names: Annotated[Optional[List[str]], Argument()] = None

jasonk avatar Jan 31 '24 20:01 jasonk