Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce simple reporting client with support for single metric historical data #16

Merged
merged 5 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions examples/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# License: MIT
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH

"""Examples usage of reporting API."""

import argparse
import asyncio
from datetime import datetime
from pprint import pprint
from typing import AsyncGenerator

import pandas as pd
from frequenz.client.common.metric import Metric

from frequenz.client.reporting import ReportingClient

# Experimental import
from frequenz.client.reporting._client import MetricSample


# pylint: disable=too-many-locals
async def main(microgrid_id: int, component_id: int) -> None:
"""Test the ReportingClient.

Args:
microgrid_id: int
component_id: int
"""
service_address = "localhost:50051"
client = ReportingClient(service_address)

microgrid_components = [(microgrid_id, [component_id])]
metrics = [
Metric.DC_POWER,
Metric.DC_CURRENT,
]

start_dt = datetime.fromisoformat("2023-11-21T12:00:00.00+00:00")
end_dt = datetime.fromisoformat("2023-11-21T12:01:00.00+00:00")

page_size = 10

print("########################################################")
print("Iterate over single metric generator")

async for sample in client.iterate_single_metric(
microgrid_id=microgrid_id,
component_id=component_id,
metric=metrics[0],
start_dt=start_dt,
end_dt=end_dt,
page_size=page_size,
):
print("Received:", sample)

###########################################################################
#
# The following code is experimental and demonstrates potential future
# usage of the ReportingClient.
#
###########################################################################

async def components_data_iter() -> AsyncGenerator[MetricSample, None]:
"""Iterate over components data.

Yields:
Single metric sample
"""
# pylint: disable=protected-access
async for page in client._iterate_components_data_pages(
microgrid_components=microgrid_components,
metrics=metrics,
start_dt=start_dt,
end_dt=end_dt,
page_size=page_size,
):
for entry in page.iterate_metric_samples():
yield entry
tiyash-basu-frequenz marked this conversation as resolved.
Show resolved Hide resolved

async def components_data_dict(
components_data_iter: AsyncGenerator[MetricSample, None]
) -> dict[int, dict[int, dict[datetime, dict[Metric, float]]]]:
"""Convert components data iterator into a single dict.

The nesting structure is:
{
microgrid_id: {
component_id: {
timestamp: {
metric: value
}
}
}
}

Args:
components_data_iter: async generator

Returns:
Single dict with with all components data
"""
ret: dict[int, dict[int, dict[datetime, dict[Metric, float]]]] = {}

async for ts, mid, cid, met, value in components_data_iter:
if mid not in ret:
ret[mid] = {}
if cid not in ret[mid]:
ret[mid][cid] = {}
if ts not in ret[mid][cid]:
ret[mid][cid][ts] = {}

ret[mid][cid][ts][met] = value

return ret

print("########################################################")
print("Iterate over generator")
async for msample in components_data_iter():
print("Received:", msample)

print("########################################################")
print("Dumping all data as a single dict")
dct = await components_data_dict(components_data_iter())
pprint(dct)

print("########################################################")
print("Turn data into a pandas DataFrame")
data = [cd async for cd in components_data_iter()]
df = pd.DataFrame(data).set_index("timestamp")
pprint(df)


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("microgrid_id", type=int, help="Microgrid ID")
parser.add_argument("component_id", type=int, help="Component ID")

args = parser.parse_args()
asyncio.run(main(args.microgrid_id, args.component_id))
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ requires-python = ">= 3.11, < 4"
# TODO(cookiecutter): Remove and add more dependencies if appropriate
dependencies = [
"typing-extensions >= 4.5.0, < 5",
"frequenz-api-reporting >= 0.1.1, < 1",
"frequenz-client-common @ git+https://github.com/frequenz-floss/[email protected]",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be updated with the upcoming release.

]
dynamic = ["version"]

Expand Down Expand Up @@ -62,6 +64,7 @@ dev-mypy = [
"types-Markdown == 3.4.2.10",
# For checking the noxfile, docs/ script, and tests
"frequenz-client-reporting[dev-mkdocs,dev-noxfile,dev-pytest]",
"pandas-stubs >= 2, < 3", # Only required for example
]
dev-noxfile = [
"nox == 2023.4.22",
Expand All @@ -71,6 +74,7 @@ dev-pylint = [
"pylint == 3.0.2",
# For checking the noxfile, docs/ script, and tests
"frequenz-client-reporting[dev-mkdocs,dev-noxfile,dev-pytest]",
"pandas >= 2, < 3", # Only required for example
]
dev-pytest = [
"pytest == 8.0.0",
Expand All @@ -82,6 +86,10 @@ dev-pytest = [
dev = [
"frequenz-client-reporting[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
]
examples = [
"grpcio >= 1.51.1, < 2",
"pandas >= 2, < 3",
]

[project.urls]
Documentation = "https://frequenz-floss.github.io/frequenz-client-reporting-python/"
Expand Down
21 changes: 4 additions & 17 deletions src/frequenz/client/reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,12 @@
# License: MIT
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH

"""Reporting API client for Python.
"""Client to connect to the Reporting API.

TODO(cookiecutter): Add a more descriptive module description.
This package provides a low-level interface for interacting with the reporting API.
"""


# TODO(cookiecutter): Remove this function
def delete_me(*, blow_up: bool = False) -> bool:
"""Do stuff for demonstration purposes.
from ._client import ReportingClient

Args:
blow_up: If True, raise an exception.

Returns:
True if no exception was raised.

Raises:
RuntimeError: if blow_up is True.
"""
if blow_up:
raise RuntimeError("This function should be removed!")
return True
__all__ = ["ReportingClient"]
Loading
Loading