typer
typer copied to clipboard
[QUESTION] Option from set of choices, where choices are not valid python identifier names
First check
- [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 searched in Google "How to X in Click" and didn't find any information.
Description
How can I specify that a command argument should be from a set of choices? Based on the typer docs, the canonical way is to use enums https://typer.tiangolo.com/tutorial/parameter-types/enum/ and in click it would be with something like:
@click.option(
"--model", default="1PL", type=click.Choice(models.keys(), case_sensitive=True)
)
The problem I'm having with using enums is that the keys in my dictionary start with numbers, so they are not valid variable names in an enum. Specifically, I have something like this:
models = {
"1PL": Path("./stan/1PL.stan"),
"2PL": Path("./stan/2PL.stan"),
"3PL": Path("./stan/3PL.stan"),
}
Is it possible to pass click.Choice
to typer or if not add a feature that would enable this?
Thanks!
@EntilZha Not an answer to your request, but a simple workaround
If you create an enum that has those keys as values, you can use that in the cli and to access your models dict
from enum import Enum
from pathlib import Path
import typer
class Test(Enum):
ONE = "1PL"
TWO = "2PL"
THREE = "3PL"
app = typer.Typer()
models = {
"1PL": Path("./stan/1PL.stan"),
"2PL": Path("./stan/2PL.stan"),
"3PL": Path("./stan/3PL.stan"),
}
@app.command()
def test(choice: Test = typer.Argument(...)):
print(choice)
print(models[choice.value])
python cli.py 1PL
will print
Test.ONE
stan/1PL.stan
That's workable for my use case, thanks! It would be nice to have a more "native" way, but I also recognize that my case is fairly niche.
I needed to do something similar to this too. However, I wanted mine to be a prompt. Here's how I was able to achieve that:
from enum import Enum
from pathlib import Path
import typer
class Test(Enum):
ONE = "1PL"
TWO = "2PL"
THREE = "3PL"
app = typer.Typer()
models = {
"1PL": Path("./stan/1PL.stan"),
"2PL": Path("./stan/2PL.stan"),
"3PL": Path("./stan/3PL.stan"),
}
@app.command()
def test(
choice: Test = typer.Option(
None,
"--choice",
prompt=True,
show_choices=True,
)
) -> None:
print(choice)
print(models[choice.value])
I agree with @EntilZha -- it would be nice to have a more "native" way. One drawback of using Enums for the "choices" is that enum members are subject to python object naming restrictions (e.g. cannot have dashes).
Thanks for the help here everyone! 👏 🙇
If that solves the original problem, then you can close this issue ✔️
Have in mind that enums use the values, strings, not the identifiers.
Sorry for the long delay! 🙈 I wanted to personally address each issue/PR and they piled up through time, but now I'm checking each one in order.
Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.
I think I found the way:
# saved as test.py
from typing import Annotated
import click
import typer
app = typer.Typer()
@app.command()
def test(choice: Annotated[str, typer.Option(click_type=click.Choice(['1PL', '2PL', '3PL']))] = '1PL'):
print(choice)
if __name__ == "__main__":
app()
It can be recognized as a choice:
❯ python -m test --help
Usage: test.py [OPTIONS]
╭─ Options ─────────────────────────────────────────────╮
│ --choice [1PL|2PL|3PL] [default: 1PL] │
╰───────────────────────────────────────────────────────╯
If I go like this, it would raise error:
❯ python -m test 0PL
Usage: test.py [OPTIONS]
Try 'test.py --help' for help.
╭─ Error ──────────────────────────────────────────╮
│ Got unexpected extra argument (0PL) │
╰──────────────────────────────────────────────────╯