diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 80e5f0cb..392a36b8 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,11 +6,16 @@ ## Upgrading - +- `ApiClient`: + + * The class was renamed to `MicrogridApiClient`. + * The `api` attribute was renamed to `stub`. + * 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. ## 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. ## Bug Fixes diff --git a/src/frequenz/client/microgrid/__init__.py b/src/frequenz/client/microgrid/__init__.py index 6bb3a418..56e9cedb 100644 --- a/src/frequenz/client/microgrid/__init__.py +++ b/src/frequenz/client/microgrid/__init__.py @@ -7,7 +7,7 @@ """ -from ._client import ApiClient +from ._client import MicrogridApiClient from ._component import ( Component, ComponentCategory, @@ -65,7 +65,7 @@ from ._metadata import Location, Metadata __all__ = [ - "ApiClient", + "MicrogridApiClient", "ApiClientError", "BatteryComponentState", "BatteryData", diff --git a/src/frequenz/client/microgrid/_client.py b/src/frequenz/client/microgrid/_client.py index 71775380..55de9a9b 100644 --- a/src/frequenz/client/microgrid/_client.py +++ b/src/frequenz/client/microgrid/_client.py @@ -13,7 +13,7 @@ from frequenz.api.common import components_pb2, metrics_pb2 from frequenz.api.microgrid import microgrid_pb2, microgrid_pb2_grpc from frequenz.channels import Receiver -from frequenz.client.base import channel, retry, streaming +from frequenz.client.base import channel, client, retry, streaming from google.protobuf.empty_pb2 import Empty from google.protobuf.timestamp_pb2 import Timestamp @@ -55,14 +55,15 @@ """ -class ApiClient: +class MicrogridApiClient(client.BaseApiClient[microgrid_pb2_grpc.MicrogridStub]): """A microgrid API client.""" def __init__( self, server_url: str, *, - channel_options: channel.ChannelOptions = DEFAULT_CHANNEL_OPTIONS, + channel_defaults: channel.ChannelOptions = DEFAULT_CHANNEL_OPTIONS, + connect: bool = True, retry_strategy: retry.Strategy | None = None, ) -> None: """Initialize the class instance. @@ -74,28 +75,25 @@ def __init__( where the `port` should be an int between 0 and 65535 (defaulting to 9090) and `ssl` should be a boolean (defaulting to `false`). For example: `grpc://localhost:1090?ssl=true`. - channel_options: The default options use to create the channel when not + channel_defaults: The default options use to create the channel when not specified in the URL. + connect: Whether to connect to the server as soon as a client instance is + created. If `False`, the client will not connect to the server until + [connect()][frequenz.client.base.client.BaseApiClient.connect] is + called. retry_strategy: The retry strategy to use to reconnect when the connection to the streaming method is lost. By default a linear backoff strategy is used. """ - self._server_url = server_url - """The location of the microgrid API server as a URL.""" - - self.api = microgrid_pb2_grpc.MicrogridStub( - channel.parse_grpc_uri(server_url, defaults=channel_options) + super().__init__( + server_url, + microgrid_pb2_grpc.MicrogridStub, + connect=connect, + channel_defaults=channel_defaults, ) - """The gRPC stub for the microgrid API.""" - self._broadcasters: dict[int, streaming.GrpcStreamBroadcaster[Any, Any]] = {} self._retry_strategy = retry_strategy - @property - def server_url(self) -> str: - """The server location in URL format.""" - return self._server_url - async def components(self) -> Iterable[Component]: """Fetch all the components present in the microgrid. @@ -112,7 +110,7 @@ async def components(self) -> Iterable[Component]: # but it is component_list = await cast( Awaitable[microgrid_pb2.ComponentList], - self.api.ListComponents( + self.stub.ListComponents( microgrid_pb2.ComponentFilter(), timeout=int(DEFAULT_GRPC_CALL_TIMEOUT), ), @@ -154,7 +152,7 @@ async def metadata(self) -> Metadata: try: microgrid_metadata = await cast( Awaitable[microgrid_pb2.MicrogridMetadata], - self.api.GetMicrogridMetadata( + self.stub.GetMicrogridMetadata( Empty(), timeout=int(DEFAULT_GRPC_CALL_TIMEOUT), ), @@ -203,7 +201,7 @@ async def connections( # awaitable, but it is cast( Awaitable[microgrid_pb2.ConnectionList], - self.api.ListConnections( + self.stub.ListConnections( connection_filter, timeout=int(DEFAULT_GRPC_CALL_TIMEOUT), ), @@ -269,7 +267,7 @@ async def _new_component_data_receiver( # microgrid_pb2.ComponentData], which it is. lambda: cast( AsyncIterator[microgrid_pb2.ComponentData], - self.api.StreamComponentData( + self.stub.StreamComponentData( microgrid_pb2.ComponentIdParam(id=component_id) ), ), @@ -427,7 +425,7 @@ async def set_power(self, component_id: int, power_w: float) -> None: try: await cast( Awaitable[Empty], - self.api.SetPowerActive( + self.stub.SetPowerActive( microgrid_pb2.SetPowerActiveParam( component_id=component_id, power=power_w ), @@ -472,7 +470,7 @@ async def set_bounds( try: await cast( Awaitable[Timestamp], - self.api.AddInclusionBounds( + self.stub.AddInclusionBounds( microgrid_pb2.SetBoundsParam( component_id=component_id, target_metric=target_metric, diff --git a/tests/test_client.py b/tests/test_client.py index ff0c308e..6ca18475 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -16,7 +16,6 @@ from frequenz.client.base import retry from frequenz.client.microgrid import ( - ApiClient, ApiClientError, BatteryData, Component, @@ -28,11 +27,12 @@ InverterData, InverterType, MeterData, + MicrogridApiClient, ) from frequenz.client.microgrid._connection import Connection -class _TestClient(ApiClient): +class _TestClient(MicrogridApiClient): def __init__(self, *, retry_strategy: retry.Strategy | None = None) -> None: # Here we sadly can't use spec=MicrogridStub because the generated stub typing # is a mess, and for some reason inspection of gRPC methods doesn't work. @@ -46,7 +46,7 @@ def __init__(self, *, retry_strategy: retry.Strategy | None = None) -> None: mock_stub.StreamComponentData = mock.Mock("StreamComponentData") super().__init__("grpc://mock_host:1234", retry_strategy=retry_strategy) self.mock_stub = mock_stub - self.api = mock_stub + self._stub = mock_stub # pylint: disable=protected-access async def test_components() -> None: