-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
290 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
from enum import Enum, auto | ||
from functools import partial | ||
from typing import Any, Callable, Type | ||
|
||
import click | ||
from click.decorators import FC | ||
|
||
from ..client import CMClient | ||
|
||
__all__ = [ | ||
"cmclient", | ||
"output", | ||
"OutputEnum", | ||
] | ||
|
||
|
||
class EnumChoice(click.Choice): | ||
"""A version of click.Choice specialized for enum types.""" | ||
|
||
def __init__(self, enum: Type[Enum], case_sensitive: bool = True) -> None: | ||
self._enum = enum | ||
super().__init__(list(enum.__members__.keys()), case_sensitive=case_sensitive) | ||
|
||
def convert(self, value: Any, param: click.Parameter | None, ctx: click.Context | None) -> Enum: | ||
converted_str = super().convert(value, param, ctx) | ||
return self._enum.__members__[converted_str] | ||
|
||
|
||
class PartialOption: | ||
"""Wraps click.option decorator with partial arguments for convenient | ||
reuse.""" | ||
|
||
def __init__(self, *param_decls: str, **attrs: Any) -> None: | ||
self._partial = partial(click.option, *param_decls, cls=partial(click.Option), **attrs) | ||
|
||
def __call__(self, *param_decls: str, **attrs: Any) -> Callable[[FC], FC]: | ||
return self._partial(*param_decls, **attrs) | ||
|
||
|
||
class OutputEnum(Enum): | ||
yaml = auto() | ||
json = auto() | ||
|
||
|
||
output = PartialOption( | ||
"--output", | ||
"-o", | ||
type=EnumChoice(OutputEnum), | ||
help="Output format. Summary table if not specified.", | ||
) | ||
|
||
|
||
def make_client(ctx: click.Context, param: click.Parameter, value: Any) -> CMClient: | ||
return CMClient(value) | ||
|
||
|
||
cmclient = PartialOption( | ||
"--server", | ||
"client", | ||
type=str, | ||
default="http://localhost:8080/cm-service/v1", | ||
envvar="CM_SERVICE", | ||
show_envvar=True, | ||
callback=make_client, | ||
help="URL of cm service.", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import httpx | ||
from pydantic import parse_obj_as | ||
|
||
from . import models | ||
|
||
__all__ = ["CMClient"] | ||
|
||
|
||
class CMClient: | ||
"""Interface for accessing remote cm-service""" | ||
|
||
def __init__(self, url: str) -> None: | ||
self._client = httpx.Client(base_url=url) | ||
|
||
def get_productions(self) -> list[models.Production]: | ||
skip = 0 | ||
productions = [] | ||
query = "productions?" | ||
while (results := self._client.get(f"{query}skip={skip}").json()) != []: | ||
productions.extend(parse_obj_as(list[models.Production], results)) | ||
skip += len(results) | ||
return productions | ||
|
||
def get_campaigns(self, production: int | None = None) -> list[models.Campaign]: | ||
skip = 0 | ||
campaigns = [] | ||
query = f"campaigns?{f'production={production}&' if production else ''}" | ||
while (results := self._client.get(f"{query}skip={skip}").json()) != []: | ||
campaigns.extend(parse_obj_as(list[models.Campaign], results)) | ||
skip += len(results) | ||
return campaigns | ||
|
||
def get_steps(self, campaign: int | None = None) -> list[models.Step]: | ||
skip = 0 | ||
steps = [] | ||
query = f"steps?{f'campaign={campaign}&' if campaign else ''}" | ||
while (results := self._client.get(f"{query}skip={skip}").json()) != []: | ||
steps.extend(parse_obj_as(list[models.Step], results)) | ||
skip += len(results) | ||
return steps | ||
|
||
def get_groups(self, step: int | None = None) -> list[models.Group]: | ||
skip = 0 | ||
groups = [] | ||
query = f"groups?{f'step={step}&' if step else ''}" | ||
while (results := self._client.get(f"{query}skip={skip}").json()) != []: | ||
groups.extend(parse_obj_as(list[models.Group], results)) | ||
skip += len(results) | ||
return groups |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,42 @@ | ||
from click.testing import CliRunner | ||
from safir.testing.uvicorn import UvicornProcess | ||
|
||
from lsst.cmservice.cli.commands import main | ||
from lsst.cmservice.config import config | ||
|
||
|
||
def test_commands() -> None: | ||
runner = CliRunner() | ||
def test_commands(uvicorn: UvicornProcess) -> None: | ||
env = {"CM_SERVICE": f"{uvicorn.url}{config.prefix}"} | ||
runner = CliRunner(env=env) | ||
|
||
result = runner.invoke(main, ["--version"]) | ||
result = runner.invoke(main, "--version") | ||
assert result.exit_code == 0 | ||
assert "version" in result.output | ||
|
||
result = runner.invoke(main, ["--help"]) | ||
result = runner.invoke(main, "--help") | ||
assert result.exit_code == 0 | ||
assert "Usage:" in result.output | ||
|
||
result = runner.invoke(main, ["help"]) | ||
result = runner.invoke(main, "init") | ||
assert result.exit_code == 0 | ||
assert "Usage:" in result.output | ||
|
||
result = runner.invoke(main, ["help", "init"]) | ||
result = runner.invoke(main, "get productions") | ||
assert result.exit_code == 0 | ||
assert "Usage:" in result.output | ||
|
||
result = runner.invoke(main, ["help", "bogus"]) | ||
assert result.exit_code == 2 | ||
assert "Usage:" in result.output | ||
result = runner.invoke(main, "get productions -o yaml") | ||
assert result.exit_code == 0 | ||
|
||
result = runner.invoke(main, "get productions -o json") | ||
assert result.exit_code == 0 | ||
|
||
result = runner.invoke(main, "get campaigns") | ||
assert result.exit_code == 0 | ||
|
||
result = runner.invoke(main, "get campaigns -o yaml") | ||
assert result.exit_code == 0 | ||
|
||
result = runner.invoke(main, "get campaigns -o json") | ||
assert result.exit_code == 0 | ||
|
||
result = runner.invoke(main, "tree") | ||
assert result.exit_code == 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters