Skip to content

Commit dc9214f

Browse files
committed
Implement GetMicrogridMetadata
We call the `MicrogridApiClient` method `get_microgrid_info()` to be more forward-compatible with upcoming changes. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 86cccd1 commit dc9214f

File tree

5 files changed

+205
-1
lines changed

5 files changed

+205
-1
lines changed

src/frequenz/client/microgrid/_client.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
from frequenz.api.microgrid.v1 import microgrid_pb2_grpc
1414
from frequenz.client.base import channel, client, retry, streaming
1515
from frequenz.client.common.microgrid.components import ComponentId
16+
from google.protobuf.empty_pb2 import Empty
1617
from typing_extensions import override
1718

1819
from ._exception import ClientNotConnected
20+
from ._microgrid_info import MicrogridInfo
21+
from ._microgrid_info_proto import microgrid_info_from_proto
1922

2023
DEFAULT_GRPC_CALL_TIMEOUT = 60.0
2124
"""The default timeout for gRPC calls made by this client (in seconds)."""
@@ -122,3 +125,31 @@ async def __aexit__(
122125
"Error while disconnecting from the microgrid API", exceptions
123126
)
124127
return result
128+
129+
async def get_microgrid_info( # noqa: DOC502 (raises ApiClientError indirectly)
130+
self,
131+
) -> MicrogridInfo:
132+
"""Retrieve information about the local microgrid.
133+
134+
This consists of information about the overall microgrid, for example, the
135+
microgrid ID and its location. It does not include information about the
136+
electrical components or sensors in the microgrid.
137+
138+
Returns:
139+
The information about the local microgrid.
140+
141+
Raises:
142+
ApiClientError: If the are any errors communicating with the Microgrid API,
143+
most likely a subclass of
144+
[GrpcError][frequenz.client.microgrid.GrpcError].
145+
"""
146+
microgrid = await client.call_stub_method(
147+
self,
148+
lambda: self.stub.GetMicrogridMetadata(
149+
Empty(),
150+
timeout=DEFAULT_GRPC_CALL_TIMEOUT,
151+
),
152+
method_name="GetMicrogridMetadata",
153+
)
154+
155+
return microgrid_info_from_proto(microgrid.microgrid)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Test data for successful microgrid info retrieval."""
5+
6+
from datetime import datetime, timezone
7+
from typing import Any
8+
from unittest.mock import AsyncMock
9+
10+
from frequenz.api.common.v1.microgrid import microgrid_pb2 as microgrid_common_pb2
11+
from frequenz.api.microgrid.v1 import microgrid_pb2
12+
from frequenz.client.common.microgrid import EnterpriseId, MicrogridId
13+
from google.protobuf.empty_pb2 import Empty
14+
15+
from frequenz.client.microgrid import MicrogridInfo, MicrogridStatus
16+
17+
# No client_args or client_kwargs needed for this call
18+
19+
20+
def assert_stub_method_call(stub_method: AsyncMock) -> None:
21+
"""Assert that the gRPC request matches the expected request."""
22+
stub_method.assert_called_once_with(Empty(), timeout=60.0)
23+
24+
25+
create_timestamp = datetime(2023, 1, 1, tzinfo=timezone.utc)
26+
grpc_response = microgrid_pb2.GetMicrogridMetadataResponse(
27+
microgrid=microgrid_common_pb2.Microgrid()
28+
)
29+
30+
31+
def assert_client_result(result: Any) -> None:
32+
"""Assert that the client result matches the expected MicrogridInfo."""
33+
assert result == MicrogridInfo(
34+
id=MicrogridId(0),
35+
enterprise_id=EnterpriseId(0),
36+
name=None,
37+
status=MicrogridStatus.UNSPECIFIED,
38+
location=None,
39+
delivery_area=None,
40+
create_timestamp=datetime(1970, 1, 1, 0, 0, tzinfo=timezone.utc),
41+
)
42+
assert result.is_active
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Test data for microgrid info retrieval with error."""
5+
6+
from typing import Any
7+
8+
from google.protobuf.empty_pb2 import Empty
9+
from grpc import StatusCode
10+
11+
from frequenz.client.microgrid import PermissionDenied
12+
from tests.util import make_grpc_error
13+
14+
# No client_args or client_kwargs needed for this call
15+
16+
17+
def assert_stub_method_call(stub_method: Any) -> None:
18+
"""Assert that the gRPC request matches the expected request."""
19+
stub_method.assert_called_once_with(Empty(), timeout=60.0)
20+
21+
22+
grpc_response = make_grpc_error(StatusCode.PERMISSION_DENIED)
23+
24+
25+
def assert_client_exception(exception: Exception) -> None:
26+
"""Assert that the client exception matches the expected error."""
27+
assert isinstance(exception, PermissionDenied)
28+
assert exception.grpc_error == grpc_response
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Test data for successful microgrid info retrieval."""
5+
6+
from datetime import datetime, timezone
7+
from typing import Any
8+
from unittest.mock import AsyncMock
9+
10+
import pytest
11+
from frequenz.api.common.v1 import location_pb2
12+
from frequenz.api.common.v1.grid import delivery_area_pb2
13+
from frequenz.api.common.v1.microgrid import microgrid_pb2 as microgrid_common_pb2
14+
from frequenz.api.microgrid.v1 import microgrid_pb2
15+
from frequenz.client.base.conversion import to_timestamp
16+
from frequenz.client.common.microgrid import EnterpriseId, MicrogridId
17+
from google.protobuf.empty_pb2 import Empty
18+
19+
from frequenz.client.microgrid import (
20+
DeliveryArea,
21+
EnergyMarketCodeType,
22+
Location,
23+
MicrogridInfo,
24+
MicrogridStatus,
25+
)
26+
27+
# No client_args or client_kwargs needed for this call
28+
29+
30+
def assert_stub_method_call(stub_method: AsyncMock) -> None:
31+
"""Assert that the gRPC request matches the expected request."""
32+
stub_method.assert_called_once_with(Empty(), timeout=60.0)
33+
34+
35+
create_timestamp = datetime(2023, 1, 1, tzinfo=timezone.utc)
36+
grpc_response = microgrid_pb2.GetMicrogridMetadataResponse(
37+
microgrid=microgrid_common_pb2.Microgrid(
38+
id=1234,
39+
enterprise_id=5678,
40+
name="Test Microgrid",
41+
delivery_area=delivery_area_pb2.DeliveryArea(
42+
code="Test Delivery Area",
43+
code_type=delivery_area_pb2.EnergyMarketCodeType.ENERGY_MARKET_CODE_TYPE_EUROPE_EIC,
44+
),
45+
location=location_pb2.Location(
46+
latitude=37.7749, longitude=-122.4194, country_code="DE"
47+
),
48+
status=microgrid_common_pb2.MICROGRID_STATUS_INACTIVE,
49+
create_timestamp=to_timestamp(create_timestamp),
50+
)
51+
)
52+
53+
54+
def assert_client_result(result: Any) -> None:
55+
"""Assert that the client result matches the expected MicrogridInfo."""
56+
assert result == MicrogridInfo(
57+
id=MicrogridId(1234),
58+
enterprise_id=EnterpriseId(5678),
59+
name="Test Microgrid",
60+
delivery_area=DeliveryArea(
61+
code="Test Delivery Area", code_type=EnergyMarketCodeType.EUROPE_EIC
62+
),
63+
location=Location(
64+
latitude=pytest.approx(37.7749), # type: ignore[arg-type]
65+
longitude=pytest.approx(-122.4194), # type: ignore[arg-type]
66+
country_code="DE",
67+
),
68+
status=MicrogridStatus.INACTIVE,
69+
create_timestamp=datetime(2023, 1, 1, tzinfo=timezone.utc),
70+
)
71+
assert not result.is_active

tests/test_client.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
"""Tests for the MicrogridApiClient class."""
55

6+
from __future__ import annotations
7+
8+
from collections.abc import AsyncIterator
9+
from pathlib import Path
610

711
import pytest
812
from frequenz.api.microgrid.v1 import microgrid_pb2_grpc
@@ -15,10 +19,26 @@
1519
MicrogridApiClient,
1620
)
1721

18-
from .util import patch_client_class
22+
from .util import ApiClientTestCaseSpec, get_test_specs, patch_client_class
1923

2024
# pylint: disable=protected-access
2125

26+
TESTS_DIR = Path(__file__).parent / "client_test_cases"
27+
28+
29+
@pytest.fixture
30+
async def client() -> AsyncIterator[MicrogridApiClient]:
31+
"""Fixture that provides a MicrogridApiClient with a mock gRPC stub and channel."""
32+
with patch_client_class(MicrogridApiClient, microgrid_pb2_grpc.MicrogridStub):
33+
client = MicrogridApiClient(
34+
"grpc://localhost:1234",
35+
# Retry very fast to avoid long test times, and also not too many
36+
# times to avoid test hanging forever.
37+
retry_strategy=LinearBackoff(interval=0.0, jitter=0.0, limit=10),
38+
)
39+
async with client:
40+
yield client
41+
2242

2343
@patch_client_class(MicrogridApiClient, microgrid_pb2_grpc.MicrogridStub)
2444
def test_init_defaults() -> None:
@@ -64,3 +84,15 @@ def test_init_with_custom_retry_strategy() -> None:
6484
"grpc://localhost:1234", retry_strategy=retry_strategy, connect=False
6585
)
6686
client._retry_strategy = retry_strategy # pylint: disable=protected-access
87+
88+
89+
@pytest.mark.parametrize(
90+
"spec",
91+
get_test_specs("get_microgrid_info", tests_dir=TESTS_DIR),
92+
ids=str,
93+
)
94+
async def test_get_microgrid_info(
95+
client: MicrogridApiClient, spec: ApiClientTestCaseSpec
96+
) -> None:
97+
"""Test get_microgrid_info method."""
98+
await spec.test_unary_unary_call(client, "GetMicrogridMetadata")

0 commit comments

Comments
 (0)