Skip to content

Commit

Permalink
Add --help-all CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbrochart committed Jan 10, 2024
1 parent c879016 commit 02d89f5
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 4 deletions.
13 changes: 12 additions & 1 deletion src/asphalt/core/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
from ruamel.yaml import YAML, ScalarNode
from ruamel.yaml.loader import Loader

from .runner import policies, run_application
from .component import component_set_help_all
from .runner import policies, run_application, runner_set_help_all
from .utils import merge_config, qualified_name


Expand Down Expand Up @@ -63,13 +64,23 @@ def main() -> None:
type=str,
help="set configuration",
)
@click.option(
"--help-all",
is_flag=True,
default=False,
help="show all components' configuration parameters and exit",
)
def run(
configfile,
unsafe: bool,
loop: str | None,
service: str | None,
set_: list[str],
help_all: bool,
) -> None:
component_set_help_all(help_all)
runner_set_help_all(help_all)

yaml = YAML(typ="unsafe" if unsafe else "safe")
yaml.constructor.add_constructor("!Env", env_constructor)
yaml.constructor.add_constructor("!TextFile", text_file_constructor)
Expand Down
19 changes: 18 additions & 1 deletion src/asphalt/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__all__ = ("Component", "ContainerComponent", "CLIApplicationComponent")

import asyncio
import inspect
import sys
from abc import ABCMeta, abstractmethod
from asyncio import Future
Expand All @@ -14,6 +15,13 @@
from .context import Context
from .utils import PluginContainer, merge_config, qualified_name

help_all = False


def component_set_help_all(val):
global help_all
help_all = val


class Component(metaclass=ABCMeta):
"""This is the base class for all Asphalt components."""
Expand Down Expand Up @@ -54,11 +62,12 @@ class ContainerComponent(Component):
:vartype component_configs: Dict[str, Optional[Dict[str, Any]]]
"""

__slots__ = "child_components", "component_configs"
__slots__ = "child_components", "component_configs", "_hierarchy"

def __init__(self, components: dict[str, dict[str, Any] | None] | None = None) -> None:
self.child_components: OrderedDict[str, Component] = OrderedDict()
self.component_configs = components or {}
self._hierarchy = f"{self.__class__.__name__}."

def add_component(self, alias: str, type: str | type | None = None, **config) -> None:
"""
Expand Down Expand Up @@ -95,8 +104,13 @@ def add_component(self, alias: str, type: str | type | None = None, **config) ->
config = merge_config(config, override_config)

component = component_types.create_object(**config)
component._hierarchy = f"{self._hierarchy}{alias}."
self.child_components[alias] = component

if help_all:
signature = inspect.signature(component.__init__)
print(f"{self._hierarchy}{alias} {signature}")

async def start(self, ctx: Context) -> None:
"""
Create child components that have been configured but not yet created and then calls their
Expand Down Expand Up @@ -156,6 +170,9 @@ def start_run_task() -> None:
task.add_done_callback(run_complete)

await super().start(ctx)
if help_all:
sys.exit(0)

ctx.loop.call_later(0.1, start_run_task)

@abstractmethod
Expand Down
14 changes: 13 additions & 1 deletion src/asphalt/core/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__all__ = ("run_application",)

import asyncio
import inspect
import signal
import sys
from asyncio.events import AbstractEventLoop
Expand All @@ -11,12 +12,19 @@
from logging.config import dictConfig
from typing import Any, cast

from .component import Component, component_types
from .component import Component, ContainerComponent, component_types
from .context import Context, _current_context
from .utils import PluginContainer, qualified_name

policies = PluginContainer("asphalt.core.event_loop_policies")

help_all = False


def runner_set_help_all(val):
global help_all
help_all = val


def sigterm_handler(logger: Logger, event_loop: AbstractEventLoop) -> None:
if event_loop.is_running():
Expand Down Expand Up @@ -95,6 +103,10 @@ def run_application(
if isinstance(component, dict):
component = cast(Component, component_types.create_object(**component))

if help_all:
signature = inspect.signature(component.__init__) # type: ignore
print(component.__class__.__name__, signature)

logger.info("Starting application")
context = Context()
exception: BaseException | None = None
Expand Down
56 changes: 55 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@
from _pytest.monkeypatch import MonkeyPatch
from click.testing import CliRunner

from asphalt.core import Component, Context, cli
from asphalt.core import Component, ContainerComponent, Context, cli


class Container0(ContainerComponent):
async def start(self, ctx: Context) -> None:
self.add_component("container1", Container1)
self.add_component("container2", Container1)
await super().start(ctx)


class Container1(ContainerComponent):
async def start(self, ctx: Context) -> None:
self.add_component("dummy", DummyComponent)
await super().start(ctx)


class DummyComponent(Component):
Expand All @@ -24,6 +37,47 @@ def runner() -> CliRunner:
return CliRunner()


@pytest.mark.parametrize("help_all", [False, True])
@pytest.mark.parametrize("root_component", [DummyComponent, Container1, Container0])
def test_help_all(
runner: CliRunner,
help_all: bool,
root_component: Component,
) -> None:
config = f"""\
component:
type: {root_component.__module__}:{root_component.__name__}
"""
cmd = ["test.yml"]
if help_all:
cmd.append("--help-all")
with runner.isolated_filesystem():
Path("test.yml").write_text(config)
result = runner.invoke(cli.run, cmd)
if help_all:
if root_component == DummyComponent:
assert result.exit_code == 1
assert result.stdout == ("DummyComponent (dummyval1=None, dummyval2=None)\n")
elif root_component == Container1:
assert result.exit_code == 1
assert result.stdout == """\
Container1 (components: 'dict[str, dict[str, Any] | None] | None' = None) -> 'None'
Container1.dummy (dummyval1=None, dummyval2=None)
"""
elif root_component == Container0:
assert result.exit_code == 1
assert result.stdout == """\
Container0 (components: 'dict[str, dict[str, Any] | None] | None' = None) -> 'None'
Container0.container1 (components: 'dict[str, dict[str, Any] | None] | None' = None) -> 'None'
Container0.container2 (components: 'dict[str, dict[str, Any] | None] | None' = None) -> 'None'
Container0.container1.dummy (dummyval1=None, dummyval2=None)
Container0.container2.dummy (dummyval1=None, dummyval2=None)
"""
else:
assert result.exit_code == 1
assert result.stdout == ("")


@pytest.mark.parametrize("loop", [None, "uvloop"], ids=["default", "override"])
@pytest.mark.parametrize("unsafe", [False, True], ids=["safe", "unsafe"])
def test_run(
Expand Down

0 comments on commit 02d89f5

Please sign in to comment.