From a6f37a3d55121a9aaa86fa9bef47d8157f4520d8 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 21 Sep 2026 12:38:43 +0000 Subject: [PATCH 1/2] Change `ElectricalComponentId` from `CID` to `ECID` `ComponentId` (the deprecated v0.3.x compatibility class restored in #287) and `ElectricalComponentId` both claimed the `str_prefix` "CID". Importing both triggered a `"Prefix 'CID' is already registered"` warning from `frequenz-core`'s `BaseId`, and two objects with the same numeric value would print identically (`CID42`) while comparing unequal and hashing differently, a subtle correctness hazard. The deprecated `ComponentId` keeps `CID` because that is its historical representation and changing it would defeat the compatibility it exists for. `ElectricalComponentId` moves to `ECID`. The prefix is output-only: `BaseId` uses it in `__str__` and the `str_prefix` property only. There is no parse-from-string path in `BaseId`, and IDs cross the wire as plain integers. This cannot break round-tripping, persistence or the protocol. The import-order test that previously asserted the duplicate prefix warning is updated to assert the warning is gone. Signed-off-by: Leandro Lucarella --- docs/user-guide/typed-ids.md | 2 +- .../common/microgrid/electrical_components/_ids.py | 2 +- .../microgrid/electrical_components/test_battery.py | 2 +- .../test_electrical_component_base.py | 4 ++-- .../test_electrical_component_connection.py | 2 +- .../electrical_components/test_ev_charger.py | 2 +- .../microgrid/electrical_components/test_inverter.py | 2 +- .../electrical_components/test_problematic.py | 4 ++-- .../test_problematic_connection.py | 2 +- tests/microgrid/test_ids.py | 12 ++++++++---- 10 files changed, 19 insertions(+), 15 deletions(-) diff --git a/docs/user-guide/typed-ids.md b/docs/user-guide/typed-ids.md index 83e55aa3..34d2f763 100644 --- a/docs/user-guide/typed-ids.md +++ b/docs/user-guide/typed-ids.md @@ -32,6 +32,6 @@ Construct an ID from its numeric value. IDs of the same type and value compare equal, so you can use them as dictionary keys or set members. IDs with the same number but different types are not equal. -The printed prefix identifies the kind of ID: `MID` for a microgrid, `CID` for +The printed prefix identifies the kind of ID: `MID` for a microgrid, `ECID` for an electrical component, `SID` for a sensor, and `EID` for an enterprise. All of these types are based on [`BaseId`][frequenz.core.id.BaseId]. diff --git a/src/frequenz/client/common/microgrid/electrical_components/_ids.py b/src/frequenz/client/common/microgrid/electrical_components/_ids.py index 37be5e91..f8ace826 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_ids.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_ids.py @@ -9,5 +9,5 @@ @final -class ElectricalComponentId(BaseId, str_prefix="CID"): +class ElectricalComponentId(BaseId, str_prefix="ECID"): """A unique identifier for a microgrid electrical component.""" diff --git a/tests/microgrid/electrical_components/test_battery.py b/tests/microgrid/electrical_components/test_battery.py index badea6e7..ae13a91a 100644 --- a/tests/microgrid/electrical_components/test_battery.py +++ b/tests/microgrid/electrical_components/test_battery.py @@ -164,4 +164,4 @@ def test_unrecognized_battery_str( _allow_construction=True, ) - assert str(battery) == "CID42:bat1:Battery:type=999" + assert str(battery) == "ECID42:bat1:Battery:type=999" diff --git a/tests/microgrid/electrical_components/test_electrical_component_base.py b/tests/microgrid/electrical_components/test_electrical_component_base.py index 7ce717cb..561373fa 100644 --- a/tests/microgrid/electrical_components/test_electrical_component_base.py +++ b/tests/microgrid/electrical_components/test_electrical_component_base.py @@ -341,8 +341,8 @@ def test_get_metric_config_bounds_int_key_invalid_raises_error() -> None: @pytest.mark.parametrize( "name,expected_str", [ - ("", "CID1::_TestElectricalComponent"), - ("test-component", "CID1:test-component:_TestElectricalComponent"), + ("", "ECID1::_TestElectricalComponent"), + ("test-component", "ECID1:test-component:_TestElectricalComponent"), ], ids=["no-name", "with-name"], ) diff --git a/tests/microgrid/electrical_components/test_electrical_component_connection.py b/tests/microgrid/electrical_components/test_electrical_component_connection.py index 34f05d87..62b3a524 100644 --- a/tests/microgrid/electrical_components/test_electrical_component_connection.py +++ b/tests/microgrid/electrical_components/test_electrical_component_connection.py @@ -129,7 +129,7 @@ def test_str() -> None: connection = ElectricalComponentConnection( source_id=ElectricalComponentId(1), destination_id=ElectricalComponentId(2) ) - assert str(connection) == "CID1->CID2" + assert str(connection) == "ECID1->ECID2" def test_equality_and_hash() -> None: diff --git a/tests/microgrid/electrical_components/test_ev_charger.py b/tests/microgrid/electrical_components/test_ev_charger.py index 9d480c34..d22cfd65 100644 --- a/tests/microgrid/electrical_components/test_ev_charger.py +++ b/tests/microgrid/electrical_components/test_ev_charger.py @@ -165,4 +165,4 @@ def test_unrecognized_ev_charger_str( _allow_construction=True, ) - assert str(charger) == "CID42:evc1:EvCharger:type=999" + assert str(charger) == "ECID42:evc1:EvCharger:type=999" diff --git a/tests/microgrid/electrical_components/test_inverter.py b/tests/microgrid/electrical_components/test_inverter.py index 3207c56a..519f996c 100644 --- a/tests/microgrid/electrical_components/test_inverter.py +++ b/tests/microgrid/electrical_components/test_inverter.py @@ -165,4 +165,4 @@ def test_unrecognized_inverter_str( _allow_construction=True, ) - assert str(inverter) == "CID42:inv1:Inverter:type=999" + assert str(inverter) == "ECID42:inv1:Inverter:type=999" diff --git a/tests/microgrid/electrical_components/test_problematic.py b/tests/microgrid/electrical_components/test_problematic.py index df8704fd..dd2a746a 100644 --- a/tests/microgrid/electrical_components/test_problematic.py +++ b/tests/microgrid/electrical_components/test_problematic.py @@ -144,7 +144,7 @@ def test_unrecognized_component_str( _allow_construction=True, ) - assert str(component) == "CID42:comp1:category=999" + assert str(component) == "ECID42:comp1:category=999" def test_mismatched_category_component_str( @@ -164,4 +164,4 @@ def test_mismatched_category_component_str( _allow_construction=True, ) - assert str(component) == "CID42:comp1:mismatched:category=BATTERY:kind=inverter" + assert str(component) == "ECID42:comp1:mismatched:category=BATTERY:kind=inverter" diff --git a/tests/microgrid/electrical_components/test_problematic_connection.py b/tests/microgrid/electrical_components/test_problematic_connection.py index d10bd2b7..6151fb15 100644 --- a/tests/microgrid/electrical_components/test_problematic_connection.py +++ b/tests/microgrid/electrical_components/test_problematic_connection.py @@ -89,7 +89,7 @@ def test_self_referencing_connection_str() -> None: source_id=ElectricalComponentId(7), destination_id=ElectricalComponentId(7), ) - assert str(connection) == "CID7->CID7" + assert str(connection) == "ECID7->ECID7" def test_self_referencing_connection_equality_and_hash() -> None: diff --git a/tests/microgrid/test_ids.py b/tests/microgrid/test_ids.py index 7d90e3cf..74003027 100644 --- a/tests/microgrid/test_ids.py +++ b/tests/microgrid/test_ids.py @@ -35,7 +35,7 @@ [ (EnterpriseId, "EID"), (MicrogridId, "MID"), - (ElectricalComponentId, "CID"), + (ElectricalComponentId, "ECID"), (SensorId, "SID"), ], ) @@ -101,8 +101,12 @@ def test_component_id_equality_and_validation() -> None: ), ], ) -def test_component_id_import_orders_keep_current_prefix_warning(imports: str) -> None: - """Test both import orders and acknowledge the current duplicate-prefix warning.""" +def test_component_id_import_orders_produce_no_prefix_warning(imports: str) -> None: + """Test both import orders emit no duplicate-prefix warning. + + ElectricalComponentId uses ECID and ComponentId uses CID, so they no + longer share a prefix regardless of import order. + """ source_root = Path(__file__).resolve().parents[2] / "src" script = f""" import logging @@ -123,7 +127,7 @@ def test_component_id_import_orders_keep_current_prefix_warning(imports: str) -> ) assert result.returncode == 0, result.stderr - assert "Prefix 'CID' is already registered" in result.stderr + assert "already registered" not in result.stderr def test_component_id_does_not_restore_legacy_enums() -> None: From ae3ecca21e51554fc57dc201c2495f616624f383 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Mon, 21 Sep 2026 12:38:54 +0000 Subject: [PATCH 2/2] Update release notes Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 91ed2c69..13cf6c95 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -18,7 +18,13 @@ There are a few intentional hard breaks too, all listed in the Upgrading section ## Upgrading -* `frequenz.client.common.microgrid.components.ComponentId` is restored as a deprecated compatibility class. It keeps its historical import path and remains a distinct type from `ElectricalComponentId`; importing both classes logs the existing duplicate `CID` prefix warning. This restores only `ComponentId`, not the removed `ComponentCategory`, `ComponentStateCode` or `ComponentErrorCode` symbols. The v0.4.0 release remains incompatible with users of the removed import path. Removal will be coordinated with downstream migration rather than tied automatically to v0.5.0. +* `frequenz.client.common.microgrid.components.ComponentId` is restored as a deprecated compatibility class. It keeps its historical import path and remains a distinct type from `ElectricalComponentId`. This restores only `ComponentId`, not the removed `ComponentCategory`, `ComponentStateCode` or `ComponentErrorCode` symbols. The v0.4.0 release remains incompatible with users of the removed import path. Removal will be coordinated with downstream migration rather than tied automatically to v0.5.0. + +* `frequenz.client.common.microgrid.electrical_components.ElectricalComponentId` now renders with the prefix `ECID` instead of `CID` (for example, `ECID42` instead of `CID42`). + + This is a display-only change. + + Anyone who adopted v0.4.0 and logs or displays an `ElectricalComponentId` will see `ECID42` where they previously saw `CID42`. No code changes are needed, only an update to any log-parsing rules or UI labels that checked for the `CID` prefix. * The `frequenz.client.common.microgrid.electrical_components.ElectricalComponentCategory` enum is now deprecated and will be removed in a future release. @@ -98,7 +104,7 @@ There are a few intentional hard breaks too, all listed in the Upgrading section * `frequenz.client.common.metrics.MetricConnection.__str__` renders as `{name}:{category}` (with `name` possibly empty); known categories render as their member name, the unspecified category renders as `cat=`, and unknown non-zero categories render as `cat=`. * `frequenz.client.common.metrics.MetricSample` gained a compact `__str__` (`metric=value`, plus `@connection` when a connection is set) instead of falling back to the dataclass `repr`. - * `UnrecognizedElectricalComponent`, `MismatchedCategoryElectricalComponent`, `UnrecognizedBattery`, `UnrecognizedEvCharger` and `UnrecognizedInverter` now expose their raw wire `category` / `type` in `__str__` (e.g. `CID1:comp1:Inverter:type=99`), instead of hiding it behind the class name alone. These values are merely unrecognized (forward-compatible), not invariant violations, so they use a plain `:field=value` detail rather than the `` marker. + * `UnrecognizedElectricalComponent`, `MismatchedCategoryElectricalComponent`, `UnrecognizedBattery`, `UnrecognizedEvCharger` and `UnrecognizedInverter` now expose their raw wire `category` / `type` in `__str__` (e.g. `ECID1:comp1:Inverter:type=99`), instead of hiding it behind the class name alone. These values are merely unrecognized (forward-compatible), not invariant violations, so they use a plain `:field=value` detail rather than the `` marker. * `frequenz.client.common.metrics.MetricSample.bounds` is now deprecated; use `bounds_set` instead.