Coverage for typer / _types.py: 100%
49 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-09 00:15 +0000
1from collections.abc import Iterable, Mapping, Sequence 1abcdefg
2from enum import Enum 1abcdefg
3from typing import Any, Generic, TypeVar 1abcdefg
5from . import _click 1abcdefg
6from ._click import types 1abcdefg
7from ._click.shell_completion import CompletionItem 1abcdefg
9ParamTypeValue = TypeVar("ParamTypeValue") 1abcdefg
12class TyperChoice(types.ParamType, Generic[ParamTypeValue]): 1abcdefg
13 # Code adapted from Click 8.3.1, with Typer using enum values in normalize_choice
14 name = "choice" 1abcdefg
16 def __init__( 1abcdefg
17 self, choices: Iterable[ParamTypeValue], case_sensitive: bool = True
18 ) -> None:
19 self.choices: Sequence[ParamTypeValue] = tuple(choices) 1abcdefg
20 self.case_sensitive = case_sensitive 1abcdefg
22 def _normalized_mapping( 1abcdefg
23 self, ctx: _click.Context | None = None
24 ) -> Mapping[ParamTypeValue, str]:
25 """
26 Returns mapping where keys are the original choices and the values are
27 the normalized values that are accepted via the command line.
28 """
29 return { 1abcdefg
30 choice: self.normalize_choice(
31 choice=choice,
32 ctx=ctx,
33 )
34 for choice in self.choices
35 }
37 def normalize_choice( 1abcdefg
38 self, choice: ParamTypeValue, ctx: _click.Context | None
39 ) -> str:
40 normed_value = str(choice.value) if isinstance(choice, Enum) else str(choice) 1abcdefg
42 if ctx is not None and ctx.token_normalize_func is not None: 1abcdefg
43 normed_value = ctx.token_normalize_func(normed_value) 1abcdefg
45 if not self.case_sensitive: 1abcdefg
46 normed_value = normed_value.casefold() 1abcdefg
48 return normed_value 1abcdefg
50 def get_metavar(self, param: _click.Parameter, ctx: _click.Context) -> str | None: 1abcdefg
51 if param.param_type_name == "option" and not param.show_choices: # type: ignore 1abcdefg
52 choice_metavars = [ 1abcdefg
53 types.convert_type(type(choice)).name for choice in self.choices
54 ]
55 choices_str = "|".join([*dict.fromkeys(choice_metavars)]) 1abcdefg
56 else:
57 choices_str = "|".join( 1abcdefg
58 [str(i) for i in self._normalized_mapping(ctx=ctx).values()]
59 )
61 # Use square braces as it's a type.
62 return f"<{choices_str}>" 1abcdefg
64 def get_missing_message( 1abcdefg
65 self, param: _click.Parameter, ctx: _click.Context | None
66 ) -> str:
67 """Message shown when no choice is passed."""
68 choices = ",\n\t".join(self._normalized_mapping(ctx=ctx).values()) 1abcdefg
69 return f"Choose from:\n\t{choices}" 1abcdefg
71 def convert( 1abcdefg
72 self, value: Any, param: _click.Parameter | None, ctx: _click.Context | None
73 ) -> ParamTypeValue:
74 """
75 For a given value from the parser, normalize it and find its
76 matching normalized value in the list of choices. Then return the
77 matched "original" choice.
78 """
79 normed_value = self.normalize_choice(choice=value, ctx=ctx) 1abcdefg
80 normalized_mapping = self._normalized_mapping(ctx=ctx) 1abcdefg
82 try: 1abcdefg
83 return next( 1abcdefg
84 original
85 for original, normalized in normalized_mapping.items()
86 if normalized == normed_value
87 )
88 except StopIteration: 1abcdefg
89 self.fail( 1abcdefg
90 self.get_invalid_choice_message(value=value, ctx=ctx),
91 param=param,
92 ctx=ctx,
93 )
95 def get_invalid_choice_message(self, value: Any, ctx: _click.Context | None) -> str: 1abcdefg
96 """Get the error message when the given choice is invalid."""
97 choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values())) 1abcdefg
98 return f"{value!r} is not one of {choices_str}." 1abcdefg
100 def __repr__(self) -> str: 1abcdefg
101 return f"Choice({list(self.choices)})" 1abcdefg
103 def shell_complete( 1abcdefg
104 self, ctx: _click.Context, param: _click.Parameter, incomplete: str
105 ) -> list[CompletionItem]:
106 """Complete choices that start with the incomplete value."""
108 str_choices = map(str, self.choices) 1abcdefg
110 if self.case_sensitive: 1abcdefg
111 matched = (c for c in str_choices if c.startswith(incomplete)) 1abcdefg
112 else:
113 incomplete = incomplete.lower() 1abcdefg
114 matched = (c for c in str_choices if c.lower().startswith(incomplete)) 1abcdefg
116 return [CompletionItem(c) for c in matched] 1abcdefg