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,
|
hooks: HookManager | None = None,
|
||||||
inject_last_result: bool = False,
|
inject_last_result: bool = False,
|
||||||
inject_into: str = "last_result",
|
inject_into: str = "last_result",
|
||||||
never_prompt: bool = False,
|
never_prompt: bool | None = None,
|
||||||
logging_hooks: bool = False,
|
logging_hooks: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -72,7 +72,7 @@ class BaseAction(ABC):
|
|||||||
self.shared_context: SharedContext | None = None
|
self.shared_context: SharedContext | None = None
|
||||||
self.inject_last_result: bool = inject_last_result
|
self.inject_last_result: bool = inject_last_result
|
||||||
self.inject_into: str = inject_into
|
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._skip_in_chain: bool = False
|
||||||
self.console: Console = console
|
self.console: Console = console
|
||||||
self.options_manager: OptionsManager | None = None
|
self.options_manager: OptionsManager | None = None
|
||||||
@ -122,7 +122,9 @@ class BaseAction(ABC):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def never_prompt(self) -> bool:
|
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(
|
def prepare(
|
||||||
self, shared_context: SharedContext, options_manager: OptionsManager | None = None
|
self, shared_context: SharedContext, options_manager: OptionsManager | None = None
|
||||||
|
@ -30,7 +30,7 @@ from falyx.themes import OneColors
|
|||||||
|
|
||||||
class SelectFileAction(BaseAction):
|
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.)
|
- file content (as text, JSON, CSV, etc.)
|
||||||
- or the file path itself.
|
- or the file path itself.
|
||||||
|
|
||||||
@ -50,6 +50,9 @@ class SelectFileAction(BaseAction):
|
|||||||
style (str): Style for the selection options.
|
style (str): Style for the selection options.
|
||||||
suffix_filter (str | None): Restrict to certain file types.
|
suffix_filter (str | None): Restrict to certain file types.
|
||||||
return_type (FileType): What to return (path, content, parsed).
|
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.
|
prompt_session (PromptSession | None): Prompt session for user input.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -217,7 +220,7 @@ class SelectFileAction(BaseAction):
|
|||||||
er.record(context)
|
er.record(context)
|
||||||
|
|
||||||
async def preview(self, parent: Tree | None = None):
|
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 = parent.add(label) if parent else Tree(label)
|
||||||
|
|
||||||
tree.add(f"[dim]Directory:[/] {str(self.directory)}")
|
tree.add(f"[dim]Directory:[/] {str(self.directory)}")
|
||||||
@ -243,6 +246,6 @@ class SelectFileAction(BaseAction):
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return (
|
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})"
|
f"suffix_filter={self.suffix_filter!r}, return_type={self.return_type})"
|
||||||
)
|
)
|
||||||
|
@ -25,11 +25,60 @@ from falyx.themes import OneColors
|
|||||||
|
|
||||||
class SelectionAction(BaseAction):
|
class SelectionAction(BaseAction):
|
||||||
"""
|
"""
|
||||||
A selection action that prompts the user to select an option from a list or
|
A Falyx Action for interactively or programmatically selecting one or more items
|
||||||
dictionary. The selected option is then returned as the result of the action.
|
from a list or dictionary of options.
|
||||||
|
|
||||||
If return_key is True, the key of the selected option is returned instead of
|
`SelectionAction` supports both `list[str]` and `dict[str, SelectionOption]`
|
||||||
the value.
|
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__(
|
def __init__(
|
||||||
@ -46,7 +95,7 @@ class SelectionAction(BaseAction):
|
|||||||
title: str = "Select an option",
|
title: str = "Select an option",
|
||||||
columns: int = 5,
|
columns: int = 5,
|
||||||
prompt_message: str = "Select > ",
|
prompt_message: str = "Select > ",
|
||||||
default_selection: str = "",
|
default_selection: str | list[str] = "",
|
||||||
number_selections: int | str = 1,
|
number_selections: int | str = 1,
|
||||||
separator: str = ",",
|
separator: str = ",",
|
||||||
allow_duplicates: bool = False,
|
allow_duplicates: bool = False,
|
||||||
@ -202,6 +251,95 @@ class SelectionAction(BaseAction):
|
|||||||
raise ValueError(f"Unsupported return type: {self.return_type}")
|
raise ValueError(f"Unsupported return type: {self.return_type}")
|
||||||
return result
|
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:
|
async def _run(self, *args, **kwargs) -> Any:
|
||||||
kwargs = self._maybe_inject_last_result(kwargs)
|
kwargs = self._maybe_inject_last_result(kwargs)
|
||||||
context = ExecutionContext(
|
context = ExecutionContext(
|
||||||
@ -211,28 +349,7 @@ class SelectionAction(BaseAction):
|
|||||||
action=self,
|
action=self,
|
||||||
)
|
)
|
||||||
|
|
||||||
effective_default = str(self.default_selection)
|
effective_default = await self._resolve_effective_default()
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.never_prompt and not effective_default:
|
if self.never_prompt and not effective_default:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -251,6 +368,9 @@ class SelectionAction(BaseAction):
|
|||||||
columns=self.columns,
|
columns=self.columns,
|
||||||
formatter=self.cancel_formatter,
|
formatter=self.cancel_formatter,
|
||||||
)
|
)
|
||||||
|
if effective_default is None or isinstance(effective_default, int):
|
||||||
|
effective_default = ""
|
||||||
|
|
||||||
if not self.never_prompt:
|
if not self.never_prompt:
|
||||||
indices: int | list[int] = await prompt_for_index(
|
indices: int | list[int] = await prompt_for_index(
|
||||||
len(self.selections),
|
len(self.selections),
|
||||||
@ -265,8 +385,13 @@ class SelectionAction(BaseAction):
|
|||||||
cancel_key=self.cancel_key,
|
cancel_key=self.cancel_key,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if effective_default:
|
if effective_default and self.number_selections == 1:
|
||||||
indices = int(effective_default)
|
indices = int(effective_default)
|
||||||
|
elif effective_default:
|
||||||
|
indices = [
|
||||||
|
int(index)
|
||||||
|
for index in effective_default.split(self.separator)
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"[{self.name}] 'never_prompt' is True but no valid "
|
f"[{self.name}] 'never_prompt' is True but no valid "
|
||||||
@ -308,7 +433,15 @@ class SelectionAction(BaseAction):
|
|||||||
cancel_key=self.cancel_key,
|
cancel_key=self.cancel_key,
|
||||||
)
|
)
|
||||||
else:
|
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:
|
if keys == self.cancel_key:
|
||||||
raise CancelSignal("User cancelled the selection.")
|
raise CancelSignal("User cancelled the selection.")
|
||||||
|
|
||||||
@ -337,13 +470,13 @@ class SelectionAction(BaseAction):
|
|||||||
|
|
||||||
if isinstance(self.selections, list):
|
if isinstance(self.selections, list):
|
||||||
sub = tree.add(f"[dim]Type:[/] List[str] ({len(self.selections)} items)")
|
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}")
|
sub.add(f"[dim]{i}[/]: {item}")
|
||||||
if len(self.selections) > 10:
|
if len(self.selections) > 10:
|
||||||
sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]")
|
sub.add(f"[dim]... ({len(self.selections) - 10} more)[/]")
|
||||||
elif isinstance(self.selections, dict):
|
elif isinstance(self.selections, dict):
|
||||||
sub = tree.add(
|
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]):
|
for i, (key, option) in enumerate(list(self.selections.items())[:10]):
|
||||||
sub.add(f"[dim]{key}[/]: {option.description}")
|
sub.add(f"[dim]{key}[/]: {option.description}")
|
||||||
@ -353,9 +486,30 @@ class SelectionAction(BaseAction):
|
|||||||
tree.add(f"[{OneColors.DARK_RED_b}]Invalid selections type[/]")
|
tree.add(f"[{OneColors.DARK_RED_b}]Invalid selections type[/]")
|
||||||
return
|
return
|
||||||
|
|
||||||
tree.add(f"[dim]Default:[/] '{self.default_selection or self.last_result}'")
|
default = self.default_selection or self.last_result
|
||||||
tree.add(f"[dim]Return:[/] {self.return_type.name.capitalize()}")
|
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]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:
|
if not parent:
|
||||||
self.console.print(tree)
|
self.console.print(tree)
|
||||||
|
@ -75,7 +75,7 @@ class ExecutionRegistry:
|
|||||||
_store_by_name: dict[str, list[ExecutionContext]] = defaultdict(list)
|
_store_by_name: dict[str, list[ExecutionContext]] = defaultdict(list)
|
||||||
_store_by_index: dict[int, ExecutionContext] = {}
|
_store_by_index: dict[int, ExecutionContext] = {}
|
||||||
_store_all: list[ExecutionContext] = []
|
_store_all: list[ExecutionContext] = []
|
||||||
_console = Console(color_system="truecolor")
|
_console: Console = console
|
||||||
_index = 0
|
_index = 0
|
||||||
_lock = Lock()
|
_lock = Lock()
|
||||||
|
|
||||||
@ -205,8 +205,8 @@ class ExecutionRegistry:
|
|||||||
elif status.lower() in ["all", "success"]:
|
elif status.lower() in ["all", "success"]:
|
||||||
final_status = f"[{OneColors.GREEN}]✅ Success"
|
final_status = f"[{OneColors.GREEN}]✅ Success"
|
||||||
final_result = repr(ctx.result)
|
final_result = repr(ctx.result)
|
||||||
if len(final_result) > 1000:
|
if len(final_result) > 50:
|
||||||
final_result = f"{final_result[:1000]}..."
|
final_result = f"{final_result[:50]}..."
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
__version__ = "0.1.63"
|
__version__ = "0.1.64"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "falyx"
|
name = "falyx"
|
||||||
version = "0.1.63"
|
version = "0.1.64"
|
||||||
description = "Reliable and introspectable async CLI action framework."
|
description = "Reliable and introspectable async CLI action framework."
|
||||||
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
|
||||||
license = "MIT"
|
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