Fix never_prompt precedence in BaseAction, Allow default_selection to be resolved from index, keys, values, or description
This commit is contained in:
		| @@ -63,7 +63,7 @@ class BaseAction(ABC): | ||||
|         hooks: HookManager | None = None, | ||||
|         inject_last_result: bool = False, | ||||
|         inject_into: str = "last_result", | ||||
|         never_prompt: bool = False, | ||||
|         never_prompt: bool | None = None, | ||||
|         logging_hooks: bool = False, | ||||
|     ) -> None: | ||||
|         self.name = name | ||||
| @@ -72,7 +72,7 @@ class BaseAction(ABC): | ||||
|         self.shared_context: SharedContext | None = None | ||||
|         self.inject_last_result: bool = inject_last_result | ||||
|         self.inject_into: str = inject_into | ||||
|         self._never_prompt: bool = never_prompt | ||||
|         self._never_prompt: bool | None = never_prompt | ||||
|         self._skip_in_chain: bool = False | ||||
|         self.console: Console = console | ||||
|         self.options_manager: OptionsManager | None = None | ||||
| @@ -122,7 +122,9 @@ class BaseAction(ABC): | ||||
|  | ||||
|     @property | ||||
|     def never_prompt(self) -> bool: | ||||
|         return self.get_option("never_prompt", self._never_prompt) | ||||
|         if self._never_prompt is not None: | ||||
|             return self._never_prompt | ||||
|         return self.get_option("never_prompt", False) | ||||
|  | ||||
|     def prepare( | ||||
|         self, shared_context: SharedContext, options_manager: OptionsManager | None = None | ||||
|   | ||||
| @@ -30,7 +30,7 @@ from falyx.themes import OneColors | ||||
|  | ||||
| class SelectFileAction(BaseAction): | ||||
|     """ | ||||
|     SelectFileAction allows users to select a file from a directory and return: | ||||
|     SelectFileAction allows users to select a file(s) from a directory and return: | ||||
|     - file content (as text, JSON, CSV, etc.) | ||||
|     - or the file path itself. | ||||
|  | ||||
| @@ -50,6 +50,9 @@ class SelectFileAction(BaseAction): | ||||
|         style (str): Style for the selection options. | ||||
|         suffix_filter (str | None): Restrict to certain file types. | ||||
|         return_type (FileType): What to return (path, content, parsed). | ||||
|         number_selections (int | str): How many files to select (1, 2, '*'). | ||||
|         separator (str): Separator for multiple selections. | ||||
|         allow_duplicates (bool): Allow selecting the same file multiple times. | ||||
|         prompt_session (PromptSession | None): Prompt session for user input. | ||||
|     """ | ||||
|  | ||||
| @@ -217,7 +220,7 @@ class SelectFileAction(BaseAction): | ||||
|             er.record(context) | ||||
|  | ||||
|     async def preview(self, parent: Tree | None = None): | ||||
|         label = f"[{OneColors.GREEN}]📁 SelectFilesAction[/] '{self.name}'" | ||||
|         label = f"[{OneColors.GREEN}]📁 SelectFileAction[/] '{self.name}'" | ||||
|         tree = parent.add(label) if parent else Tree(label) | ||||
|  | ||||
|         tree.add(f"[dim]Directory:[/] {str(self.directory)}") | ||||
| @@ -243,6 +246,6 @@ class SelectFileAction(BaseAction): | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
|         return ( | ||||
|             f"SelectFilesAction(name={self.name!r}, dir={str(self.directory)!r}, " | ||||
|             f"SelectFileAction(name={self.name!r}, dir={str(self.directory)!r}, " | ||||
|             f"suffix_filter={self.suffix_filter!r}, return_type={self.return_type})" | ||||
|         ) | ||||
|   | ||||
| @@ -25,11 +25,60 @@ from falyx.themes import OneColors | ||||
|  | ||||
| class SelectionAction(BaseAction): | ||||
|     """ | ||||
|     A selection action that prompts the user to select an option from a list or | ||||
|     dictionary. The selected option is then returned as the result of the action. | ||||
|     A Falyx Action for interactively or programmatically selecting one or more items | ||||
|     from a list or dictionary of options. | ||||
|  | ||||
|     If return_key is True, the key of the selected option is returned instead of | ||||
|     the value. | ||||
|     `SelectionAction` supports both `list[str]` and `dict[str, SelectionOption]` | ||||
|     inputs. It renders a prompt (unless `never_prompt=True`), validates user input | ||||
|     or injected defaults, and returns a structured result based on the specified | ||||
|     `return_type`. | ||||
|  | ||||
|     It is commonly used for item pickers, confirmation flows, dynamic parameterization, | ||||
|     or guided workflows in interactive or headless CLI pipelines. | ||||
|  | ||||
|     Features: | ||||
|     - Supports single or multiple selections (`number_selections`) | ||||
|     - Dictionary mode allows rich metadata (description, value, style) | ||||
|     - Flexible return values: key(s), value(s), item(s), description(s), or mappings | ||||
|     - Fully hookable lifecycle (`before`, `on_success`, `on_error`, `after`, `on_teardown`) | ||||
|     - Default selection logic supports previous results (`last_result`) | ||||
|     - Can run in headless mode using `never_prompt` and fallback defaults | ||||
|  | ||||
|     Args: | ||||
|         name (str): Action name for tracking and logging. | ||||
|         selections (list[str] | dict[str, SelectionOption] | dict[str, Any]): | ||||
|             The available choices. If a plain dict is passed, values are converted | ||||
|             into `SelectionOption` instances. | ||||
|         title (str): Title shown in the selection UI (default: "Select an option"). | ||||
|         columns (int): Number of columns in the selection table. | ||||
|         prompt_message (str): Input prompt for the user (default: "Select > "). | ||||
|         default_selection (str | list[str]): Key(s) or index(es) used as fallback selection. | ||||
|         number_selections (int | str): Max number of choices allowed (or "*" for unlimited). | ||||
|         separator (str): Character used to separate multi-selections (default: ","). | ||||
|         allow_duplicates (bool): Whether duplicate selections are allowed. | ||||
|         inject_last_result (bool): If True, attempts to inject the last result as default. | ||||
|         inject_into (str): The keyword name for injected value (default: "last_result"). | ||||
|         return_type (SelectionReturnType | str): The type of result to return. | ||||
|         prompt_session (PromptSession | None): Reused or customized prompt_toolkit session. | ||||
|         never_prompt (bool): If True, skips prompting and uses default_selection or last_result. | ||||
|         show_table (bool): Whether to render the selection table before prompting. | ||||
|  | ||||
|     Returns: | ||||
|         Any: The selected result(s), shaped according to `return_type`. | ||||
|  | ||||
|     Raises: | ||||
|         CancelSignal: If the user chooses the cancel option. | ||||
|         ValueError: If configuration is invalid or no selection can be resolved. | ||||
|         TypeError: If `selections` is not a supported type. | ||||
|  | ||||
|     Example: | ||||
|         SelectionAction( | ||||
|             name="PickEnv", | ||||
|             selections={"dev": "Development", "prod": "Production"}, | ||||
|             return_type="key", | ||||
|         ) | ||||
|  | ||||
|     This Action supports use in both interactive menus and chained, non-interactive CLI flows. | ||||
|     """ | ||||
|  | ||||
|     def __init__( | ||||
| @@ -46,7 +95,7 @@ class SelectionAction(BaseAction): | ||||
|         title: str = "Select an option", | ||||
|         columns: int = 5, | ||||
|         prompt_message: str = "Select > ", | ||||
|         default_selection: str = "", | ||||
|         default_selection: str | list[str] = "", | ||||
|         number_selections: int | str = 1, | ||||
|         separator: str = ",", | ||||
|         allow_duplicates: bool = False, | ||||
| @@ -202,6 +251,95 @@ class SelectionAction(BaseAction): | ||||
|             raise ValueError(f"Unsupported return type: {self.return_type}") | ||||
|         return result | ||||
|  | ||||
|     async def _resolve_effective_default(self) -> str: | ||||
|         effective_default: str | list[str] = self.default_selection | ||||
|         maybe_result = self.last_result | ||||
|         if self.number_selections == 1: | ||||
|             if isinstance(effective_default, list): | ||||
|                 effective_default = effective_default[0] if effective_default else "" | ||||
|             elif isinstance(maybe_result, list): | ||||
|                 maybe_result = maybe_result[0] if maybe_result else "" | ||||
|             default = await self._resolve_single_default(maybe_result) | ||||
|             if not default: | ||||
|                 default = await self._resolve_single_default(effective_default) | ||||
|             if not default and self.inject_last_result: | ||||
|                 logger.warning( | ||||
|                     "[%s] Injected last result '%s' not found in selections", | ||||
|                     self.name, | ||||
|                     maybe_result, | ||||
|                 ) | ||||
|             return default | ||||
|  | ||||
|         if maybe_result and isinstance(maybe_result, list): | ||||
|             maybe_result = [ | ||||
|                 await self._resolve_single_default(item) for item in maybe_result | ||||
|             ] | ||||
|             if ( | ||||
|                 maybe_result | ||||
|                 and self.number_selections != "*" | ||||
|                 and len(maybe_result) != self.number_selections | ||||
|             ): | ||||
|                 raise ValueError( | ||||
|                     f"[{self.name}] 'number_selections' is {self.number_selections}, " | ||||
|                     f"but last_result has a different length: {len(maybe_result)}." | ||||
|                 ) | ||||
|             return self.separator.join(maybe_result) | ||||
|         elif effective_default and isinstance(effective_default, list): | ||||
|             effective_default = [ | ||||
|                 await self._resolve_single_default(item) for item in effective_default | ||||
|             ] | ||||
|             if ( | ||||
|                 effective_default | ||||
|                 and self.number_selections != "*" | ||||
|                 and len(effective_default) != self.number_selections | ||||
|             ): | ||||
|                 raise ValueError( | ||||
|                     f"[{self.name}] 'number_selections' is {self.number_selections}, " | ||||
|                     f"but default_selection has a different length: {len(effective_default)}." | ||||
|                 ) | ||||
|             return self.separator.join(effective_default) | ||||
|         if self.inject_last_result: | ||||
|             logger.warning( | ||||
|                 "[%s] Injected last result '%s' not found in selections", | ||||
|                 self.name, | ||||
|                 maybe_result, | ||||
|             ) | ||||
|         return "" | ||||
|  | ||||
|     async def _resolve_single_default(self, maybe_result: str) -> str: | ||||
|         effective_default = "" | ||||
|         if isinstance(self.selections, dict): | ||||
|             if str(maybe_result) in self.selections: | ||||
|                 effective_default = str(maybe_result) | ||||
|             elif maybe_result in ( | ||||
|                 selection.value for selection in self.selections.values() | ||||
|             ): | ||||
|                 selection = [ | ||||
|                     key | ||||
|                     for key, sel in self.selections.items() | ||||
|                     if sel.value == maybe_result | ||||
|                 ] | ||||
|                 if selection: | ||||
|                     effective_default = selection[0] | ||||
|             elif maybe_result in ( | ||||
|                 selection.description for selection in self.selections.values() | ||||
|             ): | ||||
|                 selection = [ | ||||
|                     key | ||||
|                     for key, sel in self.selections.items() | ||||
|                     if sel.description == maybe_result | ||||
|                 ] | ||||
|                 if selection: | ||||
|                     effective_default = selection[0] | ||||
|         elif isinstance(self.selections, list): | ||||
|             if str(maybe_result).isdigit() and int(maybe_result) in range( | ||||
|                 len(self.selections) | ||||
|             ): | ||||
|                 effective_default = maybe_result | ||||
|             elif maybe_result in self.selections: | ||||
|                 effective_default = str(self.selections.index(maybe_result)) | ||||
|         return effective_default | ||||
|  | ||||
|     async def _run(self, *args, **kwargs) -> Any: | ||||
|         kwargs = self._maybe_inject_last_result(kwargs) | ||||
|         context = ExecutionContext( | ||||
| @@ -211,28 +349,7 @@ class SelectionAction(BaseAction): | ||||
|             action=self, | ||||
|         ) | ||||
|  | ||||
|         effective_default = str(self.default_selection) | ||||
|         maybe_result = str(self.last_result) | ||||
|         if isinstance(self.selections, dict): | ||||
|             if maybe_result in self.selections: | ||||
|                 effective_default = maybe_result | ||||
|             elif self.inject_last_result: | ||||
|                 logger.warning( | ||||
|                     "[%s] Injected last result '%s' not found in selections", | ||||
|                     self.name, | ||||
|                     maybe_result, | ||||
|                 ) | ||||
|         elif isinstance(self.selections, list): | ||||
|             if maybe_result.isdigit() and int(maybe_result) in range( | ||||
|                 len(self.selections) | ||||
|             ): | ||||
|                 effective_default = maybe_result | ||||
|             elif self.inject_last_result: | ||||
|                 logger.warning( | ||||
|                     "[%s] Injected last result '%s' not found in selections", | ||||
|                     self.name, | ||||
|                     maybe_result, | ||||
|                 ) | ||||
|         effective_default = await self._resolve_effective_default() | ||||
|  | ||||
|         if self.never_prompt and not effective_default: | ||||
|             raise ValueError( | ||||
| @@ -251,6 +368,9 @@ class SelectionAction(BaseAction): | ||||
|                     columns=self.columns, | ||||
|                     formatter=self.cancel_formatter, | ||||
|                 ) | ||||
|                 if effective_default is None or isinstance(effective_default, int): | ||||
|                     effective_default = "" | ||||
|  | ||||
|                 if not self.never_prompt: | ||||
|                     indices: int | list[int] = await prompt_for_index( | ||||
|                         len(self.selections), | ||||
| @@ -265,8 +385,13 @@ class SelectionAction(BaseAction): | ||||
|                         cancel_key=self.cancel_key, | ||||
|                     ) | ||||
|                 else: | ||||
|                     if effective_default: | ||||
|                     if effective_default and self.number_selections == 1: | ||||
|                         indices = int(effective_default) | ||||
|                     elif effective_default: | ||||
|                         indices = [ | ||||
|                             int(index) | ||||
|                             for index in effective_default.split(self.separator) | ||||
|                         ] | ||||
|                     else: | ||||
|                         raise ValueError( | ||||
|                             f"[{self.name}] 'never_prompt' is True but no valid " | ||||
| @@ -308,7 +433,15 @@ class SelectionAction(BaseAction): | ||||
|                         cancel_key=self.cancel_key, | ||||
|                     ) | ||||
|                 else: | ||||
|                     keys = effective_default | ||||
|                     if effective_default and self.number_selections == 1: | ||||
|                         keys = effective_default | ||||
|                     elif effective_default: | ||||
|                         keys = effective_default.split(self.separator) | ||||
|                     else: | ||||
|                         raise ValueError( | ||||
|                             f"[{self.name}] 'never_prompt' is True but no valid " | ||||
|                             "default_selection was provided." | ||||
|                         ) | ||||
|                 if keys == self.cancel_key: | ||||
|                     raise CancelSignal("User cancelled the selection.") | ||||
|  | ||||
| @@ -337,13 +470,13 @@ class SelectionAction(BaseAction): | ||||
|  | ||||
|         if isinstance(self.selections, list): | ||||
|             sub = tree.add(f"[dim]Type:[/] List[str] ({len(self.selections)} items)") | ||||
|             for i, item in enumerate(self.selections[:10]):  # limit to 10 | ||||
|             for i, item in enumerate(self.selections[:10]): | ||||
|                 sub.add(f"[dim]{i}[/]: {item}") | ||||
|             if len(self.selections) > 10: | ||||
|                 sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]") | ||||
|         elif isinstance(self.selections, dict): | ||||
|             sub = tree.add( | ||||
|                 f"[dim]Type:[/] Dict[str, (str, Any)] ({len(self.selections)} items)" | ||||
|                 f"[dim]Type:[/] Dict[str, SelectionOption] ({len(self.selections)} items)" | ||||
|             ) | ||||
|             for i, (key, option) in enumerate(list(self.selections.items())[:10]): | ||||
|                 sub.add(f"[dim]{key}[/]: {option.description}") | ||||
| @@ -353,9 +486,30 @@ class SelectionAction(BaseAction): | ||||
|             tree.add(f"[{OneColors.DARK_RED_b}]Invalid selections type[/]") | ||||
|             return | ||||
|  | ||||
|         tree.add(f"[dim]Default:[/] '{self.default_selection or self.last_result}'") | ||||
|         tree.add(f"[dim]Return:[/] {self.return_type.name.capitalize()}") | ||||
|         default = self.default_selection or self.last_result | ||||
|         if isinstance(default, list): | ||||
|             default_display = self.separator.join(str(d) for d in default) | ||||
|         else: | ||||
|             default_display = str(default or "") | ||||
|  | ||||
|         tree.add(f"[dim]Default:[/] '{default_display}'") | ||||
|  | ||||
|         return_behavior = { | ||||
|             "KEY": "selected key(s)", | ||||
|             "VALUE": "mapped value(s)", | ||||
|             "DESCRIPTION": "description(s)", | ||||
|             "ITEMS": "SelectionOption object(s)", | ||||
|             "DESCRIPTION_VALUE": "{description: value}", | ||||
|         }.get(self.return_type.name, self.return_type.name) | ||||
|  | ||||
|         tree.add( | ||||
|             f"[dim]Return:[/] {self.return_type.name.capitalize()} → {return_behavior}" | ||||
|         ) | ||||
|         tree.add(f"[dim]Prompt:[/] {'Disabled' if self.never_prompt else 'Enabled'}") | ||||
|         tree.add(f"[dim]Columns:[/] {self.columns}") | ||||
|         tree.add( | ||||
|             f"[dim]Multi-select:[/] {'Yes' if self.number_selections != 1 else 'No'}" | ||||
|         ) | ||||
|  | ||||
|         if not parent: | ||||
|             self.console.print(tree) | ||||
|   | ||||
| @@ -75,7 +75,7 @@ class ExecutionRegistry: | ||||
|     _store_by_name: dict[str, list[ExecutionContext]] = defaultdict(list) | ||||
|     _store_by_index: dict[int, ExecutionContext] = {} | ||||
|     _store_all: list[ExecutionContext] = [] | ||||
|     _console = Console(color_system="truecolor") | ||||
|     _console: Console = console | ||||
|     _index = 0 | ||||
|     _lock = Lock() | ||||
|  | ||||
| @@ -205,8 +205,8 @@ class ExecutionRegistry: | ||||
|             elif status.lower() in ["all", "success"]: | ||||
|                 final_status = f"[{OneColors.GREEN}]✅ Success" | ||||
|                 final_result = repr(ctx.result) | ||||
|                 if len(final_result) > 1000: | ||||
|                     final_result = f"{final_result[:1000]}..." | ||||
|                 if len(final_result) > 50: | ||||
|                     final_result = f"{final_result[:50]}..." | ||||
|             else: | ||||
|                 continue | ||||
|  | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| __version__ = "0.1.63" | ||||
| __version__ = "0.1.64" | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| [tool.poetry] | ||||
| name = "falyx" | ||||
| version = "0.1.63" | ||||
| version = "0.1.64" | ||||
| description = "Reliable and introspectable async CLI action framework." | ||||
| authors = ["Roland Thomas Jr <roland@rtj.dev>"] | ||||
| license = "MIT" | ||||
|   | ||||
							
								
								
									
										287
									
								
								tests/test_actions/test_selection_action.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								tests/test_actions/test_selection_action.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | ||||
| import pytest | ||||
|  | ||||
| from falyx.action.selection_action import SelectionAction | ||||
| from falyx.selection import SelectionOption | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_list_never_prompt_by_value(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=["a", "b", "c"], | ||||
|         default_selection="b", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "b" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "b" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_list_never_prompt_by_index(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=["a", "b", "c"], | ||||
|         default_selection="2", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "2" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "c" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_list_never_prompt_by_value_multi_select(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=["a", "b", "c"], | ||||
|         default_selection=["b", "c"], | ||||
|         never_prompt=True, | ||||
|         number_selections=2, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["b", "c"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["b", "c"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_list_never_prompt_by_index_multi_select(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=["a", "b", "c"], | ||||
|         default_selection=["1", "2"], | ||||
|         never_prompt=True, | ||||
|         number_selections=2, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["1", "2"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["b", "c"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_dict_never_prompt(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections={"a": "Alpha", "b": "Beta", "c": "Gamma"}, | ||||
|         default_selection="b", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "b" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "Beta" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_dict_never_prompt_by_value(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections={"a": "Alpha", "b": "Beta", "c": "Gamma"}, | ||||
|         default_selection="Beta", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "Beta" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "Beta" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_dict_never_prompt_by_key(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections={"a": "Alpha", "b": "Beta", "c": "Gamma"}, | ||||
|         default_selection="b", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "b" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "Beta" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_key(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection="c", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "c" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "Gamma Service" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_description(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection="Alpha", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "Alpha" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "Alpha Service" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_value(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection="Beta Service", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == "Beta Service" | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == "Beta Service" | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_dict_never_prompt_by_value_multi_select(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections={"a": "Alpha", "b": "Beta", "c": "Gamma"}, | ||||
|         default_selection=["Beta", "Gamma"], | ||||
|         number_selections=2, | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["Beta", "Gamma"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["Beta", "Gamma"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_dict_never_prompt_by_key_multi_select(): | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections={"a": "Alpha", "b": "Beta", "c": "Gamma"}, | ||||
|         default_selection=["a", "b"], | ||||
|         number_selections=2, | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["a", "b"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["Alpha", "Beta"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_key_multi_select(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection=["b", "c"], | ||||
|         number_selections=2, | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["b", "c"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["Beta Service", "Gamma Service"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_description_multi_select(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection=["Alpha", "Gamma"], | ||||
|         number_selections=2, | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["Alpha", "Gamma"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["Alpha Service", "Gamma Service"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_value_multi_select(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection=["Beta Service", "Alpha Service"], | ||||
|         number_selections=2, | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["Beta Service", "Alpha Service"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["Beta Service", "Alpha Service"] | ||||
|  | ||||
|  | ||||
| @pytest.mark.asyncio | ||||
| async def test_selection_prompt_map_never_prompt_by_value_wildcard(): | ||||
|     prompt_map = { | ||||
|         "a": SelectionOption(description="Alpha", value="Alpha Service"), | ||||
|         "b": SelectionOption(description="Beta", value="Beta Service"), | ||||
|         "c": SelectionOption(description="Gamma", value="Gamma Service"), | ||||
|     } | ||||
|     action = SelectionAction( | ||||
|         name="test", | ||||
|         selections=prompt_map, | ||||
|         default_selection=["Beta Service", "Alpha Service"], | ||||
|         number_selections="*", | ||||
|         never_prompt=True, | ||||
|     ) | ||||
|     assert action.never_prompt is True | ||||
|     assert action.default_selection == ["Beta Service", "Alpha Service"] | ||||
|  | ||||
|     result = await action() | ||||
|     assert result == ["Beta Service", "Alpha Service"] | ||||
		Reference in New Issue
	
	Block a user