From ad803e01be938b5c6e06e463bfef6dcaeacab82c Mon Sep 17 00:00:00 2001 From: Roland Thomas Date: Thu, 8 May 2025 22:10:05 -0400 Subject: [PATCH] Add register_teardown, Rename session -> prompt_session, Add sets, tuples to SelectionAction --- falyx/action.py | 18 ++++++++++++++++++ falyx/falyx.py | 24 ++++++++++++------------ falyx/http_action.py | 5 ++++- falyx/init.py | 6 ++++-- falyx/menu_action.py | 6 ++---- falyx/selection.py | 34 +++++++++++++++++----------------- falyx/selection_action.py | 18 ++++++++++-------- falyx/version.py | 2 +- pyproject.toml | 2 +- 9 files changed, 69 insertions(+), 46 deletions(-) diff --git a/falyx/action.py b/falyx/action.py index 3f44562..7b3b67c 100644 --- a/falyx/action.py +++ b/falyx/action.py @@ -448,6 +448,8 @@ class ChainedAction(BaseAction, ActionListMixin): if self.actions and self.auto_inject and not action.inject_last_result: action.inject_last_result = True super().add_action(action) + if hasattr(action, "register_teardown") and callable(action.register_teardown): + action.register_teardown(self.hooks) async def _run(self, *args, **kwargs) -> list[Any]: if not self.actions: @@ -617,6 +619,22 @@ class ActionGroup(BaseAction, ActionListMixin): if actions: self.set_actions(actions) + def _wrap_if_needed(self, action: BaseAction | Any) -> BaseAction: + if isinstance(action, BaseAction): + return action + elif callable(action): + return Action(name=action.__name__, action=action) + else: + raise TypeError( + f"ActionGroup only accepts BaseAction or callable, got {type(action).__name__}" + ) + + def add_action(self, action: BaseAction | Any) -> None: + action = self._wrap_if_needed(action) + super().add_action(action) + if hasattr(action, "register_teardown") and callable(action.register_teardown): + action.register_teardown(self.hooks) + async def _run(self, *args, **kwargs) -> list[tuple[str, Any]]: shared_context = SharedContext(name=self.name, is_parallel=True) if self.shared_context: diff --git a/falyx/falyx.py b/falyx/falyx.py index ca5b2b3..57aa25a 100644 --- a/falyx/falyx.py +++ b/falyx/falyx.py @@ -148,7 +148,7 @@ class Falyx: self.render_menu: Callable[["Falyx"], None] | None = render_menu self.custom_table: Callable[["Falyx"], Table] | Table | None = custom_table self.validate_options(cli_args, options) - self._session: PromptSession | None = None + self._prompt_session: PromptSession | None = None def validate_options( self, @@ -337,11 +337,11 @@ class Falyx: move_cursor_to_end=True, ) - def _invalidate_session_cache(self): - """Forces the session to be recreated on the next access.""" - if hasattr(self, "session"): - del self.session - self._session = None + def _invalidate_prompt_session_cache(self): + """Forces the prompt session to be recreated on the next access.""" + if hasattr(self, "prompt_session"): + del self.prompt_session + self._prompt_session = None def add_help_command(self): """Adds a help command to the menu if it doesn't already exist.""" @@ -375,7 +375,7 @@ class Falyx: raise FalyxError( "Bottom bar must be a string, callable, or BottomBar instance." ) - self._invalidate_session_cache() + self._invalidate_prompt_session_cache() def _get_bottom_bar_render(self) -> Callable[[], Any] | str | None: """Returns the bottom bar for the menu.""" @@ -390,10 +390,10 @@ class Falyx: return None @cached_property - def session(self) -> PromptSession: + def prompt_session(self) -> PromptSession: """Returns the prompt session for the menu.""" - if self._session is None: - self._session = PromptSession( + if self._prompt_session is None: + self._prompt_session = PromptSession( message=self.prompt, multiline=False, completer=self._get_completer(), @@ -402,7 +402,7 @@ class Falyx: bottom_toolbar=self._get_bottom_bar_render(), key_bindings=self.key_bindings, ) - return self._session + return self._prompt_session def register_all_hooks(self, hook_type: HookType, hooks: Hook | list[Hook]) -> None: """Registers hooks for all commands in the menu and actions recursively.""" @@ -717,7 +717,7 @@ class Falyx: async def process_command(self) -> bool: """Processes the action of the selected command.""" - choice = await self.session.prompt_async() + choice = await self.prompt_session.prompt_async() selected_command = self.get_command(choice) if not selected_command: logger.info(f"Invalid command '{choice}'.") diff --git a/falyx/http_action.py b/falyx/http_action.py index 2cbf9c5..d5beac0 100644 --- a/falyx/http_action.py +++ b/falyx/http_action.py @@ -15,6 +15,7 @@ from rich.tree import Tree from falyx.action import Action from falyx.context import ExecutionContext, SharedContext +from falyx.hook_manager import HookManager, HookType from falyx.themes.colors import OneColors from falyx.utils import logger @@ -97,7 +98,6 @@ class HTTPAction(Action): ) async def _request(self, *args, **kwargs) -> dict[str, Any]: - # TODO: Add check for HOOK registration if self.shared_context: context: SharedContext = self.shared_context session = context.get("http_session") @@ -128,6 +128,9 @@ class HTTPAction(Action): if not self.shared_context: await session.close() + def register_teardown(self, hooks: HookManager): + hooks.register(HookType.ON_TEARDOWN, close_shared_http_session) + async def preview(self, parent: Tree | None = None): label = [ f"[{OneColors.CYAN_b}]๐ŸŒ HTTPAction[/] '{self.name}'", diff --git a/falyx/init.py b/falyx/init.py index ba30288..173f9df 100644 --- a/falyx/init.py +++ b/falyx/init.py @@ -33,8 +33,10 @@ async def cleanup(): """ GLOBAL_CONFIG = """\ -async def cleanup(): - print("๐Ÿงน Cleaning temp files...") +- key: C + description: Cleanup temp files + action: tasks.cleanup + aliases: [clean, cleanup] """ console = Console(color_system="auto") diff --git a/falyx/menu_action.py b/falyx/menu_action.py index caa9c49..3db4c97 100644 --- a/falyx/menu_action.py +++ b/falyx/menu_action.py @@ -168,15 +168,13 @@ class MenuAction(BaseAction): await self.hooks.trigger(HookType.BEFORE, context) key = effective_default if not self.never_prompt: - console = self.console - session = self.prompt_session table = self._build_table() key = await prompt_for_selection( self.menu_options.keys(), table, default_selection=self.default_selection, - console=console, - session=session, + console=self.console, + prompt_session=self.prompt_session, prompt_message=self.prompt_message, show_table=self.show_table, ) diff --git a/falyx/selection.py b/falyx/selection.py index f1c81da..db32d9a 100644 --- a/falyx/selection.py +++ b/falyx/selection.py @@ -203,17 +203,17 @@ async def prompt_for_index( min_index: int = 0, default_selection: str = "", console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, prompt_message: str = "Select an option > ", show_table: bool = True, ): - session = session or PromptSession() + prompt_session = prompt_session or PromptSession() console = console or Console(color_system="auto") if show_table: console.print(table) - selection = await session.prompt_async( + selection = await prompt_session.prompt_async( message=prompt_message, validator=int_range_validator(min_index, max_index), default=default_selection, @@ -226,18 +226,18 @@ async def prompt_for_selection( table: Table, default_selection: str = "", console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, prompt_message: str = "Select an option > ", show_table: bool = True, ) -> str: """Prompt the user to select a key from a set of options. Return the selected key.""" - session = session or PromptSession() + prompt_session = prompt_session or PromptSession() console = console or Console(color_system="auto") if show_table: console.print(table, justify="center") - selected = await session.prompt_async( + selected = await prompt_session.prompt_async( message=prompt_message, validator=key_validator(keys), default=default_selection, @@ -250,7 +250,7 @@ async def select_value_from_list( title: str, selections: Sequence[str], console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, prompt_message: str = "Select an option > ", default_selection: str = "", columns: int = 4, @@ -283,7 +283,7 @@ async def select_value_from_list( caption_style, highlight, ) - session = session or PromptSession() + prompt_session = prompt_session or PromptSession() console = console or Console(color_system="auto") selection_index = await prompt_for_index( @@ -291,7 +291,7 @@ async def select_value_from_list( table, default_selection=default_selection, console=console, - session=session, + prompt_session=prompt_session, prompt_message=prompt_message, ) @@ -302,12 +302,12 @@ async def select_key_from_dict( selections: dict[str, SelectionOption], table: Table, console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, prompt_message: str = "Select an option > ", default_selection: str = "", ) -> Any: """Prompt for a key from a dict, returns the key.""" - session = session or PromptSession() + prompt_session = prompt_session or PromptSession() console = console or Console(color_system="auto") console.print(table) @@ -317,7 +317,7 @@ async def select_key_from_dict( table, default_selection=default_selection, console=console, - session=session, + prompt_session=prompt_session, prompt_message=prompt_message, ) @@ -326,12 +326,12 @@ async def select_value_from_dict( selections: dict[str, SelectionOption], table: Table, console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, prompt_message: str = "Select an option > ", default_selection: str = "", ) -> Any: """Prompt for a key from a dict, but return the value.""" - session = session or PromptSession() + prompt_session = prompt_session or PromptSession() console = console or Console(color_system="auto") console.print(table) @@ -341,7 +341,7 @@ async def select_value_from_dict( table, default_selection=default_selection, console=console, - session=session, + prompt_session=prompt_session, prompt_message=prompt_message, ) @@ -352,7 +352,7 @@ async def get_selection_from_dict_menu( title: str, selections: dict[str, SelectionOption], console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, prompt_message: str = "Select an option > ", default_selection: str = "", ): @@ -366,7 +366,7 @@ async def get_selection_from_dict_menu( selections, table, console, - session, + prompt_session, prompt_message, default_selection, ) diff --git a/falyx/selection_action.py b/falyx/selection_action.py index 3b2611a..906990d 100644 --- a/falyx/selection_action.py +++ b/falyx/selection_action.py @@ -26,7 +26,7 @@ class SelectionAction(BaseAction): def __init__( self, name: str, - selections: list[str] | dict[str, SelectionOption], + selections: list[str] | set[str] | tuple[str, ...] | dict[str, SelectionOption], *, title: str = "Select an option", columns: int = 2, @@ -36,7 +36,7 @@ class SelectionAction(BaseAction): inject_last_result_as: str = "last_result", return_key: bool = False, console: Console | None = None, - session: PromptSession | None = None, + prompt_session: PromptSession | None = None, never_prompt: bool = False, show_table: bool = True, ): @@ -51,7 +51,7 @@ class SelectionAction(BaseAction): self.title = title self.columns = columns self.console = console or Console(color_system="auto") - self.session = session or PromptSession() + self.prompt_session = prompt_session or PromptSession() self.default_selection = default_selection self.prompt_message = prompt_message self.show_table = show_table @@ -61,9 +61,11 @@ class SelectionAction(BaseAction): return self._selections @selections.setter - def selections(self, value: list[str] | dict[str, SelectionOption]): - if isinstance(value, list): - self._selections: list[str] | CaseInsensitiveDict = value + def selections( + self, value: list[str] | set[str] | tuple[str, ...] | dict[str, SelectionOption] + ): + if isinstance(value, (list, tuple, set)): + self._selections: list[str] | CaseInsensitiveDict = list(value) elif isinstance(value, dict): cid = CaseInsensitiveDict() cid.update(value) @@ -123,7 +125,7 @@ class SelectionAction(BaseAction): table, default_selection=effective_default, console=self.console, - session=self.session, + prompt_session=self.prompt_session, prompt_message=self.prompt_message, show_table=self.show_table, ) @@ -140,7 +142,7 @@ class SelectionAction(BaseAction): table, default_selection=effective_default, console=self.console, - session=self.session, + prompt_session=self.prompt_session, prompt_message=self.prompt_message, show_table=self.show_table, ) diff --git a/falyx/version.py b/falyx/version.py index 08f934f..d38c350 100644 --- a/falyx/version.py +++ b/falyx/version.py @@ -1 +1 @@ -__version__ = "0.1.18" +__version__ = "0.1.19" diff --git a/pyproject.toml b/pyproject.toml index af476d1..09812af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "falyx" -version = "0.1.18" +version = "0.1.19" description = "Reliable and introspectable async CLI action framework." authors = ["Roland Thomas Jr "] license = "MIT"