From bc4fd28c8afb9cb51be26bffa2d535212941a743 Mon Sep 17 00:00:00 2001 From: Zohar Malamant Date: Tue, 16 Jun 2026 11:05:07 +0200 Subject: [PATCH] Add "acidwatch model show [ID]" command --- .../acidwatch/src/acidwatch/__init__.py | 5 ++ .../acidwatch/src/acidwatch/__main__.py | 4 ++ .../packages/acidwatch/src/acidwatch/cli.py | 51 --------------- .../acidwatch/src/acidwatch/cli/__init__.py | 17 +++++ .../acidwatch/src/acidwatch/cli/model.py | 56 +++++++++++++++++ .../acidwatch/src/acidwatch/client.py | 62 +++++++++++++++++++ 6 files changed, 144 insertions(+), 51 deletions(-) create mode 100644 backend/packages/acidwatch/src/acidwatch/__main__.py delete mode 100644 backend/packages/acidwatch/src/acidwatch/cli.py create mode 100644 backend/packages/acidwatch/src/acidwatch/cli/__init__.py create mode 100644 backend/packages/acidwatch/src/acidwatch/cli/model.py create mode 100644 backend/packages/acidwatch/src/acidwatch/client.py diff --git a/backend/packages/acidwatch/src/acidwatch/__init__.py b/backend/packages/acidwatch/src/acidwatch/__init__.py index e69de29b..756c90f8 100644 --- a/backend/packages/acidwatch/src/acidwatch/__init__.py +++ b/backend/packages/acidwatch/src/acidwatch/__init__.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from acidwatch.client import Client + +__all__ = ["Client"] diff --git a/backend/packages/acidwatch/src/acidwatch/__main__.py b/backend/packages/acidwatch/src/acidwatch/__main__.py new file mode 100644 index 00000000..e8438c0d --- /dev/null +++ b/backend/packages/acidwatch/src/acidwatch/__main__.py @@ -0,0 +1,4 @@ +if __name__ == "__main__": + from acidwatch.cli import app + + app() diff --git a/backend/packages/acidwatch/src/acidwatch/cli.py b/backend/packages/acidwatch/src/acidwatch/cli.py deleted file mode 100644 index a0920730..00000000 --- a/backend/packages/acidwatch/src/acidwatch/cli.py +++ /dev/null @@ -1,51 +0,0 @@ -from __future__ import annotations -from pydantic.alias_generators import to_camel - -from typer import Typer -from httpx import Client -from pydantic import BaseModel, RootModel, ConfigDict -from rich.console import Console -from rich.table import Table - -app = Typer() -console = Console() - - -API_URL = "https://backend-acidwatch-prod.radix.equinor.com" - - -class Model(BaseModel): - model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True) - - access_error: str | None - model_id: str - display_name: str - - -@app.command("list") -def list_models() -> None: - with Client(base_url=API_URL) as session: - response = session.get("/models") - assert response.status_code == 200 - - klass = RootModel[list[Model]] - - table = Table(title="Available AcidWatch models") - table.add_column("ID") - table.add_column("Name") - for model in klass.model_validate_json(response.content).root: - if model.access_error is not None: - continue - - table.add_row(model.model_id, model.display_name) - - console.print(table) - - -@app.command() -def run() -> None: - pass - - -if __name__ == "__main__": - app() diff --git a/backend/packages/acidwatch/src/acidwatch/cli/__init__.py b/backend/packages/acidwatch/src/acidwatch/cli/__init__.py new file mode 100644 index 00000000..44836ce2 --- /dev/null +++ b/backend/packages/acidwatch/src/acidwatch/cli/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations +from typer import Typer +from acidwatch.cli.model import model_app + + +app = Typer( + name="AcidWatch CLI", + no_args_is_help=True, + pretty_exceptions_enable=False, + rich_markup_mode=None, +) + + +app.add_typer(model_app) + + +__all__ = ["app"] diff --git a/backend/packages/acidwatch/src/acidwatch/cli/model.py b/backend/packages/acidwatch/src/acidwatch/cli/model.py new file mode 100644 index 00000000..23469f88 --- /dev/null +++ b/backend/packages/acidwatch/src/acidwatch/cli/model.py @@ -0,0 +1,56 @@ +from __future__ import annotations +from rich.panel import Panel +from rich import print as rprint +from rich.table import Table +from rich.pretty import pretty_repr +from acidwatch import Client +from typer import Typer + + +model_app = Typer(name="model") + + +@model_app.command("list") +def model_list() -> None: + table = Table("ID", "Name", title="Available AcidWatch models") + + with Client() as session: + for model in session.list_models(): + if model.access_error is not None: + continue + + table.add_row(model.model_id, model.display_name) + + rprint(table) + + +@model_app.command("show") +def model_show(model_id: str) -> None: + with Client() as session: + for model in session.list_models(): + if model.model_id == model_id: + break + + else: + rprint("No such model: ", model_id) + + info_table = Table(show_header=False) + info_table.add_row("Model ID", model.model_id) + info_table.add_row("Display name", model.display_name) + info_table.add_row("Valid substances", pretty_repr(model.valid_substances)) + rprint(info_table) + + if model.parameters: + parameter_table = Table( + "ID", "Label", "Type", "Default value", title="Parameters" + ) + for param_id, param in model.parameters.items(): + parameter_table.add_row( + param_id, param.label, param.type, str(param.default) + ) + rprint(parameter_table) + + rprint(Panel(model.description, title="Description", expand=False)) + + +__all__ = ["model_app"] diff --git a/backend/packages/acidwatch/src/acidwatch/client.py b/backend/packages/acidwatch/src/acidwatch/client.py new file mode 100644 index 00000000..d62abda9 --- /dev/null +++ b/backend/packages/acidwatch/src/acidwatch/client.py @@ -0,0 +1,62 @@ +from __future__ import annotations +from pydantic.alias_generators import to_camel +from pydantic import BaseModel, ConfigDict, RootModel, Field +from typing_extensions import Doc +from typing import Annotated, Any +import httpx + + +DEFAULT_API_URL = "https://backend-acidwatch-prod.radix.equinor.com" + + +class _Parameter(BaseModel): + model_config = ConfigDict( + alias_generator=to_camel, + populate_by_name=True, + ) + + default: Any + label: str + unit: Annotated[str | None, Field(None)] + type: Annotated[str | None, Field(None)] + + +class Model(BaseModel): + model_config = ConfigDict( + alias_generator=to_camel, + populate_by_name=True, + ) + + access_error: str | None + model_id: str + display_name: str + description: str + + valid_substances: list[str] + parameters: dict[str, _Parameter] + + +class Client(httpx.Client): + def __init__( + self, + api_url: Annotated[ + str, + Doc( + """ + AcidWatch API URL, pointing to the root of the "backend". + For local AcidWatch instance, use eg. "http://localhost:8000" + """ + ), + ] = DEFAULT_API_URL, + ) -> None: + super().__init__(base_url=api_url) + + def list_models(self) -> list[Model]: + resp = self.get("/models") + + assert resp.status_code == 200 + + root_model = RootModel[list[Model]] + object = root_model.model_validate_json(resp.content) + + return object.root