Add ExecutionContext.signature, fix partial command matching with arguments, fix passing args to Falyx._create_context helper

This commit is contained in:
Roland Thomas Jr 2025-06-05 17:23:27 -04:00
parent ac82076511
commit b24079ea7e
Signed by: roland
GPG Key ID: 7C3C2B085A4C2872
7 changed files with 63 additions and 31 deletions

View File

@ -70,7 +70,7 @@ class ExecutionContext(BaseModel):
name: str
args: tuple = ()
kwargs: dict = {}
kwargs: dict = Field(default_factory=dict)
action: Any
result: Any | None = None
exception: Exception | None = None
@ -120,6 +120,17 @@ class ExecutionContext(BaseModel):
def status(self) -> str:
return "OK" if self.success else "ERROR"
@property
def signature(self) -> str:
"""
Returns a string representation of the action signature, including
its name and arguments.
"""
args = ", ".join(map(repr, self.args))
kwargs = ", ".join(f"{key}={value!r}" for key, value in self.kwargs.items())
signature = ", ".join(filter(None, [args, kwargs]))
return f"{self.name} ({signature})"
def as_dict(self) -> dict:
return {
"name": self.name,

View File

@ -8,7 +8,7 @@ from falyx.logger import logger
def log_before(context: ExecutionContext):
"""Log the start of an action."""
args = ", ".join(map(repr, context.args))
kwargs = ", ".join(f"{k}={v!r}" for k, v in context.kwargs.items())
kwargs = ", ".join(f"{key}={value!r}" for key, value in context.kwargs.items())
signature = ", ".join(filter(None, [args, kwargs]))
logger.info("[%s] Starting -> %s(%s)", context.name, context.action, signature)

View File

@ -30,7 +30,7 @@ from __future__ import annotations
from collections import defaultdict
from datetime import datetime
from threading import Lock
from typing import Any, Literal
from typing import Literal
from rich import box
from rich.console import Console
@ -111,8 +111,8 @@ class ExecutionRegistry:
def summary(
cls,
name: str = "",
index: int = -1,
result: int = -1,
index: int | None = None,
result: int | None = None,
clear: bool = False,
last_result: bool = False,
status: Literal["all", "success", "error"] = "all",
@ -138,7 +138,7 @@ class ExecutionRegistry:
)
return
if result and result >= 0:
if result is not None and result >= 0:
try:
result_context = cls._store_by_index[result]
except KeyError:
@ -146,6 +146,10 @@ class ExecutionRegistry:
f"[{OneColors.DARK_RED}]❌ No execution found for index {index}."
)
return
cls._console.print(f"{result_context.signature}:")
if result_context.exception:
cls._console.print(result_context.exception)
else:
cls._console.print(result_context.result)
return
@ -157,9 +161,10 @@ class ExecutionRegistry:
)
return
title = f"📊 Execution History for '{contexts[0].name}'"
elif index and index >= 0:
elif index is not None and index >= 0:
try:
contexts = [cls._store_by_index[index]]
print(contexts)
except KeyError:
cls._console.print(
f"[{OneColors.DARK_RED}]❌ No execution found for index {index}."

View File

@ -796,7 +796,12 @@ class Falyx:
def table(self) -> Table:
"""Creates or returns a custom table to display the menu commands."""
if callable(self.custom_table):
return self.custom_table(self)
custom_table = self.custom_table(self)
if not isinstance(custom_table, Table):
raise FalyxError(
"custom_table must return an instance of rich.table.Table."
)
return custom_table
elif isinstance(self.custom_table, Table):
return self.custom_table
else:
@ -834,21 +839,31 @@ class Falyx:
choice = choice.upper()
name_map = self._name_map
run_command = None
if name_map.get(choice):
run_command = name_map[choice]
else:
prefix_matches = [
cmd for key, cmd in name_map.items() if key.startswith(choice)
]
if len(prefix_matches) == 1:
run_command = prefix_matches[0]
if run_command:
if not from_validate:
logger.info("Command '%s' selected.", choice)
logger.info("Command '%s' selected.", run_command.key)
if is_preview:
return True, name_map[choice], args, kwargs
return True, run_command, args, kwargs
elif self.mode in {FalyxMode.RUN, FalyxMode.RUN_ALL, FalyxMode.PREVIEW}:
return False, name_map[choice], args, kwargs
return False, run_command, args, kwargs
try:
args, kwargs = await name_map[choice].parse_args(
input_args, from_validate
)
args, kwargs = await run_command.parse_args(input_args, from_validate)
except (CommandArgumentError, Exception) as error:
if not from_validate:
name_map[choice].show_help()
self.console.print(f"[{OneColors.DARK_RED}]❌ [{choice}]: {error}")
run_command.show_help()
self.console.print(
f"[{OneColors.DARK_RED}]❌ [{run_command.key}]: {error}"
)
else:
raise ValidationError(
message=str(error), cursor_position=len(raw_choices)
@ -856,11 +871,7 @@ class Falyx:
return is_preview, None, args, kwargs
except HelpSignal:
return True, None, args, kwargs
return is_preview, name_map[choice], args, kwargs
prefix_matches = [cmd for key, cmd in name_map.items() if key.startswith(choice)]
if len(prefix_matches) == 1:
return is_preview, prefix_matches[0], args, kwargs
return is_preview, run_command, args, kwargs
fuzzy_matches = get_close_matches(choice, list(name_map.keys()), n=3, cutoff=0.7)
if fuzzy_matches:
@ -890,12 +901,14 @@ class Falyx:
)
return is_preview, None, args, kwargs
def _create_context(self, selected_command: Command) -> ExecutionContext:
"""Creates a context dictionary for the selected command."""
def _create_context(
self, selected_command: Command, args: tuple, kwargs: dict[str, Any]
) -> ExecutionContext:
"""Creates an ExecutionContext object for the selected command."""
return ExecutionContext(
name=selected_command.description,
args=tuple(),
kwargs={},
args=args,
kwargs=kwargs,
action=selected_command,
)
@ -929,7 +942,7 @@ class Falyx:
logger.info("Back selected: exiting %s", self.get_title())
return False
context = self._create_context(selected_command)
context = self._create_context(selected_command, args, kwargs)
context.start_timer()
try:
await self.hooks.trigger(HookType.BEFORE, context)
@ -974,7 +987,7 @@ class Falyx:
selected_command.description,
)
context = self._create_context(selected_command)
context = self._create_context(selected_command, args, kwargs)
context.start_timer()
try:
await self.hooks.trigger(HookType.BEFORE, context)

View File

@ -629,7 +629,10 @@ class CommandArgumentParser:
consumed_positional_indicies.add(j)
if i < len(args):
raise CommandArgumentError(f"Unexpected positional argument: {args[i:]}")
plural = "s" if len(args[i:]) > 1 else ""
raise CommandArgumentError(
f"Unexpected positional argument{plural}: {', '.join(args[i:])}"
)
return i

View File

@ -1 +1 @@
__version__ = "0.1.49"
__version__ = "0.1.50"

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "falyx"
version = "0.1.49"
version = "0.1.50"
description = "Reliable and introspectable async CLI action framework."
authors = ["Roland Thomas Jr <roland@rtj.dev>"]
license = "MIT"