Skip to content

Commit

Permalink
feat: standardized CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
Max KvR committed Oct 1, 2024
1 parent e65d0de commit 1b53019
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 25 deletions.
6 changes: 2 additions & 4 deletions examples/metricq_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@
import logging

import aiomonitor # type: ignore
import click
import click_log # type: ignore

import metricq
from metricq.cli import metricq_command

logger = metricq.get_logger()
click_log.basic_config(logger)
Expand All @@ -66,9 +66,7 @@ async def run(server: str, token: str) -> None:
await client.stopped()


@click.command()
@click.option("--server", default="amqp://admin:admin@localhost/")
@click.option("--token", default="client-py-example")
@metricq_command(default_token="client-py-example")
@click_log.simple_verbosity_option(logger) # type: ignore
def main(server: str, token: str) -> None:
asyncio.run(run(server, token))
Expand Down
5 changes: 2 additions & 3 deletions examples/metricq_get_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import click_log # type: ignore

import metricq
from metricq.cli import metricq_command

logger = metricq.get_logger()

Expand Down Expand Up @@ -98,9 +99,7 @@ async def aget_history(
click.echo(aggregate)


@click.command()
@click.option("--server", default="amqp://localhost/")
@click.option("--token", default="history-py-dummy")
@metricq_command(default_token="history-py-dummy")
@click.option("--metric", default=None)
@click.option("--list-metrics", is_flag=True)
@click.option("--list-metadata", is_flag=True)
Expand Down
5 changes: 2 additions & 3 deletions examples/metricq_get_history_raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import click_log # type: ignore

import metricq
from metricq.cli import metricq_command
from metricq.history_client import HistoryRequestType

logger = metricq.get_logger()
Expand Down Expand Up @@ -83,9 +84,7 @@ async def aget_history(server: str, token: str, metric: str) -> None:
await client.stop(None)


@click.command()
@click.option("--server", default="amqp://localhost/")
@click.option("--token", default="history-py-dummy")
@metricq_command(default_token="history-py-dummy")
@click.argument("metric")
@click_log.simple_verbosity_option(logger) # type: ignore
def get_history(server: str, token: str, metric: str) -> None:
Expand Down
5 changes: 2 additions & 3 deletions examples/metricq_pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import click_log # type: ignore

import metricq
from metricq.cli import metricq_command
from metricq.pandas import PandasHistoryClient

logger = metricq.get_logger()
Expand Down Expand Up @@ -81,9 +82,7 @@ async def aget_history(server: str, token: str, metric: str) -> None:
click.echo("----------")


@click.command()
@click.option("--server", default="amqp://localhost/")
@click.option("--token", default="history-py-dummy")
@metricq_command(default_token="history-py-dummy")
@click.option("--metric", default="example.quantity")
@click_log.simple_verbosity_option(logger) # type: ignore
def get_history(server: str, token: str, metric: str) -> None:
Expand Down
5 changes: 2 additions & 3 deletions examples/metricq_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import metricq
from metricq import Metric
from metricq.cli import metricq_command
from metricq.logging import get_logger

logger = get_logger()
Expand Down Expand Up @@ -76,9 +77,7 @@ async def on_data(
)


@click.command()
@click.option("--server", default="amqp://localhost/")
@click.option("--token", default="sink-py-dummy")
@metricq_command(default_token="sink-py-dummy")
@click.option("-m", "--metrics", multiple=True, required=True)
@click_log.simple_verbosity_option(logger) # type: ignore
def source(server: str, token: str, metrics: list[Metric]) -> None:
Expand Down
6 changes: 2 additions & 4 deletions examples/metricq_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@
import random
from typing import Any

import click
import click_log # type: ignore

import metricq
from metricq.cli import metricq_command
from metricq.logging import get_logger

logger = get_logger()
Expand Down Expand Up @@ -74,9 +74,7 @@ async def update(self) -> None:
)


@click.command()
@click.option("--server", default="amqp://localhost/")
@click.option("--token", default="source-py-dummy")
@metricq_command(default_token="source-py-dummy")
@click_log.simple_verbosity_option(logger) # type: ignore
def source(server: str, token: str) -> None:
src = DummySource(token=token, url=server)
Expand Down
6 changes: 2 additions & 4 deletions examples/metricq_synchronous_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@
import random
import time

import click
import click_log # type: ignore

from metricq import SynchronousSource, Timestamp, get_logger
from metricq.cli import metricq_command

logger = get_logger()

Expand All @@ -47,9 +47,7 @@
)


@click.command()
@click.option("--server", default="amqp://localhost/")
@click.option("--token", default="source-py-dummy")
@metricq_command(default_token="source-py-dummy")
@click_log.simple_verbosity_option(logger) # type: ignore
def synchronous_source(server: str, token: str) -> None:
ssource = SynchronousSource(token=token, url=server)
Expand Down
19 changes: 19 additions & 0 deletions metricq/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .command import metricq_command
from .params import (
ChoiceParam,
CommandLineChoice,
DurationParam,
OutputFormat,
TemplateStringParam,
TimestampParam,
)

__all__ = [
"ChoiceParam",
"CommandLineChoice",
"DurationParam",
"OutputFormat",
"TemplateStringParam",
"TimestampParam",
"metricq_command",
]
84 changes: 84 additions & 0 deletions metricq/cli/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import logging
from typing import Callable, cast

import click
import click_log # type: ignore
from click import option
from dotenv import find_dotenv, load_dotenv

from .. import get_logger
from .params import FC, TemplateStringParam

# We do not interpolate (i.e. replace ${VAR} with corresponding environment variables).
# That is because we want to be able to interpolate ourselves for metrics and tokens
# using the same syntax. If it was only ${USER} for the token, we could use the
# override functionality, but most unfortunately there is no standard environment
# variable for the hostname. Even $HOST on zsh is not actually part of the environment.
# ``override=false`` just means that environment variables have priority over the
# env files.
load_dotenv(dotenv_path=find_dotenv(".metricq"), interpolate=False, override=False)


def metricq_server_option() -> Callable[[FC], FC]:
return option(
"--server",
type=TemplateStringParam(),
metavar="URL",
required=True,
help="MetricQ server URL.",
)


def metricq_token_option(default: str) -> Callable[[FC], FC]:
return option(
"--token",
type=TemplateStringParam(),
metavar="CLIENT_TOKEN",
default=default,
show_default=True,
help="A token to identify this client on the MetricQ network.",
)


def get_metric_command_looger() -> logging.Logger:
logger = get_logger()
logger.setLevel(logging.WARNING)
click_log.basic_config(logger)

return logger


def metricq_command(
default_token: str, client_version: str | None = None
) -> Callable[[FC], click.Command]:
logger = get_metric_command_looger()

log_decorator = cast(
Callable[[FC], FC], click_log.simple_verbosity_option(logger, default="warning")
)
context_settings = {"auto_envvar_prefix": "METRICQ"}
epilog = (
"All options can be passed as environment variables prefixed with 'METRICQ_'."
"I.e., 'METRICQ_SERVER=amqps://...'.\n"
"\n"
"You can also create a '.metricq' file in the current or home directory that "
"contains environment variable settings in the same format.\n"
"\n"
"Some options, including server and token, can contain placeholders for $USER "
"and $HOST."
)

def decorator(func: FC) -> click.Command:
return click.version_option(version=client_version)(
log_decorator(
metricq_token_option(default_token)(
metricq_server_option()(
click.command(context_settings=context_settings, epilog=epilog)(
func
)
)
)
)
)

return decorator
Loading

0 comments on commit 1b53019

Please sign in to comment.