tiangolo / typer

Typer, build great CLIs. Easy to code. Based on Python type hints.

Home Page:https://typer.tiangolo.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Using typer.prompt with choices from Enum doesn't display choices

StFS opened this issue · comments

First Check

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

Commit to Help

  • I commit to help with one of those options 👆

Example Code

import typer
from enum import Enum


class ChoiceEnum(Enum):
    option_one = "opt1"
    option_two = "opt2"
    option_three = "opt3"


app = typer.Typer()


@app.command()
def subcommand(argument_option: ChoiceEnum = typer.Option(ChoiceEnum.option_two.value,
                                                          prompt="argument option",
                                                          show_choices=True)):
    prompt_option: ChoiceEnum = typer.prompt("prompt option",
                                             ChoiceEnum.option_three.value,
                                             show_choices=True)
    print(f"Argument choice: {argument_option}")
    print(f"Prompt choice: {prompt_option}")


if __name__ == "__main__":
    app()



# The result of running the above script results in the following output:
#
# > argument option (opt1, opt2, opt3) [opt2]: 
# > prompt option [opt3]: 
# > Argument choice: ChoiceEnum.option_two
# > Prompt choice: opt3

Description

Using the typer.prompt function isn't displaying the choices of an enum the same way that an Option does.

Am I doing something wrong here? Is there some other way I should be doing the prompt in order for it to display the choices as is done with the argument choices?

Also, why are the return values different (the prompt gives me the string "opt3", while the argument returns the enum value itself).

Operating System

macOS

Operating System Details

No response

Typer Version

0.6.1

Python Version

Python 3.10.8

Additional Context

No response

Managed to figure out that I needed to add type=ChoiceEnum to the prompt call to get the return value to be of the same type as the argument. Still doesn't fix showing the choices.

Managed to find a workaround for this by not using an enum and instead use the click.Choice type:

    click_choice = click.Choice(['opt1a', 'opt2a', 'opt3a'])
    click_prompt_option: click.Choice = typer.prompt("click prompt option",
                                                     "opt1a",
                                                     show_choices=True,
                                                     type=click_choice)

Still think that the typer behavior needs to be looked at to make the behavior a bit more predictable. Would think that label should be updated to bug.

I solved the issue by creating a new Choice class for (Int)Enums:

class IntEnumChoice(click.Choice):
    def __init__(self, enum_type: EnumType, case_sensitive: bool = True) -> None:
        choices = [f"{value.value}: {value.name}" for value in enum_type]
        super().__init__(choices, case_sensitive)
        self._enum_type = enum_type

    def convert(self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]) -> t.Any:
        try:
            return self._enum_type(int(value))
        except ValueError:
            choices_str = ", ".join(map(repr, self.choices))
            self.fail(
                ngettext(
                    "{value!r} is not {choice}.",
                    "{value!r} is not one of {choices}.",
                    len(self.choices),
                ).format(value=value, choice=choices_str, choices=choices_str),
                param,
                ctx,
            )

You can easily adapt it for string Enums (just remove the type conversion to int in the return self._enum_type(int(value)) line).