Skip to content

Commit 9d2d8f1

Browse files
Initial client
Signed-off-by: Flora <[email protected]>
1 parent 0c14015 commit 9d2d8f1

File tree

6 files changed

+183
-26
lines changed

6 files changed

+183
-26
lines changed

RELEASE_NOTES.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@
22

33
## Summary
44

5-
<!-- Here goes a general summary of what this release is about -->
5+
This release introduces the initial version of the Assets API client, enabling retrieval of asset-related data.
66

77
## Upgrading
88

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

1111
## New Features
1212

13-
<!-- Here goes the main new features and examples or instructions on how to use them -->
13+
Introducing the initial version of the Assets API client, designed to streamline access to asset-related data.
14+
15+
* Supports querying asset components and retrieving their metrics efficiently.
16+
* Provides a structured data representation while retaining raw protobuf responses.
17+
* Currently focused on retrieving asset data for individual components.
18+
* Examples are provided to guide users through the basic usage of the client.
1419

1520
## Bug Fixes
1621

examples/client.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Examples usage of PlatformAssets API."""
5+
6+
import asyncio
7+
import argparse
8+
from pprint import pprint
9+
10+
from frequenz.client.assets import AssetsApiClient
11+
12+
13+
async def main(microgrid_id: int) -> None:
14+
"""Test the AssetsApiClient.
15+
16+
Args:
17+
microgrid_id: The ID of the microgrid to query.
18+
"""
19+
server_url = "localhost:50052"
20+
client = AssetsApiClient(server_url)
21+
22+
print("########################################################")
23+
print("Fetching microgrid details")
24+
25+
microgrid_details = await client.get_microgrid(microgrid_id)
26+
pprint(microgrid_details)
27+
28+
print("########################################################")
29+
print("Listing microgrid components")
30+
31+
components = await client.list_microgrid_components(microgrid_id)
32+
pprint(components)
33+
34+
print("########################################################")
35+
print("Listing microgrid component connections")
36+
37+
connections = await client.list_microgrid_component_connections(microgrid_id)
38+
pprint(connections)
39+
40+
41+
if __name__ == "__main__":
42+
parser = argparse.ArgumentParser()
43+
parser.add_argument("microgrid_id", type=int, help="Microgrid ID")
44+
45+
args = parser.parse_args()
46+
asyncio.run(main(args.microgrid_id))

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ classifiers = [
2727
requires-python = ">= 3.11, < 4"
2828
dependencies = [
2929
"typing-extensions >= 4.12.2, < 5",
30+
"frequenz-api-assets >= 0.1.0, < 1",
31+
"frequenz-client-common >= 0.9.0, < 1",
3032
]
3133
dynamic = ["version"]
3234

@@ -82,6 +84,10 @@ dev = [
8284
"frequenz-client-assets[dev-mkdocs,dev-flake8,dev-formatting,dev-mkdocs,dev-mypy,dev-noxfile,dev-pylint,dev-pytest]",
8385
]
8486

87+
examples = [
88+
"grpcio >= 1.51.1, < 2",
89+
]
90+
8591
[project.urls]
8692
Documentation = "https://frequenz-floss.github.io/frequenz-client-assets-python/"
8793
Changelog = "https://github.com/frequenz-floss/frequenz-client-assets-python/releases"
Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
# License: MIT
22
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
33

4-
"""Assets API client for Python."""
4+
"""Client to connect to the PlatformAssets API.
55
6+
This package provides a low-level interface for interacting
7+
with the assets API.
8+
"""
69

7-
# TODO(cookiecutter): Remove this function
8-
def delete_me(*, blow_up: bool = False) -> bool:
9-
"""Do stuff for demonstration purposes.
1010

11-
Args:
12-
blow_up: If True, raise an exception.
11+
from ._client import AssetsApiClient
1312

14-
Returns:
15-
True if no exception was raised.
16-
17-
Raises:
18-
RuntimeError: if blow_up is True.
19-
"""
20-
if blow_up:
21-
raise RuntimeError("This function should be removed!")
22-
return True
13+
__all__ = ["AssetsApiClient"]
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Client for requests to the PlatformAssets API."""
5+
6+
from frequenz.client.base.client import BaseApiClient, call_stub_method
7+
from frequenz.client.base.exception import ClientNotConnected
8+
9+
# pylint: disable=no-name-in-module
10+
from frequenz.api.assets.v1.assets_pb2_grpc import PlatformAssetsStub
11+
from frequenz.api.assets.v1.assets_pb2 import (
12+
GetMicrogridRequest as PBGetMicrogridRequest)
13+
from frequenz.api.assets.v1.assets_pb2 import (
14+
GetMicrogridResponse as PBGetMicrogridResponse)
15+
from frequenz.api.assets.v1.assets_pb2 import (
16+
ListMicrogridComponentsRequest as PBListMicrogridComponentsRequest)
17+
from frequenz.api.assets.v1.assets_pb2 import (
18+
ListMicrogridComponentsResponse as PBListMicrogridComponentsResponse)
19+
from frequenz.api.assets.v1.assets_pb2 import (
20+
ListMicrogridComponentConnectionsRequest as PBListMicrogridComponentConnectionsRequest)
21+
from frequenz.api.assets.v1.assets_pb2 import (
22+
ListMicrogridComponentConnectionsResponse as PBListMicrogridComponentConnectionsResponse)
23+
# pylint: enable=no-name-in-module
24+
25+
class AssetsApiClient(BaseApiClient[PlatformAssetsStub]):
26+
"""A client for the Frequenz PlatformAssets service."""
27+
28+
def __init__(self, server_url: str, *, connect: bool = True) -> None:
29+
"""Initialize the client.
30+
31+
Args:
32+
server_url: The URL of the PlatformAssets service.
33+
connect: Whether to establish a connection immediately.
34+
"""
35+
super().__init__(server_url, PlatformAssetsStub, connect=connect)
36+
37+
@property
38+
def stub(self) -> PlatformAssetsStub:
39+
"""Return the gRPC stub for the PlatformAssets service."""
40+
if self.channel is None or self._stub is None:
41+
raise ClientNotConnected(server_url=self.server_url, operation="stub")
42+
return self._stub # type: ignore
43+
44+
async def get_microgrid(self, microgrid_id: int) -> PBGetMicrogridResponse:
45+
"""Fetch details of a specific microgrid.
46+
47+
Args:
48+
microgrid_id: The ID of the microgrid to fetch.
49+
50+
Returns:
51+
The response containing the microgrid details.
52+
"""
53+
request = PBGetMicrogridRequest(microgrid_id=microgrid_id)
54+
return await call_stub_method(self, lambda: self.stub.GetMicrogrid(request))
55+
56+
async def list_microgrid_components(
57+
self, microgrid_id: int, component_ids=None, categories=None
58+
) -> PBListMicrogridComponentsResponse:
59+
"""List electrical components of a microgrid.
60+
61+
Args:
62+
microgrid_id: The ID of the microgrid to fetch components for.
63+
component_ids: The IDs of the components to fetch.
64+
categories: The categories of the components to fetch.
65+
66+
Returns:
67+
The response containing the microgrid components.
68+
"""
69+
request = PBListMicrogridComponentsRequest(
70+
microgrid_id=microgrid_id,
71+
component_ids=component_ids or [],
72+
categories=categories or [],
73+
)
74+
return await call_stub_method(self, lambda: self.stub.ListMicrogridComponents(request))
75+
76+
async def list_microgrid_component_connections(
77+
self, microgrid_id: int, source_component_ids=None, destination_component_ids=None
78+
) -> PBListMicrogridComponentConnectionsResponse:
79+
"""List electrical connections between components in a microgrid.
80+
81+
Args:
82+
microgrid_id: The ID of the microgrid to fetch component connections for.
83+
source_component_ids: The IDs of the source components to fetch connections for.
84+
destination_component_ids: The IDs of the destination components to fetch connections for.
85+
86+
Returns:
87+
The response containing the component connections.
88+
"""
89+
request = PBListMicrogridComponentConnectionsRequest(
90+
microgrid_id=microgrid_id,
91+
source_component_ids=source_component_ids or [],
92+
destination_component_ids=destination_component_ids or [],
93+
)
94+
return await call_stub_method(self, lambda: self.stub.ListMicrogridComponentConnections(request))

tests/test_frequenz_client_assets.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,32 @@
22
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
33

44
"""Tests for the frequenz.client.assets package."""
5-
import pytest
5+
from typing import Generator
6+
from unittest.mock import MagicMock, patch
67

7-
from frequenz.client.assets import delete_me
8+
import pytest
89

10+
from frequenz.client.assets import AssetsApiClient
911

10-
def test_frequenz_assets_succeeds() -> None: # TODO(cookiecutter): Remove
11-
"""Test that the delete_me function succeeds."""
12-
assert delete_me() is True
12+
@pytest.fixture
13+
def mock_channel() -> Generator[MagicMock, None, None]:
14+
"""Fixture for grpc.aio.insecure_channel."""
15+
with patch("grpc.aio.insecure_channel") as mock:
16+
yield mock
1317

18+
@pytest.mark.asyncio
19+
async def test_client_initialization(mock_channel: MagicMock) -> None:
20+
"""Test that the client initializes the channel."""
21+
client = AssetsApiClient("localhost:50051") # noqa: F841
22+
mock_channel.assert_called_once_with("localhost:50051")
1423

15-
def test_frequenz_assets_fails() -> None: # TODO(cookiecutter): Remove
16-
"""Test that the delete_me function fails."""
17-
with pytest.raises(RuntimeError, match="This function should be removed!"):
18-
delete_me(blow_up=True)
24+
@pytest.mark.asyncio
25+
async def test_get_microgrid(mock_channel: MagicMock) -> None:
26+
"""Test that get_microgrid calls the stub correctly."""
27+
mock_stub = MagicMock()
28+
mock_stub.GetMicrogrid.return_value = MagicMock()
29+
with patch("frequenz.client.assets.AssetsApiClient.stub", new_callable=lambda: mock_stub):
30+
client = AssetsApiClient("localhost:50051")
31+
response = await client.get_microgrid(123)
32+
mock_stub.GetMicrogrid.assert_called_once()
33+
assert response is not None

0 commit comments

Comments
 (0)