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
2 changes: 1 addition & 1 deletion .github/containers/test-installation/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This Dockerfile is used to test the installation of the python package in
# multiple platforms in the CI. It is not used to build the package itself.

FROM --platform=${TARGETPLATFORM} python:3.11-slim
FROM python:3.11-slim

RUN apt-get update -y && \
apt-get install --no-install-recommends -y \
Expand Down
2 changes: 2 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
* The constructor parameter `channel_options` was renamed to `channels_defaults` to match the name used in `BaseApiClient`.
* The constructor now accepts a `connect` parameter, which is `True` by default. If set to `False`, the client will not connect to the server upon instantiation. You can connect later by calling the `connect()` method.

* The `frequenz-client-base` dependency was bumped to v0.8.0.

## New Features

- The client now inherits from `frequenz.client.base.BaseApiClient`, so it provides a few new features, like `disconnect()`ing or using it as a context manager. Please refer to the [`BaseApiClient` documentation](https://frequenz-floss.github.io/frequenz-client-base-python/latest/reference/frequenz/client/base/client/#frequenz.client.base.client.BaseApiClient) for more information on these features.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ requires-python = ">= 3.11, < 4"
dependencies = [
"frequenz-api-microgrid >= 0.15.3, < 0.16.0",
"frequenz-channels >= 1.0.0-rc1, < 2.0.0",
"frequenz-client-base >= 0.6.0, < 0.7",
"frequenz-client-base >= 0.8.0, < 0.9",
"grpcio >= 1.54.2, < 2",
"protobuf >= 4.21.6, < 6",
"timezonefinder >= 6.2.0, < 7",
Expand Down
31 changes: 23 additions & 8 deletions src/frequenz/client/microgrid/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

"""Client for requests to the Microgrid API."""

from __future__ import annotations

import asyncio
import logging
from collections.abc import Callable, Iterable, Set
Expand Down Expand Up @@ -31,7 +33,7 @@
)
from ._connection import Connection
from ._constants import RECEIVER_MAX_SIZE
from ._exception import ApiClientError
from ._exception import ApiClientError, ClientNotConnected
from ._metadata import Location, Metadata

DEFAULT_GRPC_CALL_TIMEOUT = 60.0
Expand Down Expand Up @@ -89,10 +91,20 @@ def __init__(
connect=connect,
channel_defaults=channel_defaults,
)
self._async_stub: microgrid_pb2_grpc.MicrogridAsyncStub = self.stub # type: ignore
self._broadcasters: dict[int, streaming.GrpcStreamBroadcaster[Any, Any]] = {}
self._retry_strategy = retry_strategy

@property
def stub(self) -> microgrid_pb2_grpc.MicrogridAsyncStub:
"""The gRPC stub for the API."""
if self.channel is None or self._stub is None:
raise ClientNotConnected(server_url=self.server_url, operation="stub")
# This type: ignore is needed because we need to cast the sync stub to
# the async stub, but we can't use cast because the async stub doesn't
# actually exists to the eyes of the interpreter, it only exists for the
# type-checker, so it can only be used for type hints.
return self._stub # type: ignore

async def components( # noqa: DOC502 (raises ApiClientError indirectly)
self,
) -> Iterable[Component]:
Expand All @@ -108,7 +120,7 @@ async def components( # noqa: DOC502 (raises ApiClientError indirectly)
"""
component_list = await client.call_stub_method(
self,
lambda: self._async_stub.ListComponents(
lambda: self.stub.ListComponents(
microgrid_pb2.ComponentFilter(),
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
),
Expand Down Expand Up @@ -145,7 +157,7 @@ async def metadata(self) -> Metadata:
try:
microgrid_metadata = await client.call_stub_method(
self,
lambda: self._async_stub.GetMicrogridMetadata(
lambda: self.stub.GetMicrogridMetadata(
Empty(),
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
),
Expand Down Expand Up @@ -192,7 +204,7 @@ async def connections( # noqa: DOC502 (raises ApiClientError indirectly)
self.components(),
client.call_stub_method(
self,
lambda: self._async_stub.ListConnections(
lambda: self.stub.ListConnections(
connection_filter,
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
),
Expand Down Expand Up @@ -249,12 +261,15 @@ async def _new_component_data_receiver(
broadcaster = streaming.GrpcStreamBroadcaster(
f"raw-component-data-{component_id}",
lambda: aiter(
self._async_stub.StreamComponentData(
self.stub.StreamComponentData(
microgrid_pb2.ComponentIdParam(id=component_id)
)
),
transform,
retry_strategy=self._retry_strategy,
# We don't expect any data stream to end, so if it is exhausted for any
# reason we want to keep retrying
retry_on_exhausted_stream=True,
)
self._broadcasters[component_id] = broadcaster
return broadcaster.new_receiver(maxsize=maxsize)
Expand Down Expand Up @@ -408,7 +423,7 @@ async def set_power( # noqa: DOC502 (raises ApiClientError indirectly)
"""
await client.call_stub_method(
self,
lambda: self._async_stub.SetPowerActive(
lambda: self.stub.SetPowerActive(
microgrid_pb2.SetPowerActiveParam(
component_id=component_id, power=power_w
),
Expand Down Expand Up @@ -447,7 +462,7 @@ async def set_bounds( # noqa: DOC503 (raises ApiClientError indirectly)
)
await client.call_stub_method(
self,
lambda: self._async_stub.AddInclusionBounds(
lambda: self.stub.AddInclusionBounds(
microgrid_pb2.SetBoundsParam(
component_id=component_id,
target_metric=target_metric,
Expand Down
1 change: 0 additions & 1 deletion tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def __init__(self, *, retry_strategy: retry.Strategy | None = None) -> None:
super().__init__("grpc://mock_host:1234", retry_strategy=retry_strategy)
self.mock_stub = mock_stub
self._stub = mock_stub # pylint: disable=protected-access
self._async_stub = mock_stub # pylint: disable=protected-access


async def test_components() -> None:
Expand Down