diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d466455..bec9d27 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,8 @@ ## New Features +* Add CLI tool to print formulas from assets API component graph. + ## Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index 81f450c..02b66f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ description = "High-level interface to grid pools for the Frequenz platform." readme = "README.md" license = { text = "MIT" } keywords = ["frequenz", "python", "lib", "library", "gridpool"] -# TODO(cookiecutter): Remove and add more classifiers if appropriate classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", @@ -26,20 +25,22 @@ classifiers = [ "Typing :: Typed", ] requires-python = ">= 3.11, < 4" -# TODO(cookiecutter): Remove and add more dependencies if appropriate dependencies = [ - "marshmallow-dataclass>=8.7.1,<9", + "marshmallow-dataclass >= 8.7.1, < 9", + "asyncclick >= 8.3.0.4, < 9", "typing-extensions >= 4.14.1, < 5", "frequenz-microgrid-component-graph >= 0.2.0, < 0.3", "frequenz-client-assets >= 0.2.0, < 0.3", ] dynamic = ["version"] +[project.scripts] +gridpool-cli = "frequenz.gridpool.cli.__main__:main" + [[project.authors]] name = "Frequenz Energy-as-a-Service GmbH" email = "floss@frequenz.com" -# TODO(cookiecutter): Remove and add more optional dependencies if appropriate [project.optional-dependencies] dev-flake8 = [ "flake8 == 7.3.0", diff --git a/src/frequenz/gridpool/cli/__init__.py b/src/frequenz/gridpool/cli/__init__.py new file mode 100644 index 0000000..ffedaa5 --- /dev/null +++ b/src/frequenz/gridpool/cli/__init__.py @@ -0,0 +1 @@ +"""Package for CLI tool for gridpool functionality.""" diff --git a/src/frequenz/gridpool/cli/__main__.py b/src/frequenz/gridpool/cli/__main__.py new file mode 100644 index 0000000..96cf28a --- /dev/null +++ b/src/frequenz/gridpool/cli/__main__.py @@ -0,0 +1,72 @@ +# License: MIT +# Copyright © 2025 Frequenz Energy-as-a-Service GmbH + +"""CLI tool for gridpool functionality.""" + +import os + +import asyncclick as click +from frequenz.client.assets import AssetsApiClient +from frequenz.client.common.microgrid import MicrogridId + +from frequenz.gridpool import ComponentGraphGenerator + + +@click.group() +async def cli() -> None: + """CLI tool for gridpool functionality.""" + + +@cli.command() +@click.argument("microgrid_id", type=int) +@click.option( + "--prefix", + type=str, + default="{component}", + help="Prefix format for the output (Supports {microgrid_id} and {component} placeholders).", +) +async def print_formulas( + microgrid_id: int, + prefix: str, +) -> None: + """Fetch and print component graph formulas for a microgrid.""" + url = os.environ.get("ASSETS_API_URL") + key = os.environ.get("ASSETS_API_AUTH_KEY") + secret = os.environ.get("ASSETS_API_SIGN_SECRET") + if not url or not key or not secret: + raise click.ClickException( + "ASSETS_API_URL, ASSETS_API_AUTH_KEY, ASSETS_API_SIGN_SECRET must be set." + ) + + async with AssetsApiClient( + url, + auth_key=key, + sign_secret=secret, + ) as client: + cgg = ComponentGraphGenerator(client) + + graph = await cgg.get_component_graph(MicrogridId(microgrid_id)) + power_formulas = { + "consumption": graph.consumer_formula(), + "generation": graph.producer_formula(), + "grid": graph.grid_formula(), + "pv": graph.pv_formula(None), + "battery": graph.battery_formula(None), + "chp": graph.chp_formula(None), + "ev": graph.ev_charger_formula(None), + } + + for component, formula in power_formulas.items(): + print( + prefix.format(component=component, microgrid_id=microgrid_id) + + f' = "{formula}"' + ) + + +def main() -> None: + """Run the CLI tool.""" + cli(_anyio_backend="asyncio") + + +if __name__ == "__main__": + main()