Skip to content
Open
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
10 changes: 7 additions & 3 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

<!-- Here goes a general summary of what this release is about -->

## Upgrading
## Deprecation

<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->
- 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

<!-- Here goes the main new features and examples or instructions on how to use them -->
- 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

Expand Down
11 changes: 11 additions & 0 deletions src/frequenz/client/common/metrics/proto/v1alpha8/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
34 changes: 34 additions & 0 deletions src/frequenz/client/common/metrics/proto/v1alpha8/_metric.py
Original file line number Diff line number Diff line change
@@ -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)
83 changes: 83 additions & 0 deletions tests/metrics/proto/v1alpha8/test_metric.py
Original file line number Diff line number Diff line change
@@ -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