From 73674ebae44406343f731835f49708c2426010d9 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 23 Jan 2026 13:24:46 +0100 Subject: [PATCH 1/2] Add metric from/to protobuf v1alpha8 conversion To make sure we can decouple the clients from the underlying protocol, we need to make sure we always use explicit conversion between Python enums and protobuf enums. This is especially important to be able to support multiple versions of a protobuf message, for example an upcoming v1alpha9 Metric enum, so downstream project can migrate to new versions in a backwards-compatible way. Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 10 ++- .../common/metrics/proto/v1alpha8/__init__.py | 11 +++ .../common/metrics/proto/v1alpha8/_metric.py | 34 ++++++++ tests/metrics/proto/v1alpha8/test_metric.py | 83 +++++++++++++++++++ 4 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py create mode 100644 src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py create mode 100644 tests/metrics/proto/v1alpha8/test_metric.py diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 92616f8..1c9106f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,13 +4,17 @@ -## Upgrading +## Deprecation - +- Converting `Metric` enums from/to protobuf directly is deprecated and will be dropped in the next breaking release. + + You should switch to use the new conversion functions in `frequenz.client.common.metrics.proto.v1alpha8` to convert from/to protobuf. + + Since we can't emit deprecation messages for this (as they will trigger every time a metric value is used), please consider using the new conversion functions as soon as possible so the migration to the next breaking release is smooth. ## New Features - +- A new module `frequenz.client.common.metrics.proto.v1alpha8` has been added to provide conversion functions for `Metric`s from/to protobuf version `v1alpha8`. ## Bug Fixes diff --git a/src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py b/src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py new file mode 100644 index 0000000..53b0a69 --- /dev/null +++ b/src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py @@ -0,0 +1,11 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Conversion of Metric from/to protobuf v1alpha8.""" + +from ._metric import metric_from_proto, metric_to_proto + +__all__ = [ + "metric_from_proto", + "metric_to_proto", +] diff --git a/src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py b/src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py new file mode 100644 index 0000000..aafe59d --- /dev/null +++ b/src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py @@ -0,0 +1,34 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Coversion of Metric to/from protobuf v1alpha8.""" + + +from frequenz.api.common.v1alpha8.metrics import metrics_pb2 + +from ....proto import enum_from_proto +from ..._metric import Metric + + +def metric_from_proto(message: metrics_pb2.Metric.ValueType) -> Metric | int: + """Convert a protobuf Metric message to a Metric enum member. + + Args: + message: A protobuf Metric message. + + Returns: + The corresponding Metric enum member. + """ + return enum_from_proto(message, Metric) + + +def metric_to_proto(metric: Metric) -> metrics_pb2.Metric.ValueType: + """Convert a Metric enum member to a protobuf Metric message. + + Args: + metric: A Metric enum member. + + Returns: + The corresponding protobuf Metric message. + """ + return metrics_pb2.Metric.ValueType(metric.value) diff --git a/tests/metrics/proto/v1alpha8/test_metric.py b/tests/metrics/proto/v1alpha8/test_metric.py new file mode 100644 index 0000000..0de0650 --- /dev/null +++ b/tests/metrics/proto/v1alpha8/test_metric.py @@ -0,0 +1,83 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Tests for Metric to/from protobuf v1alpha8 conversion. + +These tests ensure that, for this version, all enum members are correctly matched by +name and value between the Python `Metric` enum and the protobuf `Metric` enum. +""" + +import pytest +from frequenz.api.common.v1alpha8.metrics import metrics_pb2 + +from frequenz.client.common.metrics import Metric +from frequenz.client.common.metrics.proto.v1alpha8 import ( + metric_from_proto, + metric_to_proto, +) + +PB_NAMES: list[str] = [m.name for m in metrics_pb2.Metric.DESCRIPTOR.values] + +UNKNOWN_PB_VALUE = metrics_pb2.Metric.ValueType(max(m.value for m in Metric) + 1) + + +@pytest.mark.parametrize("pb_name", PB_NAMES) +def test_proto_enum_matches_enum_name(pb_name: str) -> None: + """Test that all known protobuf enum names have a matching Metric enum member.""" + pb_value = metrics_pb2.Metric.Value(pb_name) + try: + metric = Metric[pb_name.removeprefix("METRIC_")] + assert metric.value == pb_value + except KeyError: + pass # It is OK to have new protobuf enum values not yet in Metric. + + +@pytest.mark.parametrize("pb_name", PB_NAMES) +def test_proto_enum_matches_enum_value(pb_name: str) -> None: + """Test that all known protobuf enum values have a matching Metric enum member.""" + pb_value = metrics_pb2.Metric.Value(pb_name) + try: + metric = Metric(pb_value) + assert metric.value == pb_value + except ValueError: + pass # It is OK to have new protobuf enum values not yet in Metric. + + +@pytest.mark.parametrize("metric", list(Metric), ids=lambda m: m.name) +def test_enum_matches_proto_enum_name(metric: Metric) -> None: + """Test that all Metric enum members have a matching protobuf enum name.""" + pb_value = metrics_pb2.Metric.ValueType(metric.value) + pb_name = metrics_pb2.Metric.Name(pb_value) + assert pb_name == f"METRIC_{metric.name}" + + +@pytest.mark.parametrize("metric", list(Metric), ids=lambda m: m.name) +def test_enum_matches_proto_enum_value(metric: Metric) -> None: + """Test that all Metric enum members have a matching protobuf enum value.""" + pb_value = metrics_pb2.Metric.Value(f"METRIC_{metric.name}") + assert metric.value == pb_value + + +@pytest.mark.parametrize("pb_name", PB_NAMES) +def test_from_proto(pb_name: str) -> None: + """Test conversion from protobuf returns a matching member or the int for unknown values.""" + pb_value = metrics_pb2.Metric.Value(pb_name) + metric = metric_from_proto(pb_value) + if pb_value in [m.value for m in Metric]: + assert metric is Metric(pb_value) + else: + assert metric == pb_value + + +def test_from_proto_unknown() -> None: + """Test conversion from protobuf for yet unknown values return the int.""" + metric = metric_from_proto(UNKNOWN_PB_VALUE) + assert isinstance(metric, int) + assert metric == UNKNOWN_PB_VALUE + + +@pytest.mark.parametrize("metric", list(Metric), ids=lambda m: m.name) +def test_to_proto(metric: Metric) -> None: + """Test conversion to protobuf return a matching protobuf value.""" + pb_value = metric_to_proto(metric) + assert pb_value == metric.value From a790a8c379cb527ec7f2371176b756b56adce5a4 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Fri, 23 Jan 2026 13:28:49 +0100 Subject: [PATCH 2/2] Replace Metric enum values with plain ints This ensures that enum values are not accidentally passed to protobuf functions without conversion. Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 10 +- src/frequenz/client/common/metrics/_metric.py | 167 ++++++++---------- .../proto/test_sample_metric_sample.py | 13 +- tests/metrics/proto/v1alpha8/test_metric.py | 10 ++ 4 files changed, 91 insertions(+), 109 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 1c9106f..e50d1f6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,17 +4,13 @@ -## Deprecation +## Upgrading -- Converting `Metric` enums from/to protobuf directly is deprecated and will be dropped in the next breaking release. - - You should switch to use the new conversion functions in `frequenz.client.common.metrics.proto.v1alpha8` to convert from/to protobuf. - - Since we can't emit deprecation messages for this (as they will trigger every time a metric value is used), please consider using the new conversion functions as soon as possible so the migration to the next breaking release is smooth. +- Converting `Metric` enums from/to protobuf directly is not supported anymore, you need to use explicit conversion functions, like the ones in `frequenz.client.common.metrics.proto.v1alpha8`. ## New Features -- A new module `frequenz.client.common.metrics.proto.v1alpha8` has been added to provide conversion functions for `Metric`s from/to protobuf version `v1alpha8`. + ## Bug Fixes diff --git a/src/frequenz/client/common/metrics/_metric.py b/src/frequenz/client/common/metrics/_metric.py index e180296..02dc0a4 100644 --- a/src/frequenz/client/common/metrics/_metric.py +++ b/src/frequenz/client/common/metrics/_metric.py @@ -3,11 +3,8 @@ """Supported metrics for microgrid components.""" - import enum -from frequenz.api.common.v1alpha8.metrics import metrics_pb2 - @enum.unique class Metric(enum.Enum): @@ -42,237 +39,215 @@ class Metric(enum.Enum): period, and therefore can be inconsistent. """ - UNSPECIFIED = metrics_pb2.METRIC_UNSPECIFIED + UNSPECIFIED = 0 """The metric is unspecified (this should not be used).""" - DC_VOLTAGE = metrics_pb2.METRIC_DC_VOLTAGE + DC_VOLTAGE = 1 """The DC voltage.""" - DC_CURRENT = metrics_pb2.METRIC_DC_CURRENT + DC_CURRENT = 2 """The DC current.""" - DC_POWER = metrics_pb2.METRIC_DC_POWER + DC_POWER = 3 """The DC power.""" - AC_FREQUENCY = metrics_pb2.METRIC_AC_FREQUENCY + AC_FREQUENCY = 10 """The AC frequency.""" - AC_VOLTAGE = metrics_pb2.METRIC_AC_VOLTAGE + AC_VOLTAGE = 11 """The AC electric potential difference.""" - AC_VOLTAGE_PHASE_1_N = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_1_N + AC_VOLTAGE_PHASE_1_N = 12 """The AC electric potential difference between phase 1 and neutral.""" - AC_VOLTAGE_PHASE_2_N = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_2_N + AC_VOLTAGE_PHASE_2_N = 13 """The AC electric potential difference between phase 2 and neutral.""" - AC_VOLTAGE_PHASE_3_N = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_3_N + AC_VOLTAGE_PHASE_3_N = 14 """The AC electric potential difference between phase 3 and neutral.""" - AC_VOLTAGE_PHASE_1_PHASE_2 = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_1_PHASE_2 + AC_VOLTAGE_PHASE_1_PHASE_2 = 15 """The AC electric potential difference between phase 1 and phase 2.""" - AC_VOLTAGE_PHASE_2_PHASE_3 = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_2_PHASE_3 + AC_VOLTAGE_PHASE_2_PHASE_3 = 16 """The AC electric potential difference between phase 2 and phase 3.""" - AC_VOLTAGE_PHASE_3_PHASE_1 = metrics_pb2.METRIC_AC_VOLTAGE_PHASE_3_PHASE_1 + AC_VOLTAGE_PHASE_3_PHASE_1 = 17 """The AC electric potential difference between phase 3 and phase 1.""" - AC_CURRENT = metrics_pb2.METRIC_AC_CURRENT + AC_CURRENT = 18 """The AC current.""" - AC_CURRENT_PHASE_1 = metrics_pb2.METRIC_AC_CURRENT_PHASE_1 + AC_CURRENT_PHASE_1 = 19 """The AC current in phase 1.""" - AC_CURRENT_PHASE_2 = metrics_pb2.METRIC_AC_CURRENT_PHASE_2 + AC_CURRENT_PHASE_2 = 20 """The AC current in phase 2.""" - AC_CURRENT_PHASE_3 = metrics_pb2.METRIC_AC_CURRENT_PHASE_3 + AC_CURRENT_PHASE_3 = 21 """The AC current in phase 3.""" - AC_POWER_APPARENT = metrics_pb2.METRIC_AC_POWER_APPARENT + AC_POWER_APPARENT = 22 """The AC apparent power.""" - AC_POWER_APPARENT_PHASE_1 = metrics_pb2.METRIC_AC_POWER_APPARENT_PHASE_1 + AC_POWER_APPARENT_PHASE_1 = 23 """The AC apparent power in phase 1.""" - AC_POWER_APPARENT_PHASE_2 = metrics_pb2.METRIC_AC_POWER_APPARENT_PHASE_2 + AC_POWER_APPARENT_PHASE_2 = 24 """The AC apparent power in phase 2.""" - AC_POWER_APPARENT_PHASE_3 = metrics_pb2.METRIC_AC_POWER_APPARENT_PHASE_3 + AC_POWER_APPARENT_PHASE_3 = 25 """The AC apparent power in phase 3.""" - AC_POWER_ACTIVE = metrics_pb2.METRIC_AC_POWER_ACTIVE + AC_POWER_ACTIVE = 26 """The AC active power.""" - AC_POWER_ACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_POWER_ACTIVE_PHASE_1 + AC_POWER_ACTIVE_PHASE_1 = 27 """The AC active power in phase 1.""" - AC_POWER_ACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_POWER_ACTIVE_PHASE_2 + AC_POWER_ACTIVE_PHASE_2 = 28 """The AC active power in phase 2.""" - AC_POWER_ACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_POWER_ACTIVE_PHASE_3 + AC_POWER_ACTIVE_PHASE_3 = 29 """The AC active power in phase 3.""" - AC_POWER_REACTIVE = metrics_pb2.METRIC_AC_POWER_REACTIVE + AC_POWER_REACTIVE = 30 """The AC reactive power.""" - AC_POWER_REACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_POWER_REACTIVE_PHASE_1 + AC_POWER_REACTIVE_PHASE_1 = 31 """The AC reactive power in phase 1.""" - AC_POWER_REACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_POWER_REACTIVE_PHASE_2 + AC_POWER_REACTIVE_PHASE_2 = 32 """The AC reactive power in phase 2.""" - AC_POWER_REACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_POWER_REACTIVE_PHASE_3 + AC_POWER_REACTIVE_PHASE_3 = 33 """The AC reactive power in phase 3.""" - AC_POWER_FACTOR = metrics_pb2.METRIC_AC_POWER_FACTOR + AC_POWER_FACTOR = 40 """The AC power factor.""" - AC_POWER_FACTOR_PHASE_1 = metrics_pb2.METRIC_AC_POWER_FACTOR_PHASE_1 + AC_POWER_FACTOR_PHASE_1 = 41 """The AC power factor in phase 1.""" - AC_POWER_FACTOR_PHASE_2 = metrics_pb2.METRIC_AC_POWER_FACTOR_PHASE_2 + AC_POWER_FACTOR_PHASE_2 = 42 """The AC power factor in phase 2.""" - AC_POWER_FACTOR_PHASE_3 = metrics_pb2.METRIC_AC_POWER_FACTOR_PHASE_3 + AC_POWER_FACTOR_PHASE_3 = 43 """The AC power factor in phase 3.""" - AC_ENERGY_APPARENT = metrics_pb2.METRIC_AC_ENERGY_APPARENT + AC_ENERGY_APPARENT = 50 """The AC apparent energy.""" - AC_ENERGY_APPARENT_PHASE_1 = metrics_pb2.METRIC_AC_ENERGY_APPARENT_PHASE_1 + AC_ENERGY_APPARENT_PHASE_1 = 51 """The AC apparent energy in phase 1.""" - AC_ENERGY_APPARENT_PHASE_2 = metrics_pb2.METRIC_AC_ENERGY_APPARENT_PHASE_2 + AC_ENERGY_APPARENT_PHASE_2 = 52 """The AC apparent energy in phase 2.""" - AC_ENERGY_APPARENT_PHASE_3 = metrics_pb2.METRIC_AC_ENERGY_APPARENT_PHASE_3 + AC_ENERGY_APPARENT_PHASE_3 = 53 """The AC apparent energy in phase 3.""" - AC_ENERGY_ACTIVE = metrics_pb2.METRIC_AC_ENERGY_ACTIVE + AC_ENERGY_ACTIVE = 54 """The AC active energy.""" - AC_ENERGY_ACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_PHASE_1 + AC_ENERGY_ACTIVE_PHASE_1 = 55 """The AC active energy in phase 1.""" - AC_ENERGY_ACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_PHASE_2 + AC_ENERGY_ACTIVE_PHASE_2 = 56 """The AC active energy in phase 2.""" - AC_ENERGY_ACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_PHASE_3 + AC_ENERGY_ACTIVE_PHASE_3 = 57 """The AC active energy in phase 3.""" - AC_ENERGY_ACTIVE_CONSUMED = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED + AC_ENERGY_ACTIVE_CONSUMED = 58 """The AC active energy consumed.""" - AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 - ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 = 59 """The AC active energy consumed in phase 1.""" - AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 - ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 = 60 """The AC active energy consumed in phase 2.""" - AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 - ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 = 61 """The AC active energy consumed in phase 3.""" - AC_ENERGY_ACTIVE_DELIVERED = metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED + AC_ENERGY_ACTIVE_DELIVERED = 62 """The AC active energy delivered.""" - AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 - ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 = 63 """The AC active energy delivered in phase 1.""" - AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 - ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 = 64 """The AC active energy delivered in phase 2.""" - AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 = ( - metrics_pb2.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 - ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 = 65 """The AC active energy delivered in phase 3.""" - AC_ENERGY_REACTIVE = metrics_pb2.METRIC_AC_ENERGY_REACTIVE + AC_ENERGY_REACTIVE = 66 """The AC reactive energy.""" - AC_ENERGY_REACTIVE_PHASE_1 = metrics_pb2.METRIC_AC_ENERGY_REACTIVE_PHASE_1 + AC_ENERGY_REACTIVE_PHASE_1 = 67 """The AC reactive energy in phase 1.""" - AC_ENERGY_REACTIVE_PHASE_2 = metrics_pb2.METRIC_AC_ENERGY_REACTIVE_PHASE_2 + AC_ENERGY_REACTIVE_PHASE_2 = 68 """The AC reactive energy in phase 2.""" - AC_ENERGY_REACTIVE_PHASE_3 = metrics_pb2.METRIC_AC_ENERGY_REACTIVE_PHASE_3 + AC_ENERGY_REACTIVE_PHASE_3 = 69 """The AC reactive energy in phase 3.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT = 80 """The AC total harmonic distortion current.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 = 81 """The AC total harmonic distortion current in phase 1.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 = 82 """The AC total harmonic distortion current in phase 2.""" - AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 = ( - metrics_pb2.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 - ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 = 83 """The AC total harmonic distortion current in phase 3.""" - BATTERY_CAPACITY = metrics_pb2.METRIC_BATTERY_CAPACITY + BATTERY_CAPACITY = 100 """The capacity of the battery.""" - BATTERY_SOC_PCT = metrics_pb2.METRIC_BATTERY_SOC_PCT + BATTERY_SOC_PCT = 101 """The state of charge of the battery as a percentage.""" - BATTERY_TEMPERATURE = metrics_pb2.METRIC_BATTERY_TEMPERATURE + BATTERY_TEMPERATURE = 102 """The temperature of the battery.""" - INVERTER_TEMPERATURE = metrics_pb2.METRIC_INVERTER_TEMPERATURE + INVERTER_TEMPERATURE = 120 """The temperature of the inverter.""" - INVERTER_TEMPERATURE_CABINET = metrics_pb2.METRIC_INVERTER_TEMPERATURE_CABINET + INVERTER_TEMPERATURE_CABINET = 121 """The temperature of the inverter cabinet.""" - INVERTER_TEMPERATURE_HEATSINK = metrics_pb2.METRIC_INVERTER_TEMPERATURE_HEATSINK + INVERTER_TEMPERATURE_HEATSINK = 122 """The temperature of the inverter heatsink.""" - INVERTER_TEMPERATURE_TRANSFORMER = ( - metrics_pb2.METRIC_INVERTER_TEMPERATURE_TRANSFORMER - ) + INVERTER_TEMPERATURE_TRANSFORMER = 123 """The temperature of the inverter transformer.""" - EV_CHARGER_TEMPERATURE = metrics_pb2.METRIC_EV_CHARGER_TEMPERATURE + EV_CHARGER_TEMPERATURE = 140 """The temperature of the EV charger.""" - SENSOR_WIND_SPEED = metrics_pb2.METRIC_SENSOR_WIND_SPEED + SENSOR_WIND_SPEED = 160 """The speed of the wind measured.""" - SENSOR_WIND_DIRECTION = metrics_pb2.METRIC_SENSOR_WIND_DIRECTION + SENSOR_WIND_DIRECTION = 161 """The direction of the wind measured.""" - SENSOR_TEMPERATURE = metrics_pb2.METRIC_SENSOR_TEMPERATURE + SENSOR_TEMPERATURE = 162 """The temperature measured.""" - SENSOR_RELATIVE_HUMIDITY = metrics_pb2.METRIC_SENSOR_RELATIVE_HUMIDITY + SENSOR_RELATIVE_HUMIDITY = 163 """The relative humidity measured.""" - SENSOR_DEW_POINT = metrics_pb2.METRIC_SENSOR_DEW_POINT + SENSOR_DEW_POINT = 164 """The dew point measured.""" - SENSOR_AIR_PRESSURE = metrics_pb2.METRIC_SENSOR_AIR_PRESSURE + SENSOR_AIR_PRESSURE = 165 """The air pressure measured.""" - SENSOR_IRRADIANCE = metrics_pb2.METRIC_SENSOR_IRRADIANCE + SENSOR_IRRADIANCE = 166 """The irradiance measured.""" diff --git a/tests/metrics/proto/test_sample_metric_sample.py b/tests/metrics/proto/test_sample_metric_sample.py index fe48490..b4aae81 100644 --- a/tests/metrics/proto/test_sample_metric_sample.py +++ b/tests/metrics/proto/test_sample_metric_sample.py @@ -20,6 +20,7 @@ MetricSample, ) from frequenz.client.common.metrics.proto import metric_sample_from_proto_with_issues +from frequenz.client.common.metrics.proto.v1alpha8 import metric_to_proto DATETIME: Final[datetime] = datetime(2023, 3, 15, 12, 0, 0, tzinfo=timezone.utc) TIMESTAMP: Final[Timestamp] = Timestamp(seconds=int(DATETIME.timestamp())) @@ -52,7 +53,7 @@ class _TestCase: name="simple_value", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), @@ -69,7 +70,7 @@ class _TestCase: name="aggregated_value", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( aggregated_metric=metrics_pb2.AggregatedMetricValue( avg_value=5.0, min_value=1.0, max_value=10.0 @@ -88,7 +89,7 @@ class _TestCase: name="no_value", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), ), expected_sample=MetricSample( sample_time=DATETIME, @@ -115,7 +116,7 @@ class _TestCase: name="with_valid_bounds", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), @@ -133,7 +134,7 @@ class _TestCase: name="with_invalid_bounds", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), @@ -160,7 +161,7 @@ class _TestCase: name="with_connection", proto_message=metrics_pb2.MetricSample( sample_time=TIMESTAMP, - metric=Metric.AC_POWER_ACTIVE.value, + metric=metric_to_proto(Metric.AC_POWER_ACTIVE), value=metrics_pb2.MetricValueVariant( simple_metric=metrics_pb2.SimpleMetricValue(value=5.0) ), diff --git a/tests/metrics/proto/v1alpha8/test_metric.py b/tests/metrics/proto/v1alpha8/test_metric.py index 0de0650..c7ff84b 100644 --- a/tests/metrics/proto/v1alpha8/test_metric.py +++ b/tests/metrics/proto/v1alpha8/test_metric.py @@ -21,6 +21,16 @@ UNKNOWN_PB_VALUE = metrics_pb2.Metric.ValueType(max(m.value for m in Metric) + 1) +def test_no_implicit_to_proto_conversion() -> None: + """Test that protobuf enum values are not implicitly convertible to Metric enum members.""" + # mypy should complain about this assignment, so we ignore the type check here. + # If mypy doesn't find an issue with this conversion, it should complain about + # the ignore comment having no effect. + metric = list(Metric)[0] + _: metrics_pb2.Metric.ValueType = metric.value # type: ignore[assignment] + metrics_pb2.Metric.Name(metric.value) # type: ignore[arg-type] + + @pytest.mark.parametrize("pb_name", PB_NAMES) def test_proto_enum_matches_enum_name(pb_name: str) -> None: """Test that all known protobuf enum names have a matching Metric enum member."""