Skip to content

Commit

Permalink
don't search parent_meta while actually executing commands. Add more …
Browse files Browse the repository at this point in the history
…meta tests.
  • Loading branch information
BrianPugh committed Jan 9, 2025
1 parent 596289f commit 840a197
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 6 deletions.
14 changes: 9 additions & 5 deletions cyclopts/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,16 @@ def _validate_default_command(x):
return x


def _combined_meta_command_mapping(app: Optional["App"], recurse_meta=True) -> dict[str, "App"]:
def _combined_meta_command_mapping(
app: Optional["App"], recurse_meta=True, recurse_parent_meta=True
) -> dict[str, "App"]:
"""Return a copied and combined mapping containing app and meta-app commands."""
if app is None:
return {}
command_mapping = copy(app._commands)
if recurse_meta:
command_mapping.update(_combined_meta_command_mapping(app._meta))
if app._meta_parent:
if recurse_parent_meta and app._meta_parent:
command_mapping.update(_combined_meta_command_mapping(app._meta_parent, recurse_meta=False))
return command_mapping

Expand Down Expand Up @@ -581,6 +583,8 @@ def meta(self) -> "App":
def parse_commands(
self,
tokens: Union[None, str, Iterable[str]] = None,
*,
include_parent_meta=True,
) -> tuple[tuple[str, ...], tuple["App", ...], list[str]]:
"""Extract out the command tokens from a command.
Expand Down Expand Up @@ -608,7 +612,7 @@ def parse_commands(
apps: list[App] = [app]
unused_tokens = tokens

command_mapping = _combined_meta_command_mapping(app)
command_mapping = _combined_meta_command_mapping(app, recurse_parent_meta=include_parent_meta)

for i, token in enumerate(tokens):
try:
Expand All @@ -618,7 +622,7 @@ def parse_commands(
except KeyError:
break
command_chain.append(token)
command_mapping = _combined_meta_command_mapping(app)
command_mapping = _combined_meta_command_mapping(app, recurse_parent_meta=include_parent_meta)

return tuple(command_chain), tuple(apps), unused_tokens

Expand Down Expand Up @@ -898,7 +902,7 @@ def _parse_known_args(

meta_parent = self

command_chain, apps, unused_tokens = self.parse_commands(tokens)
command_chain, apps, unused_tokens = self.parse_commands(tokens, include_parent_meta=False)
command_app = apps[-1]

ignored: dict[str, Any] = {}
Expand Down
121 changes: 120 additions & 1 deletion tests/test_meta.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from textwrap import dedent
from typing import Annotated

import pytest

from cyclopts import Parameter
from cyclopts import App, Parameter


@pytest.mark.parametrize(
Expand Down Expand Up @@ -31,3 +32,121 @@ def meta(*tokens: Annotated[str, Parameter(allow_leading_hyphen=True)], meta_fla
def test_meta_app_config_inheritance(app):
app.config = ("foo", "bar")
assert app.meta.config == ("foo", "bar")


@pytest.fixture
def queue():
return []


@pytest.fixture
def nested_meta_app(queue, console):
subapp = App(console=console)

@subapp.meta.default
def subapp_meta(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]) -> None:
"""This is subapp's help."""
queue.append("subapp meta")
subapp(tokens)

@subapp.command
def foo(value: int) -> None:
"""Subapp foo help string.
Parameters
----------
value: int
The value a user inputted.
"""
queue.append(f"subapp foo body {value}")

app = App(name="test_app", console=console)
app.command(subapp.meta, name="subapp")

@app.meta.default
def meta(*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)]):
queue.append("root meta")
app(tokens)

return app


def test_meta_app_nested_root_help(nested_meta_app, console, queue):
with console.capture() as capture:
nested_meta_app.meta(["--help"])

actual = capture.get()

expected = dedent(
"""\
Usage: test_app COMMAND
╭─ Commands ─────────────────────────────────────────────────────────╮
│ subapp This is subapp's help. │
│ --help -h Display this message and exit. │
│ --version Display application version. │
╰────────────────────────────────────────────────────────────────────╯
"""
)

assert actual == expected
assert not queue


def test_meta_app_nested_subapp_help(nested_meta_app, console, queue):
with console.capture() as capture:
nested_meta_app.meta(["subapp", "--help"])

actual = capture.get()

expected = dedent(
"""\
Usage: test_app subapp COMMAND [ARGS]
This is subapp's help.
╭─ Commands ─────────────────────────────────────────────────────────╮
│ foo Subapp foo help string. │
│ --help -h Display this message and exit. │
│ --version Display application version. │
╰────────────────────────────────────────────────────────────────────╯
"""
)

assert actual == expected
assert not queue


def test_meta_app_nested_subapp_foo_help(nested_meta_app, console, queue):
with console.capture() as capture:
nested_meta_app.meta(["subapp", "foo", "--help"])

actual = capture.get()

expected = dedent(
"""\
Usage: test_app subapp foo [ARGS] [OPTIONS]
Subapp foo help string.
╭─ Parameters ───────────────────────────────────────────────────────╮
│ * VALUE --value The value a user inputted. [required] │
╰────────────────────────────────────────────────────────────────────╯
"""
)

assert actual == expected
assert not queue


@pytest.mark.parametrize(
"cmd_str,expected",
[
("", ["root meta"]),
("subapp", ["root meta", "subapp meta"]),
("subapp foo 5", ["root meta", "subapp meta", "subapp foo body 5"]),
],
)
def test_meta_app_nested_exec(nested_meta_app, queue, cmd_str, expected):
nested_meta_app.meta(cmd_str)
assert queue == expected

0 comments on commit 840a197

Please sign in to comment.