feat(core): clone commands and actions when binding runtimes
Add clone support across Action types and Command so commands can be safely registered or runner-bound without mutating the original instances. - clone BaseAction implementations across simple, composite, IO, prompt, file, HTTP, process, and signal actions - bind cloned commands in Falyx.add_command_from_command() and CommandRunner - preserve local never_prompt settings when cloning actions - rename shared runtime state from options to options_manager for consistency - seed root and execution option namespaces consistently - apply scoped root and namespace option overrides during routing and dispatch - improve namespace completion by delegating option suggestions to FalyxParser - enrich missing-value errors and error hints
This commit is contained in:
@@ -7,7 +7,6 @@ from rich.text import Text
|
||||
from falyx import Falyx
|
||||
from falyx.console import console as falyx_console
|
||||
from falyx.exceptions import FalyxError
|
||||
from falyx.parser import ParseResult
|
||||
from falyx.signals import BackSignal, CancelSignal, FlowSignal, HelpSignal, QuitSignal
|
||||
|
||||
|
||||
@@ -107,27 +106,25 @@ async def test_run_default_to_menu_help(flx):
|
||||
async def test_run_debug_hooks(flx):
|
||||
sys.argv = ["falyx", "--debug-hooks", "T"]
|
||||
|
||||
assert flx.options.get("debug_hooks") is False
|
||||
assert flx.options_manager.get("debug_hooks", namespace_name="root") is False
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
await flx.run()
|
||||
|
||||
assert flx.options.get("debug_hooks") is True
|
||||
assert flx.options_manager.get("debug_hooks", namespace_name="root") is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_never_prompt(flx):
|
||||
sys.argv = ["falyx", "--never-prompt", "T"]
|
||||
|
||||
assert flx.options.get("never_prompt") is False
|
||||
assert flx.options_manager.get("never_prompt", namespace_name="root") is False
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
await flx.run()
|
||||
|
||||
falyx_console.print(flx.options.get_namespace_dict("default"))
|
||||
|
||||
assert flx.options.get("debug_hooks") is False
|
||||
assert flx.options.get("never_prompt") is True
|
||||
assert flx.options_manager.get("debug_hooks", namespace_name="root") is False
|
||||
assert flx.options_manager.get("never_prompt", namespace_name="root") is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -253,3 +250,70 @@ async def test_run_preview(flx):
|
||||
captured = Text.from_ansi(capture.get()).plain
|
||||
assert "Command: 'T'" in captured
|
||||
assert "Would call: <lambda>(args=(), kwargs={})" in captured
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_applies_root_defaults_without_overwriting_existing_root_values():
|
||||
child = Falyx(program="child")
|
||||
child.add_command("D", "Deploy", action=lambda: "ok", aliases=["deploy"])
|
||||
|
||||
child.options_manager.set("verbose", True, "root")
|
||||
|
||||
root = Falyx(program="root")
|
||||
root.add_submenu(
|
||||
key="C",
|
||||
description="Child Menu",
|
||||
submenu=child,
|
||||
)
|
||||
|
||||
async def fake_dispatch_route(*, route, args, kwargs, execution_args, **_):
|
||||
assert route.namespace is child
|
||||
assert route.root_overrides == {}
|
||||
assert route.root_defaults["verbose"] is False
|
||||
assert route.namespace.options_manager.get("verbose", False, "root") is True
|
||||
|
||||
root._dispatch_route = fake_dispatch_route
|
||||
|
||||
sys.argv = ["falyx", "C", "D"]
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
await root.run()
|
||||
|
||||
assert excinfo.value.code == 0
|
||||
|
||||
assert child.options_manager.get("verbose", False, "root") is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_applies_root_overrides_temporarily_and_restores_root_namespace():
|
||||
child = Falyx(program="child")
|
||||
child.add_command("D", "Deploy", action=lambda: "ok", aliases=["deploy"])
|
||||
|
||||
child.options_manager.set("verbose", False, "root")
|
||||
|
||||
root = Falyx(program="root")
|
||||
root.add_submenu(
|
||||
key="C",
|
||||
description="Child Menu",
|
||||
submenu=child,
|
||||
)
|
||||
|
||||
seen_during_dispatch = {}
|
||||
|
||||
async def fake_dispatch_route(*, route, args, kwargs, execution_args, **_):
|
||||
seen_during_dispatch["verbose"] = route.namespace.options_manager.get(
|
||||
"verbose", False, "root"
|
||||
)
|
||||
assert route.namespace is child
|
||||
assert route.root_overrides == {"verbose": True}
|
||||
assert seen_during_dispatch["verbose"] is True
|
||||
|
||||
root._dispatch_route = fake_dispatch_route
|
||||
|
||||
sys.argv = ["falyx", "--verbose", "C", "D"]
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
await root.run()
|
||||
|
||||
assert excinfo.value.code == 0
|
||||
assert seen_during_dispatch["verbose"] is True
|
||||
|
||||
assert child.options_manager.get("verbose", False, "root") is False
|
||||
|
||||
Reference in New Issue
Block a user