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

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

Open
7 tasks done
StFS opened this issue Oct 24, 2022 · 3 comments
Open
7 tasks done

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

StFS opened this issue Oct 24, 2022 · 3 comments
Labels
investigate question Question or problem

Comments

@StFS
Copy link

StFS commented Oct 24, 2022

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

@StFS StFS added the question Question or problem label Oct 24, 2022
@StFS
Copy link
Author

StFS commented Oct 24, 2022

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.

@StFS
Copy link
Author

StFS commented Oct 24, 2022

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.

@MShekow
Copy link

MShekow commented May 21, 2023

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigate question Question or problem
Projects
None yet
Development

No branches or pull requests

3 participants