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

fixed showing choices of list,tuples and set type annotations #123

Merged
merged 5 commits into from
Mar 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 10 additions & 7 deletions cyclopts/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,19 @@ def format_doc(root_app, app: "App", format: str = "restructuredtext"):


def _get_choices(type_: Type) -> str:
if get_origin(type_) is Union:
choices: str = ""
_origin = get_origin(type_)
if isclass(type_) and issubclass(type_, Enum):
choices = ",".join(x.name.lower().replace("_", "-") for x in type_)
elif _origin is Union:
inner_choices = [_get_choices(inner) for inner in get_args(type_)]
choices = ",".join(x for x in inner_choices if x)
elif get_origin(type_) is Literal:
elif _origin is Literal:
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 _origin in (list, set, tuple):
args = get_args(type_)
BrianPugh marked this conversation as resolved.
Show resolved Hide resolved
if len(args) == 1 or (_origin is tuple and len(args) == 2 and args[1] is Ellipsis):
choices = _get_choices(args[0])
return choices


Expand Down
194 changes: 193 additions & 1 deletion tests/test_help.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -519,6 +519,198 @@ def cmd(
assert actual == expected


@pytest.mark.skipif(
sys.version_info < (3, 9), reason="https://peps.python.org/pep-0585/ Standard Collections Type Hints"
)
def test_help_format_group_parameters_choices_enum_list(capture_format_group_parameters):
class CompSciProblem(Enum):
fizz = "bleep bloop blop"
buzz = "blop bleep bloop"

def cmd(
foo: Annotated[
Optional[list[CompSciProblem]], # pyright: ignore
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


@pytest.mark.skipif(
sys.version_info < (3, 9), reason="https://peps.python.org/pep-0585/ Standard Collections Type Hints"
)
def test_help_format_group_parameters_choices_literal_set(capture_format_group_parameters):
def cmd(
steps_to_skip: Annotated[
Optional[set[Literal["build", "deploy"]]], # pyright: ignore
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


@pytest.mark.skipif(
sys.version_info < (3, 9), reason="https://peps.python.org/pep-0585/ Standard Collections Type Hints"
)
def test_help_format_group_parameters_choices_literal_tuple(capture_format_group_parameters):
def cmd(
steps_to_skip: Annotated[
Optional[tuple[Literal["build", "deploy"]]], # pyright: ignore
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[
Tuple[Literal["build", "deploy"]],
Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_choices=True),
] = ("build",),
):
pass

actual = capture_format_group_parameters(cmd)
expected = dedent(
"""\
╭─ Parameters ───────────────────────────────────────────────────────╮
│ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │
│ [choices: build,deploy] [default: │
│ ('build',)] │
╰────────────────────────────────────────────────────────────────────╯
"""
)
assert actual == expected


def test_help_format_group_parameters_choices_literal_tuple_variadic_typing(capture_format_group_parameters):
def cmd(
steps_to_skip: Annotated[
Tuple[Literal["build", "deploy"], ...],
Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_choices=True),
] = (),
):
pass

actual = capture_format_group_parameters(cmd)
expected = dedent(
"""\
╭─ Parameters ───────────────────────────────────────────────────────╮
│ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │
│ [choices: build,deploy] [default: │
│ ()] │
╰────────────────────────────────────────────────────────────────────╯
"""
)
assert actual == expected


@pytest.mark.skipif(
sys.version_info < (3, 9), reason="https://peps.python.org/pep-0585/ Standard Collections Type Hints"
)
def test_help_format_group_parameters_choices_literal_tuple_variadic(capture_format_group_parameters):
def cmd(
steps_to_skip: Annotated[
tuple[Literal["build", "deploy"], ...], # pyright: ignore
Parameter(help="Docstring for steps_to_skip.", negative_iterable=(), show_choices=True),
] = ("build",),
):
pass

actual = capture_format_group_parameters(cmd)
expected = dedent(
"""\
╭─ Parameters ───────────────────────────────────────────────────────╮
│ STEPS-TO-SKIP,--steps-to-skip Docstring for steps_to_skip. │
│ [choices: build,deploy] [default: │
│ ('build',)] │
╰────────────────────────────────────────────────────────────────────╯
"""
)
print(actual)
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,
Expand Down