Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 14 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,27 @@

## Summary

<!-- Here goes a general summary of what this release is about -->
This release introduces the initial version of the Reporting API client with support for
retrieving single metric historical data for a single component.

## Upgrading

<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->

## New Features

<!-- Here goes the main new features and examples or instructions on how to use them -->
* Introducing the initial version of the Reporting API client, streamlined for
retrieving single metric historical data for a single component. It incorporates
pagination handling and utilizes a wrapper data class that retains the raw
protobuf response while offering transformation capabilities limited here
to generators of structured data representation via named tuples.

* Current limitations include a single metric focus with plans for extensibility,
ongoing development for states and bounds integration, as well as support for
service-side features like resampling, streaming, and formula aggregations.

* Code examples are provided to guide users through the basic usage of the client.


## Bug Fixes

Expand Down
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

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/frequenz-client-common-python.git@v0.x.x",
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