From 71ac672867a015af4a6d9dc54cf747a8e333389f Mon Sep 17 00:00:00 2001 From: Or Hayat Date: Tue, 5 Mar 2024 20:02:38 +0200 Subject: [PATCH] fixed showing choices of list,tuples and set type annotations when the annotated type contain single type --- cyclopts/help.py | 8 ++- tests/test_help.py | 145 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/cyclopts/help.py b/cyclopts/help.py index 5b3802b..b25e92d 100644 --- a/cyclopts/help.py +++ b/cyclopts/help.py @@ -190,6 +190,7 @@ def format_doc(root_app, app: "App", format: str = "restructuredtext"): def _get_choices(type_: Type) -> str: + choices: str = "" if get_origin(type_) is Union: inner_choices = [_get_choices(inner) for inner in get_args(type_)] choices = ",".join(x for x in inner_choices if x) @@ -197,9 +198,10 @@ def _get_choices(type_: Type) -> str: choices = ",".join(str(x) for x in get_args(type_)) elif isclass(type_) and issubclass(type_, Enum): choices = ",".join(x.name.lower().replace("_", "-") for x in type_) - else: - choices = "" - + elif get_origin(type_) in (list, set, tuple): + args = get_args(type_) + if len(args) == 1: + choices = _get_choices(args[0]) return choices diff --git a/tests/test_help.py b/tests/test_help.py index 0609092..ea8e20b 100644 --- a/tests/test_help.py +++ b/tests/test_help.py @@ -1,7 +1,7 @@ import sys from enum import Enum from textwrap import dedent -from typing import List, Literal, Optional, Union +from typing import List, Literal, Optional, Set, Tuple, Union import pytest @@ -519,6 +519,149 @@ def cmd( assert actual == expected +def test_help_format_group_parameters_choices_enum_list(capture_format_group_parameters): + if sys.version_info < (3, 9): + return + + class CompSciProblem(Enum): + fizz = "bleep bloop blop" + buzz = "blop bleep bloop" + + def cmd( + foo: Annotated[ + Optional[list[CompSciProblem]], + Parameter(help="Docstring for foo.", negative_iterable=(), show_default=False, show_choices=True), + ] = None, + ): + pass + + actual = capture_format_group_parameters(cmd) + expected = dedent( + """\ + ╭─ Parameters ───────────────────────────────────────────────────────╮ + │ FOO,--foo Docstring for foo. [choices: fizz,buzz] │ + ╰────────────────────────────────────────────────────────────────────╯ + """ + ) + assert actual == expected + + +def test_help_format_group_parameters_choices_enum_list_typing(capture_format_group_parameters): + class CompSciProblem(Enum): + fizz = "bleep bloop blop" + buzz = "blop bleep bloop" + + def cmd( + foo: Annotated[ + Optional[List[CompSciProblem]], + Parameter(help="Docstring for foo.", negative_iterable=(), show_default=False, show_choices=True), + ] = None, + ): + pass + + actual = capture_format_group_parameters(cmd) + expected = dedent( + """\ + ╭─ Parameters ───────────────────────────────────────────────────────╮ + │ FOO,--foo Docstring for foo. [choices: fizz,buzz] │ + ╰────────────────────────────────────────────────────────────────────╯ + """ + ) + assert actual == expected + + +def test_help_format_group_parameters_choices_literal_set(capture_format_group_parameters): + if sys.version_info < (3, 9): + return + + def cmd( + steps_to_skip: Annotated[ + Optional[set[Literal["build", "deploy"]]], + Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), + ] = None, + ): + pass + + actual = capture_format_group_parameters(cmd) + expected = dedent( + """\ + ╭─ Parameters ───────────────────────────────────────────────────────╮ + │ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │ + │ [choices: build,deploy] │ + ╰────────────────────────────────────────────────────────────────────╯ + """ + ) + print(expected) + assert actual == expected + + +def test_help_format_group_parameters_choices_literal_set_typing(capture_format_group_parameters): + def cmd( + steps_to_skip: Annotated[ + Optional[Set[Literal["build", "deploy"]]], + Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), + ] = None, + ): + pass + + actual = capture_format_group_parameters(cmd) + expected = dedent( + """\ + ╭─ Parameters ───────────────────────────────────────────────────────╮ + │ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │ + │ [choices: build,deploy] │ + ╰────────────────────────────────────────────────────────────────────╯ + """ + ) + assert actual == expected + + +def test_help_format_group_parameters_choices_literal_tuple(capture_format_group_parameters): + if sys.version_info < (3, 9): + return + + def cmd( + steps_to_skip: Annotated[ + Optional[tuple[Literal["build", "deploy"]]], + Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), + ] = None, + ): + pass + + actual = capture_format_group_parameters(cmd) + expected = dedent( + """\ + ╭─ Parameters ───────────────────────────────────────────────────────╮ + │ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │ + │ [choices: build,deploy] │ + ╰────────────────────────────────────────────────────────────────────╯ + """ + ) + print(actual) + assert actual == expected + + +def test_help_format_group_parameters_choices_literal_tuple_typing(capture_format_group_parameters): + def cmd( + steps_to_skip: Annotated[ + Optional[Tuple[Literal["build", "deploy"]]], + Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_default=False, show_choices=True), + ] = None, + ): + pass + + actual = capture_format_group_parameters(cmd) + expected = dedent( + """\ + ╭─ Parameters ───────────────────────────────────────────────────────╮ + │ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │ + │ [choices: build,deploy] │ + ╰────────────────────────────────────────────────────────────────────╯ + """ + ) + assert actual == expected + + def test_help_format_group_parameters_env_var(capture_format_group_parameters): def cmd( foo: Annotated[int, Parameter(env_var=["FOO", "BAR"], help="Docstring for foo.")] = 123,