Make auto_args default fallback, integrate io_actions with argument parsing
This commit is contained in:
parent
4fa6e3bf1f
commit
3c0a81359c
|
@ -24,7 +24,6 @@ cmd = Command(
|
||||||
key="G",
|
key="G",
|
||||||
description="Greet someone with multiple variations.",
|
description="Greet someone with multiple variations.",
|
||||||
action=group,
|
action=group,
|
||||||
auto_args=True,
|
|
||||||
arg_metadata={
|
arg_metadata={
|
||||||
"name": {
|
"name": {
|
||||||
"help": "The name of the person to greet.",
|
"help": "The name of the person to greet.",
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from falyx import Action, Falyx
|
from falyx import Action, ChainedAction, Falyx
|
||||||
|
from falyx.utils import setup_logging
|
||||||
|
|
||||||
|
setup_logging()
|
||||||
|
|
||||||
|
|
||||||
async def deploy(service: str, region: str = "us-east-1", verbose: bool = False):
|
async def deploy(service: str, region: str = "us-east-1", verbose: bool = False) -> str:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"Deploying {service} to {region}...")
|
print(f"Deploying {service} to {region}...")
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
if verbose:
|
if verbose:
|
||||||
print(f"{service} deployed successfully!")
|
print(f"{service} deployed successfully!")
|
||||||
|
return f"{service} deployed to {region}"
|
||||||
|
|
||||||
|
|
||||||
flx = Falyx("Deployment CLI")
|
flx = Falyx("Deployment CLI")
|
||||||
|
@ -21,7 +25,6 @@ flx.add_command(
|
||||||
name="deploy_service",
|
name="deploy_service",
|
||||||
action=deploy,
|
action=deploy,
|
||||||
),
|
),
|
||||||
auto_args=True,
|
|
||||||
arg_metadata={
|
arg_metadata={
|
||||||
"service": "Service name",
|
"service": "Service name",
|
||||||
"region": {"help": "Deployment region", "choices": ["us-east-1", "us-west-2"]},
|
"region": {"help": "Deployment region", "choices": ["us-east-1", "us-west-2"]},
|
||||||
|
@ -29,4 +32,23 @@ flx.add_command(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
deploy_chain = ChainedAction(
|
||||||
|
name="DeployChain",
|
||||||
|
actions=[
|
||||||
|
Action(name="deploy_service", action=deploy),
|
||||||
|
Action(
|
||||||
|
name="notify",
|
||||||
|
action=lambda last_result: print(f"Notification: {last_result}"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
auto_inject=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
flx.add_command(
|
||||||
|
key="N",
|
||||||
|
aliases=["notify"],
|
||||||
|
description="Deploy a service and notify.",
|
||||||
|
action=deploy_chain,
|
||||||
|
)
|
||||||
|
|
||||||
asyncio.run(flx.run())
|
asyncio.run(flx.run())
|
||||||
|
|
|
@ -47,6 +47,7 @@ from falyx.execution_registry import ExecutionRegistry as er
|
||||||
from falyx.hook_manager import Hook, HookManager, HookType
|
from falyx.hook_manager import Hook, HookManager, HookType
|
||||||
from falyx.logger import logger
|
from falyx.logger import logger
|
||||||
from falyx.options_manager import OptionsManager
|
from falyx.options_manager import OptionsManager
|
||||||
|
from falyx.parsers.utils import same_argument_definitions
|
||||||
from falyx.retry import RetryHandler, RetryPolicy
|
from falyx.retry import RetryHandler, RetryPolicy
|
||||||
from falyx.themes import OneColors
|
from falyx.themes import OneColors
|
||||||
from falyx.utils import ensure_async
|
from falyx.utils import ensure_async
|
||||||
|
@ -101,6 +102,14 @@ class BaseAction(ABC):
|
||||||
async def preview(self, parent: Tree | None = None):
|
async def preview(self, parent: Tree | None = None):
|
||||||
raise NotImplementedError("preview must be implemented by subclasses")
|
raise NotImplementedError("preview must be implemented by subclasses")
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_infer_target(self) -> Callable[..., Any] | None:
|
||||||
|
"""
|
||||||
|
Returns the callable to be used for argument inference.
|
||||||
|
By default, it returns None.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("get_infer_target must be implemented by subclasses")
|
||||||
|
|
||||||
def set_options_manager(self, options_manager: OptionsManager) -> None:
|
def set_options_manager(self, options_manager: OptionsManager) -> None:
|
||||||
self.options_manager = options_manager
|
self.options_manager = options_manager
|
||||||
|
|
||||||
|
@ -246,6 +255,13 @@ class Action(BaseAction):
|
||||||
if policy.enabled:
|
if policy.enabled:
|
||||||
self.enable_retry()
|
self.enable_retry()
|
||||||
|
|
||||||
|
def get_infer_target(self) -> Callable[..., Any]:
|
||||||
|
"""
|
||||||
|
Returns the callable to be used for argument inference.
|
||||||
|
By default, it returns the action itself.
|
||||||
|
"""
|
||||||
|
return self.action
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs) -> Any:
|
async def _run(self, *args, **kwargs) -> Any:
|
||||||
combined_args = args + self.args
|
combined_args = args + self.args
|
||||||
combined_kwargs = self._maybe_inject_last_result({**self.kwargs, **kwargs})
|
combined_kwargs = self._maybe_inject_last_result({**self.kwargs, **kwargs})
|
||||||
|
@ -477,6 +493,14 @@ class ChainedAction(BaseAction, ActionListMixin):
|
||||||
if hasattr(action, "register_teardown") and callable(action.register_teardown):
|
if hasattr(action, "register_teardown") and callable(action.register_teardown):
|
||||||
action.register_teardown(self.hooks)
|
action.register_teardown(self.hooks)
|
||||||
|
|
||||||
|
def get_infer_target(self) -> Callable[..., Any] | None:
|
||||||
|
if self.actions:
|
||||||
|
return self.actions[0].get_infer_target()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _clear_args(self):
|
||||||
|
return (), {}
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs) -> list[Any]:
|
async def _run(self, *args, **kwargs) -> list[Any]:
|
||||||
if not self.actions:
|
if not self.actions:
|
||||||
raise EmptyChainError(f"[{self.name}] No actions to execute.")
|
raise EmptyChainError(f"[{self.name}] No actions to execute.")
|
||||||
|
@ -505,12 +529,8 @@ class ChainedAction(BaseAction, ActionListMixin):
|
||||||
continue
|
continue
|
||||||
shared_context.current_index = index
|
shared_context.current_index = index
|
||||||
prepared = action.prepare(shared_context, self.options_manager)
|
prepared = action.prepare(shared_context, self.options_manager)
|
||||||
last_result = shared_context.last_result()
|
|
||||||
try:
|
try:
|
||||||
if self.requires_io_injection() and last_result is not None:
|
result = await prepared(*args, **updated_kwargs)
|
||||||
result = await prepared(**{prepared.inject_into: last_result})
|
|
||||||
else:
|
|
||||||
result = await prepared(*args, **updated_kwargs)
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
if index + 1 < len(self.actions) and isinstance(
|
if index + 1 < len(self.actions) and isinstance(
|
||||||
self.actions[index + 1], FallbackAction
|
self.actions[index + 1], FallbackAction
|
||||||
|
@ -529,6 +549,7 @@ class ChainedAction(BaseAction, ActionListMixin):
|
||||||
fallback._skip_in_chain = True
|
fallback._skip_in_chain = True
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
args, updated_kwargs = self._clear_args()
|
||||||
shared_context.add_result(result)
|
shared_context.add_result(result)
|
||||||
context.extra["results"].append(result)
|
context.extra["results"].append(result)
|
||||||
context.extra["rollback_stack"].append(prepared)
|
context.extra["rollback_stack"].append(prepared)
|
||||||
|
@ -669,6 +690,16 @@ class ActionGroup(BaseAction, ActionListMixin):
|
||||||
if hasattr(action, "register_teardown") and callable(action.register_teardown):
|
if hasattr(action, "register_teardown") and callable(action.register_teardown):
|
||||||
action.register_teardown(self.hooks)
|
action.register_teardown(self.hooks)
|
||||||
|
|
||||||
|
def get_infer_target(self) -> Callable[..., Any] | None:
|
||||||
|
arg_defs = same_argument_definitions(self.actions)
|
||||||
|
if arg_defs:
|
||||||
|
return self.actions[0].get_infer_target()
|
||||||
|
logger.debug(
|
||||||
|
"[%s] auto_args disabled: mismatched ActionGroup arguments",
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs) -> list[tuple[str, Any]]:
|
async def _run(self, *args, **kwargs) -> list[tuple[str, Any]]:
|
||||||
shared_context = SharedContext(name=self.name, action=self, is_parallel=True)
|
shared_context = SharedContext(name=self.name, action=self, is_parallel=True)
|
||||||
if self.shared_context:
|
if self.shared_context:
|
||||||
|
@ -787,8 +818,11 @@ class ProcessAction(BaseAction):
|
||||||
self.executor = executor or ProcessPoolExecutor()
|
self.executor = executor or ProcessPoolExecutor()
|
||||||
self.is_retryable = True
|
self.is_retryable = True
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs):
|
def get_infer_target(self) -> Callable[..., Any] | None:
|
||||||
if self.inject_last_result:
|
return self.action
|
||||||
|
|
||||||
|
async def _run(self, *args, **kwargs) -> Any:
|
||||||
|
if self.inject_last_result and self.shared_context:
|
||||||
last_result = self.shared_context.last_result()
|
last_result = self.shared_context.last_result()
|
||||||
if not self._validate_pickleable(last_result):
|
if not self._validate_pickleable(last_result):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
|
||||||
"""action_factory.py"""
|
"""action_factory.py"""
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
|
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
|
|
||||||
|
@ -55,6 +55,9 @@ class ActionFactoryAction(BaseAction):
|
||||||
def factory(self, value: ActionFactoryProtocol):
|
def factory(self, value: ActionFactoryProtocol):
|
||||||
self._factory = ensure_async(value)
|
self._factory = ensure_async(value)
|
||||||
|
|
||||||
|
def get_infer_target(self) -> Callable[..., Any]:
|
||||||
|
return self.factory
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs) -> Any:
|
async def _run(self, *args, **kwargs) -> Any:
|
||||||
updated_kwargs = self._maybe_inject_last_result(kwargs)
|
updated_kwargs = self._maybe_inject_last_result(kwargs)
|
||||||
context = ExecutionContext(
|
context = ExecutionContext(
|
||||||
|
|
|
@ -19,7 +19,7 @@ import asyncio
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Any
|
from typing import Any, Callable
|
||||||
|
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
|
|
||||||
|
@ -81,15 +81,15 @@ class BaseIOAction(BaseAction):
|
||||||
def to_output(self, result: Any) -> str | bytes:
|
def to_output(self, result: Any) -> str | bytes:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
async def _resolve_input(self, kwargs: dict[str, Any]) -> str | bytes:
|
async def _resolve_input(
|
||||||
last_result = kwargs.pop(self.inject_into, None)
|
self, args: tuple[Any], kwargs: dict[str, Any]
|
||||||
|
) -> str | bytes:
|
||||||
data = await self._read_stdin()
|
data = await self._read_stdin()
|
||||||
if data:
|
if data:
|
||||||
return self.from_input(data)
|
return self.from_input(data)
|
||||||
|
|
||||||
if last_result is not None:
|
if len(args) == 1:
|
||||||
return last_result
|
return self.from_input(args[0])
|
||||||
|
|
||||||
if self.inject_last_result and self.shared_context:
|
if self.inject_last_result and self.shared_context:
|
||||||
return self.shared_context.last_result()
|
return self.shared_context.last_result()
|
||||||
|
@ -99,6 +99,9 @@ class BaseIOAction(BaseAction):
|
||||||
)
|
)
|
||||||
raise FalyxError("No input provided and no last result to inject.")
|
raise FalyxError("No input provided and no last result to inject.")
|
||||||
|
|
||||||
|
def get_infer_target(self) -> Callable[..., Any] | None:
|
||||||
|
return None
|
||||||
|
|
||||||
async def __call__(self, *args, **kwargs):
|
async def __call__(self, *args, **kwargs):
|
||||||
context = ExecutionContext(
|
context = ExecutionContext(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
|
@ -117,8 +120,8 @@ class BaseIOAction(BaseAction):
|
||||||
pass
|
pass
|
||||||
result = getattr(self, "_last_result", None)
|
result = getattr(self, "_last_result", None)
|
||||||
else:
|
else:
|
||||||
parsed_input = await self._resolve_input(kwargs)
|
parsed_input = await self._resolve_input(args, kwargs)
|
||||||
result = await self._run(parsed_input, *args, **kwargs)
|
result = await self._run(parsed_input)
|
||||||
output = self.to_output(result)
|
output = self.to_output(result)
|
||||||
await self._write_stdout(output)
|
await self._write_stdout(output)
|
||||||
context.result = result
|
context.result = result
|
||||||
|
@ -220,6 +223,11 @@ class ShellAction(BaseIOAction):
|
||||||
)
|
)
|
||||||
return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip()
|
return raw.strip() if isinstance(raw, str) else raw.decode("utf-8").strip()
|
||||||
|
|
||||||
|
def get_infer_target(self) -> Callable[..., Any] | None:
|
||||||
|
if sys.stdin.isatty():
|
||||||
|
return self._run
|
||||||
|
return None
|
||||||
|
|
||||||
async def _run(self, parsed_input: str) -> str:
|
async def _run(self, parsed_input: str) -> str:
|
||||||
# Replace placeholder in template, or use raw input as full command
|
# Replace placeholder in template, or use raw input as full command
|
||||||
command = self.command_template.format(parsed_input)
|
command = self.command_template.format(parsed_input)
|
||||||
|
|
|
@ -73,6 +73,9 @@ class MenuAction(BaseAction):
|
||||||
table.add_row(*row)
|
table.add_row(*row)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
|
def get_infer_target(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
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(
|
||||||
|
|
|
@ -121,6 +121,9 @@ class SelectFileAction(BaseAction):
|
||||||
logger.warning("[ERROR] Failed to parse %s: %s", file.name, error)
|
logger.warning("[ERROR] Failed to parse %s: %s", file.name, error)
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
def get_infer_target(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs) -> Any:
|
async def _run(self, *args, **kwargs) -> Any:
|
||||||
context = ExecutionContext(name=self.name, args=args, kwargs=kwargs, action=self)
|
context = ExecutionContext(name=self.name, args=args, kwargs=kwargs, action=self)
|
||||||
context.start_timer()
|
context.start_timer()
|
||||||
|
|
|
@ -85,6 +85,9 @@ class SelectionAction(BaseAction):
|
||||||
f"got {type(value).__name__}"
|
f"got {type(value).__name__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_infer_target(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
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(
|
||||||
|
|
|
@ -43,6 +43,9 @@ class UserInputAction(BaseAction):
|
||||||
self.console = console or Console(color_system="auto")
|
self.console = console or Console(color_system="auto")
|
||||||
self.prompt_session = prompt_session or PromptSession()
|
self.prompt_session = prompt_session or PromptSession()
|
||||||
|
|
||||||
|
def get_infer_target(self) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
async def _run(self, *args, **kwargs) -> str:
|
async def _run(self, *args, **kwargs) -> str:
|
||||||
context = ExecutionContext(
|
context = ExecutionContext(
|
||||||
name=self.name,
|
name=self.name,
|
||||||
|
|
|
@ -27,13 +27,7 @@ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, field_validator
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
|
|
||||||
from falyx.action.action import (
|
from falyx.action.action import Action, ActionGroup, BaseAction, ChainedAction
|
||||||
Action,
|
|
||||||
ActionGroup,
|
|
||||||
BaseAction,
|
|
||||||
ChainedAction,
|
|
||||||
ProcessAction,
|
|
||||||
)
|
|
||||||
from falyx.action.io_action import BaseIOAction
|
from falyx.action.io_action import BaseIOAction
|
||||||
from falyx.context import ExecutionContext
|
from falyx.context import ExecutionContext
|
||||||
from falyx.debug import register_debug_hooks
|
from falyx.debug import register_debug_hooks
|
||||||
|
@ -41,11 +35,8 @@ from falyx.execution_registry import ExecutionRegistry as er
|
||||||
from falyx.hook_manager import HookManager, HookType
|
from falyx.hook_manager import HookManager, HookType
|
||||||
from falyx.logger import logger
|
from falyx.logger import logger
|
||||||
from falyx.options_manager import OptionsManager
|
from falyx.options_manager import OptionsManager
|
||||||
from falyx.parsers import (
|
from falyx.parsers.argparse import CommandArgumentParser
|
||||||
CommandArgumentParser,
|
from falyx.parsers.signature import infer_args_from_func
|
||||||
infer_args_from_func,
|
|
||||||
same_argument_definitions,
|
|
||||||
)
|
|
||||||
from falyx.prompt_utils import confirm_async, should_prompt_user
|
from falyx.prompt_utils import confirm_async, should_prompt_user
|
||||||
from falyx.protocols import ArgParserProtocol
|
from falyx.protocols import ArgParserProtocol
|
||||||
from falyx.retry import RetryPolicy
|
from falyx.retry import RetryPolicy
|
||||||
|
@ -116,7 +107,7 @@ class Command(BaseModel):
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
description: str
|
description: str
|
||||||
action: BaseAction | Callable[[Any], Any]
|
action: BaseAction | Callable[..., Any]
|
||||||
args: tuple = ()
|
args: tuple = ()
|
||||||
kwargs: dict[str, Any] = Field(default_factory=dict)
|
kwargs: dict[str, Any] = Field(default_factory=dict)
|
||||||
hidden: bool = False
|
hidden: bool = False
|
||||||
|
@ -145,7 +136,7 @@ class Command(BaseModel):
|
||||||
argument_config: Callable[[CommandArgumentParser], None] | None = None
|
argument_config: Callable[[CommandArgumentParser], None] | None = None
|
||||||
custom_parser: ArgParserProtocol | None = None
|
custom_parser: ArgParserProtocol | None = None
|
||||||
custom_help: Callable[[], str | None] | None = None
|
custom_help: Callable[[], str | None] | None = None
|
||||||
auto_args: bool = False
|
auto_args: bool = True
|
||||||
arg_metadata: dict[str, str | dict[str, Any]] = Field(default_factory=dict)
|
arg_metadata: dict[str, str | dict[str, Any]] = Field(default_factory=dict)
|
||||||
|
|
||||||
_context: ExecutionContext | None = PrivateAttr(default=None)
|
_context: ExecutionContext | None = PrivateAttr(default=None)
|
||||||
|
@ -195,24 +186,9 @@ class Command(BaseModel):
|
||||||
elif self.argument_config:
|
elif self.argument_config:
|
||||||
self.argument_config(self.arg_parser)
|
self.argument_config(self.arg_parser)
|
||||||
elif self.auto_args:
|
elif self.auto_args:
|
||||||
if isinstance(self.action, (Action, ProcessAction)):
|
if isinstance(self.action, BaseAction):
|
||||||
return infer_args_from_func(self.action.action, self.arg_metadata)
|
return infer_args_from_func(
|
||||||
elif isinstance(self.action, ChainedAction):
|
self.action.get_infer_target(), self.arg_metadata
|
||||||
if self.action.actions:
|
|
||||||
action = self.action.actions[0]
|
|
||||||
if isinstance(action, Action):
|
|
||||||
return infer_args_from_func(action.action, self.arg_metadata)
|
|
||||||
elif callable(action):
|
|
||||||
return infer_args_from_func(action, self.arg_metadata)
|
|
||||||
elif isinstance(self.action, ActionGroup):
|
|
||||||
arg_defs = same_argument_definitions(
|
|
||||||
self.action.actions, self.arg_metadata
|
|
||||||
)
|
|
||||||
if arg_defs:
|
|
||||||
return arg_defs
|
|
||||||
logger.debug(
|
|
||||||
"[Command:%s] auto_args disabled: mismatched ActionGroup arguments",
|
|
||||||
self.key,
|
|
||||||
)
|
)
|
||||||
elif callable(self.action):
|
elif callable(self.action):
|
||||||
return infer_args_from_func(self.action, self.arg_metadata)
|
return infer_args_from_func(self.action, self.arg_metadata)
|
||||||
|
|
|
@ -63,7 +63,7 @@ from falyx.protocols import ArgParserProtocol
|
||||||
from falyx.retry import RetryPolicy
|
from falyx.retry import RetryPolicy
|
||||||
from falyx.signals import BackSignal, CancelSignal, FlowSignal, HelpSignal, QuitSignal
|
from falyx.signals import BackSignal, CancelSignal, FlowSignal, HelpSignal, QuitSignal
|
||||||
from falyx.themes import OneColors, get_nord_theme
|
from falyx.themes import OneColors, get_nord_theme
|
||||||
from falyx.utils import CaseInsensitiveDict, _noop, chunks, get_program_invocation
|
from falyx.utils import CaseInsensitiveDict, _noop, chunks
|
||||||
from falyx.version import __version__
|
from falyx.version import __version__
|
||||||
|
|
||||||
|
|
||||||
|
@ -158,8 +158,8 @@ class Falyx:
|
||||||
force_confirm: bool = False,
|
force_confirm: bool = False,
|
||||||
cli_args: Namespace | None = None,
|
cli_args: Namespace | None = None,
|
||||||
options: OptionsManager | None = None,
|
options: OptionsManager | None = None,
|
||||||
render_menu: Callable[["Falyx"], None] | None = None,
|
render_menu: Callable[[Falyx], None] | None = None,
|
||||||
custom_table: Callable[["Falyx"], Table] | Table | None = None,
|
custom_table: Callable[[Falyx], Table] | Table | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initializes the Falyx object."""
|
"""Initializes the Falyx object."""
|
||||||
self.title: str | Markdown = title
|
self.title: str | Markdown = title
|
||||||
|
@ -183,8 +183,8 @@ class Falyx:
|
||||||
self._never_prompt: bool = never_prompt
|
self._never_prompt: bool = never_prompt
|
||||||
self._force_confirm: bool = force_confirm
|
self._force_confirm: bool = force_confirm
|
||||||
self.cli_args: Namespace | None = cli_args
|
self.cli_args: Namespace | None = cli_args
|
||||||
self.render_menu: Callable[["Falyx"], None] | None = render_menu
|
self.render_menu: Callable[[Falyx], None] | None = render_menu
|
||||||
self.custom_table: Callable[["Falyx"], Table] | Table | None = custom_table
|
self.custom_table: Callable[[Falyx], Table] | Table | None = custom_table
|
||||||
self.validate_options(cli_args, options)
|
self.validate_options(cli_args, options)
|
||||||
self._prompt_session: PromptSession | None = None
|
self._prompt_session: PromptSession | None = None
|
||||||
self.mode = FalyxMode.MENU
|
self.mode = FalyxMode.MENU
|
||||||
|
@ -526,7 +526,7 @@ class Falyx:
|
||||||
key: str = "X",
|
key: str = "X",
|
||||||
description: str = "Exit",
|
description: str = "Exit",
|
||||||
aliases: list[str] | None = None,
|
aliases: list[str] | None = None,
|
||||||
action: Callable[[Any], Any] | None = None,
|
action: Callable[..., Any] | None = None,
|
||||||
style: str = OneColors.DARK_RED,
|
style: str = OneColors.DARK_RED,
|
||||||
confirm: bool = False,
|
confirm: bool = False,
|
||||||
confirm_message: str = "Are you sure?",
|
confirm_message: str = "Are you sure?",
|
||||||
|
@ -580,7 +580,7 @@ class Falyx:
|
||||||
self,
|
self,
|
||||||
key: str,
|
key: str,
|
||||||
description: str,
|
description: str,
|
||||||
action: BaseAction | Callable[[Any], Any],
|
action: BaseAction | Callable[..., Any],
|
||||||
*,
|
*,
|
||||||
args: tuple = (),
|
args: tuple = (),
|
||||||
kwargs: dict[str, Any] | None = None,
|
kwargs: dict[str, Any] | None = None,
|
||||||
|
@ -614,7 +614,7 @@ class Falyx:
|
||||||
argument_config: Callable[[CommandArgumentParser], None] | None = None,
|
argument_config: Callable[[CommandArgumentParser], None] | None = None,
|
||||||
custom_parser: ArgParserProtocol | None = None,
|
custom_parser: ArgParserProtocol | None = None,
|
||||||
custom_help: Callable[[], str | None] | None = None,
|
custom_help: Callable[[], str | None] | None = None,
|
||||||
auto_args: bool = False,
|
auto_args: bool = True,
|
||||||
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
||||||
) -> Command:
|
) -> Command:
|
||||||
"""Adds an command to the menu, preventing duplicates."""
|
"""Adds an command to the menu, preventing duplicates."""
|
||||||
|
@ -844,15 +844,6 @@ class Falyx:
|
||||||
await selected_command.preview()
|
await selected_command.preview()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if selected_command.requires_input:
|
|
||||||
program = get_program_invocation()
|
|
||||||
self.console.print(
|
|
||||||
f"[{OneColors.LIGHT_YELLOW}]⚠️ Command '{selected_command.key}' requires"
|
|
||||||
f" input and must be run via [{OneColors.MAGENTA}]'{program} run"
|
|
||||||
f"'[{OneColors.LIGHT_YELLOW}] with proper piping or arguments.[/]"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
self.last_run_command = selected_command
|
self.last_run_command = selected_command
|
||||||
|
|
||||||
if selected_command == self.exit_command:
|
if selected_command == self.exit_command:
|
||||||
|
|
|
@ -7,8 +7,6 @@ Licensed under the MIT License. See LICENSE file for details.
|
||||||
|
|
||||||
from .argparse import Argument, ArgumentAction, CommandArgumentParser
|
from .argparse import Argument, ArgumentAction, CommandArgumentParser
|
||||||
from .parsers import FalyxParsers, get_arg_parsers
|
from .parsers import FalyxParsers, get_arg_parsers
|
||||||
from .signature import infer_args_from_func
|
|
||||||
from .utils import same_argument_definitions
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Argument",
|
"Argument",
|
||||||
|
@ -16,6 +14,4 @@ __all__ = [
|
||||||
"CommandArgumentParser",
|
"CommandArgumentParser",
|
||||||
"get_arg_parsers",
|
"get_arg_parsers",
|
||||||
"FalyxParsers",
|
"FalyxParsers",
|
||||||
"infer_args_from_func",
|
|
||||||
"same_argument_definitions",
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
import inspect
|
import inspect
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable
|
||||||
|
|
||||||
from falyx import logger
|
from falyx.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def infer_args_from_func(
|
def infer_args_from_func(
|
||||||
func: Callable[[Any], Any],
|
func: Callable[[Any], Any] | None,
|
||||||
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Infer argument definitions from a callable's signature.
|
Infer argument definitions from a callable's signature.
|
||||||
Returns a list of kwargs suitable for CommandArgumentParser.add_argument.
|
Returns a list of kwargs suitable for CommandArgumentParser.add_argument.
|
||||||
"""
|
"""
|
||||||
|
if not callable(func):
|
||||||
|
logger.debug("Provided argument is not callable: %s", func)
|
||||||
|
return []
|
||||||
arg_metadata = arg_metadata or {}
|
arg_metadata = arg_metadata or {}
|
||||||
signature = inspect.signature(func)
|
signature = inspect.signature(func)
|
||||||
arg_defs = []
|
arg_defs = []
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from falyx import logger
|
from falyx import logger
|
||||||
from falyx.action.action import Action, ChainedAction, ProcessAction
|
|
||||||
from falyx.parsers.signature import infer_args_from_func
|
from falyx.parsers.signature import infer_args_from_func
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,17 +8,12 @@ def same_argument_definitions(
|
||||||
actions: list[Any],
|
actions: list[Any],
|
||||||
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
arg_metadata: dict[str, str | dict[str, Any]] | None = None,
|
||||||
) -> list[dict[str, Any]] | None:
|
) -> list[dict[str, Any]] | None:
|
||||||
|
from falyx.action.action import BaseAction
|
||||||
|
|
||||||
arg_sets = []
|
arg_sets = []
|
||||||
for action in actions:
|
for action in actions:
|
||||||
if isinstance(action, (Action, ProcessAction)):
|
if isinstance(action, BaseAction):
|
||||||
arg_defs = infer_args_from_func(action.action, arg_metadata)
|
arg_defs = infer_args_from_func(action.get_infer_target(), arg_metadata)
|
||||||
elif isinstance(action, ChainedAction):
|
|
||||||
if action.actions:
|
|
||||||
action = action.actions[0]
|
|
||||||
if isinstance(action, Action):
|
|
||||||
arg_defs = infer_args_from_func(action.action, arg_metadata)
|
|
||||||
elif callable(action):
|
|
||||||
arg_defs = infer_args_from_func(action, arg_metadata)
|
|
||||||
elif callable(action):
|
elif callable(action):
|
||||||
arg_defs = infer_args_from_func(action, arg_metadata)
|
arg_defs = infer_args_from_func(action, arg_metadata)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = "0.1.29"
|
__version__ = "0.1.30"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "falyx"
|
name = "falyx"
|
||||||
version = "0.1.29"
|
version = "0.1.30"
|
||||||
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"
|
||||||
|
|
Loading…
Reference in New Issue