feat(core): advance options/state handling and workflow execution integration

- extend OptionsManager to support multi-namespace option resolution and toggling
- integrate OptionsManager more deeply across Action, ChainedAction, and ActionGroup
- propagate shared runtime configuration through execution layers
- refine action composition model (sequential + parallel execution semantics)
- improve lifecycle consistency across BaseAction, Action, ChainedAction, and ActionGroup
- begin aligning execution flow with centralized context and options handling

wip: routing and root option parsing behavior still in progress
This commit is contained in:
2026-05-10 13:48:06 -04:00
parent cce92cca09
commit 8db7a9e6dc
47 changed files with 2886 additions and 1089 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,21 @@ def test_enable_execution_options_registers_retry_flags():
assert "retry_backoff" in parser._execution_dests
def test_enable_execution_options_invalid_double_registration_raises():
parser = CommandArgumentParser()
parser.enable_execution_options(frozenset({ExecutionOption.SUMMARY}))
with pytest.raises(
CommandArgumentError, match="destination 'summary' is already defined"
):
parser.enable_execution_options(frozenset({ExecutionOption.SUMMARY}))
with pytest.raises(
CommandArgumentError,
match="destination 'summary' is already registered as an execution argument",
):
parser._register_execution_dest("summary")
def test_enable_execution_options_registers_confirm_flags():
parser = CommandArgumentParser()
parser.enable_execution_options(frozenset({ExecutionOption.CONFIRM}))
@@ -48,12 +63,12 @@ def test_register_execution_dest_rejects_duplicates():
parser = CommandArgumentParser()
parser.enable_execution_options(frozenset({ExecutionOption.SUMMARY}))
with pytest.raises(
CommandArgumentError, match="Destination 'summary' is already defined"
CommandArgumentError, match="destination 'summary' is already defined"
):
parser.add_argument("--summary", action="store_true")
with pytest.raises(
CommandArgumentError, match="Destination 'summary' is already defined"
CommandArgumentError, match="destination 'summary' is already defined"
):
parser.enable_execution_options(frozenset({ExecutionOption.SUMMARY}))
@@ -138,6 +153,6 @@ async def test_parse_args_split_with_conflicting_execution_option_raises():
parser = CommandArgumentParser()
parser.add_argument("--summary", action="store_true", help="A conflicting argument.")
with pytest.raises(
CommandArgumentError, match="Destination 'summary' is already defined"
CommandArgumentError, match="destination 'summary' is already defined"
):
parser.enable_execution_options(frozenset({ExecutionOption.SUMMARY}))

View File

@@ -0,0 +1,96 @@
import pytest
from falyx.exceptions import CommandArgumentError
from falyx.parser import CommandArgumentParser
from falyx.parser.command_argument_parser import _GroupBuilder
def test_group_builder():
parser = CommandArgumentParser(program="test_program")
group_builder = _GroupBuilder(parser, group_name="test_group")
assert group_builder.group_name == "test_group"
assert "group='test_group'" in str(group_builder)
group_builder = _GroupBuilder(
parser,
mutex_name="test_group",
)
assert group_builder.mutex_name == "test_group"
assert "mutex_group='test_group'" in str(group_builder)
with pytest.raises(CommandArgumentError):
_GroupBuilder(parser, group_name="test_group", mutex_name="test_group")
with pytest.raises(CommandArgumentError):
_GroupBuilder(parser)
with pytest.raises(AssertionError):
builder = _GroupBuilder(parser, group_name="test_group")
builder.group_name = None
builder.mutex_name = None
str(builder)
def test_adding_arguments_to_group():
parser = CommandArgumentParser(program="test_program")
group = parser.add_argument_group("test_group")
assert group.group_name == "test_group"
group.add_argument("--foo", type=str, help="Foo argument")
group.add_argument("--bar", type=int, help="Bar argument")
with pytest.raises(CommandArgumentError):
parser.add_argument_group("test_group")
def test_adding_arguments_to_mutex_group():
parser = CommandArgumentParser(program="test_program")
mutex_group = parser.add_mutually_exclusive_group("test_mutex_group")
assert mutex_group.mutex_name == "test_mutex_group"
mutex_group.add_argument("--foo", type=str, help="Foo argument")
mutex_group.add_argument("--bar", type=int, help="Bar argument")
with pytest.raises(CommandArgumentError):
parser.add_mutually_exclusive_group("test_mutex_group")
def test_adding_arguments_to_group_with_invalid_group():
parser = CommandArgumentParser(program="test_program")
with pytest.raises(CommandArgumentError):
parser.add_argument(
"--foo", type=str, help="Foo argument", group="non_existent_group"
)
with pytest.raises(CommandArgumentError):
parser.add_argument(
"--bar", type=int, help="Bar argument", mutex_group="non_existent_group"
)
def test_adding_positional_arguments_to_mutex_group():
parser = CommandArgumentParser(program="test_program")
group = parser.add_mutually_exclusive_group("test_group")
with pytest.raises(CommandArgumentError):
group.add_argument(
"positional_arg", type=str, help="This should fail because it's positional"
)
def test_adding_required_arguments_to_mutex_group():
parser = CommandArgumentParser(program="test_program")
group = parser.add_mutually_exclusive_group("test_group")
with pytest.raises(CommandArgumentError):
group.add_argument(
"--foo",
type=str,
help="This should fail because it's required",
required=True,
)

View File

@@ -69,14 +69,14 @@ async def test_resolve_args_raises_on_conflicting_execution_option():
execution_options=["summary"],
)
with pytest.raises(
CommandArgumentError, match="Destination 'summary' is already defined"
CommandArgumentError, match="destination 'summary' is already defined"
):
command.arg_parser.add_argument(
"--summary", action="store_true", help="A conflicting argument."
)
with pytest.raises(
CommandArgumentError, match="Destination 'summary' is already defined"
CommandArgumentError, match="destination 'summary' is already defined"
):
command.arg_parser.enable_execution_options(frozenset({ExecutionOption.SUMMARY}))

View File

@@ -2,6 +2,7 @@ import pytest
from falyx.exceptions import CommandArgumentError
from falyx.parser.command_argument_parser import CommandArgumentParser
from falyx.parser.parser_types import TLDRExample
@pytest.mark.asyncio
@@ -45,3 +46,27 @@ async def test_add_tldr_examples_in_init():
assert parser._tldr_examples[0].description == "This is the first example."
assert parser._tldr_examples[1].usage == "example2"
assert parser._tldr_examples[1].description == "This is the second example."
def test_add_tldr_example():
parser = CommandArgumentParser()
parser.add_tldr_example("example1", "This is the first example.")
assert len(parser._tldr_examples) == 1
assert parser._tldr_examples[0].usage == "example1"
assert parser._tldr_examples[0].description == "This is the first example."
def test_add_tldr_example_bad_args():
parser = CommandArgumentParser()
with pytest.raises(TypeError):
parser.add_tldr_example("example1", "This is the first example.", "extra_arg")
def test_add_tldr_examples_with_tldr_example_objects():
parser = CommandArgumentParser()
example1 = TLDRExample(usage="example1", description="This is the first example.")
example2 = TLDRExample(usage="example2", description="This is the second example.")
parser.add_tldr_examples([example1, example2])
assert len(parser._tldr_examples) == 2
assert parser._tldr_examples[0] == example1
assert parser._tldr_examples[1] == example2