diff --git a/src/frequenz/client/common/AGENTS.md b/src/frequenz/client/common/AGENTS.md index 532101f0..f7b6a25f 100644 --- a/src/frequenz/client/common/AGENTS.md +++ b/src/frequenz/client/common/AGENTS.md @@ -23,10 +23,16 @@ same shape; learn it once and apply everywhere. Domains: `grid`, `metrics`, `microgrid` (+ `electrical_components`, `sensors`), `pagination`, `streaming`, `types`. +Exception: `test`. This module is not a *domain*, it defines testing utilities +for downstream users, they don't wrap protobuf messages. + ## CORE RULES -- **Public symbols live in `_name.py`, exported via the package `__init__.py`.** Importers use - `from frequenz.client.common.metrics import Metric`, never the underscore module path. +- **Public symbols live in `_name.py`, exported via the package `__init__.py`.** External + importers never use the underscore module path. +- **Internal cross-module imports are ALWAYS relative and use the real symbol + location**, using the public export can lead to circular imports or + import-order issues. - **`__all__` is always present and alphabetically sorted** in every `__init__.py`. - **Conversion functions are ALWAYS keyed by the `frequenz.api.common` API namespace** and live at `frequenz.client.common..proto.` — currently **only `v1alpha8` exists**. @@ -65,5 +71,5 @@ Domains: `grid`, `metrics`, `microgrid` (+ `electrical_components`, `sensors`), ## DON'T - No `as any`-style escapes / `# type: ignore` to silence mypy strict. -- Don't import proto modules from pure-type modules. +- Don't import protobuf-generated modules from pure-type modules. - Don't add a public symbol without adding it to the domain `__init__.py` `__all__`. diff --git a/src/frequenz/client/common/microgrid/electrical_components/__init__.py b/src/frequenz/client/common/microgrid/electrical_components/__init__.py index 2c7ff71a..66875563 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/__init__.py +++ b/src/frequenz/client/common/microgrid/electrical_components/__init__.py @@ -12,6 +12,7 @@ UnrecognizedBattery, UnspecifiedBattery, ) +from ._breaker import Breaker from ._category import ElectricalComponentCategory from ._chp import Chp from ._converter import Converter @@ -39,7 +40,7 @@ Inverter, InverterType, InverterTypes, - SolarInverter, + PvInverter, UnrecognizedInverter, UnspecifiedInverter, ) @@ -47,19 +48,18 @@ from ._power_transformer import PowerTransformer from ._precharger import Precharger from ._problematic import ( - MismatchedCategoryComponent, - ProblematicComponent, - UnrecognizedComponent, - UnspecifiedComponent, + MismatchedCategoryElectricalComponent, + ProblematicElectricalComponent, + UnrecognizedElectricalComponent, + UnspecifiedElectricalComponent, ) -from ._relay import Relay from ._state_code import ElectricalComponentStateCode from ._steam_boiler import SteamBoiler from ._types import ( ComponentTypes, - ProblematicComponentTypes, - UnrecognizedComponentTypes, - UnspecifiedComponentTypes, + ProblematicElectricalComponentTypes, + UnrecognizedElectricalComponentTypes, + UnspecifiedElectricalComponentTypes, ) from ._wind_turbine import WindTurbine @@ -69,6 +69,7 @@ "BatteryInverter", "BatteryType", "BatteryTypes", + "Breaker", "Chp", "ComponentTypes", "Converter", @@ -93,23 +94,22 @@ "InverterTypes", "LiIonBattery", "Meter", - "MismatchedCategoryComponent", + "MismatchedCategoryElectricalComponent", "NaIonBattery", + "PvInverter", "PowerTransformer", "Precharger", - "ProblematicComponent", - "ProblematicComponentTypes", - "Relay", - "SolarInverter", + "ProblematicElectricalComponent", + "ProblematicElectricalComponentTypes", "SteamBoiler", "UnrecognizedBattery", - "UnrecognizedComponent", - "UnrecognizedComponentTypes", + "UnrecognizedElectricalComponent", + "UnrecognizedElectricalComponentTypes", "UnrecognizedEvCharger", "UnrecognizedInverter", "UnspecifiedBattery", - "UnspecifiedComponent", - "UnspecifiedComponentTypes", + "UnspecifiedElectricalComponent", + "UnspecifiedElectricalComponentTypes", "UnspecifiedEvCharger", "UnspecifiedInverter", "WindTurbine", diff --git a/src/frequenz/client/common/microgrid/electrical_components/_battery.py b/src/frequenz/client/common/microgrid/electrical_components/_battery.py index 5f65e0ad..1415f6dc 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_battery.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_battery.py @@ -45,8 +45,9 @@ class Battery(ElectricalComponent): It is only provided for using with a newer version of the API where the client doesn't know about a new category yet (i.e. for use with - [`UnrecognizedComponent`][...UnrecognizedComponent]) and in case some - low level code needs to know the category of an electrical component. + [`UnrecognizedElectricalComponent`][...UnrecognizedElectricalComponent]) + and in case some low level code needs to know the category of an electrical + component. """ type: BatteryType | int diff --git a/src/frequenz/client/common/microgrid/electrical_components/_relay.py b/src/frequenz/client/common/microgrid/electrical_components/_breaker.py similarity index 79% rename from src/frequenz/client/common/microgrid/electrical_components/_relay.py rename to src/frequenz/client/common/microgrid/electrical_components/_breaker.py index 114e80f4..b148ac67 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_relay.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_breaker.py @@ -1,7 +1,7 @@ # License: MIT # Copyright © 2024 Frequenz Energy-as-a-Service GmbH -"""Relay electrical component.""" +"""Breaker electrical component.""" import dataclasses from typing import Literal @@ -11,8 +11,8 @@ @dataclasses.dataclass(frozen=True, kw_only=True) -class Relay(ElectricalComponent): - """A relay electrical component.""" +class Breaker(ElectricalComponent): + """A breaker electrical component.""" category: Literal[ElectricalComponentCategory.BREAKER] = ( ElectricalComponentCategory.BREAKER diff --git a/src/frequenz/client/common/microgrid/electrical_components/_electrical_component.py b/src/frequenz/client/common/microgrid/electrical_components/_electrical_component.py index 3d480642..a4cdd03f 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_electrical_component.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_electrical_component.py @@ -8,10 +8,9 @@ from datetime import datetime, timezone from typing import Any, Self -from frequenz.client.common.microgrid import MicrogridId - from ...metrics import Bounds, Metric from ...types import Lifetime +from .. import MicrogridId from ._category import ElectricalComponentCategory from ._ids import ElectricalComponentId @@ -35,8 +34,8 @@ class ElectricalComponent: # pylint: disable=too-many-instance-attributes It is only provided for using with a newer version of the API where the client doesn't know about a new category yet (i.e. for use with - [`UnrecognizedComponent`][...UnrecognizedComponent]) and in case some low level - code needs to know the category of an electrical component. + [`UnrecognizedElectricalComponent`][...UnrecognizedElectricalComponent]) and + in case some low level code needs to know the category of an electrical component. """ name: str | None = None @@ -74,7 +73,8 @@ class ElectricalComponent: # pylint: disable=too-many-instance-attributes Note: This should not be used normally, it is only useful when accessing a newer version of the API where the client doesn't know about the new metadata fields - yet (i.e. for use with [`UnrecognizedComponent`][...UnrecognizedComponent]). + yet (i.e. for use with + [`UnrecognizedElectricalComponent`][...UnrecognizedElectricalComponent]). """ def __new__(cls, *_: Any, **__: Any) -> Self: diff --git a/src/frequenz/client/common/microgrid/electrical_components/_ev_charger.py b/src/frequenz/client/common/microgrid/electrical_components/_ev_charger.py index b5ff1e5c..6df1c3d9 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_ev_charger.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_ev_charger.py @@ -47,8 +47,8 @@ class EvCharger(ElectricalComponent): It is only provided for using with a newer version of the API where the client doesn't know about a new category yet (i.e. for use with - [`UnrecognizedComponent`][...UnrecognizedComponent]) and in case some low - level code needs to know the category of an electrical component. + [`UnrecognizedElectricalComponent`][...UnrecognizedElectricalComponent]) and in + case some low level code needs to know the category of an electrical component. """ type: EvChargerType | int diff --git a/src/frequenz/client/common/microgrid/electrical_components/_inverter.py b/src/frequenz/client/common/microgrid/electrical_components/_inverter.py index 3bcc1782..718c8998 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_inverter.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_inverter.py @@ -25,8 +25,8 @@ class InverterType(enum.Enum): BATTERY = electrical_components_pb2.INVERTER_TYPE_BATTERY """The inverter is a battery inverter.""" - SOLAR = electrical_components_pb2.INVERTER_TYPE_PV - """The inverter is a solar inverter.""" + PV = electrical_components_pb2.INVERTER_TYPE_PV + """The inverter is a PV inverter.""" HYBRID = electrical_components_pb2.INVERTER_TYPE_HYBRID """The inverter is a hybrid inverter.""" @@ -47,8 +47,8 @@ class Inverter(ElectricalComponent): It is only provided for using with a newer version of the API where the client doesn't know about a new category yet (i.e. for use with - [`UnrecognizedComponent`][...UnrecognizedComponent]) and in case some low level - code needs to know the category of an electrical component. + [`UnrecognizedElectricalComponent`][...UnrecognizedElectricalComponent]) and in + case some low level code needs to know the category of an electrical component. """ type: InverterType | int @@ -106,10 +106,10 @@ class BatteryInverter(Inverter): @dataclasses.dataclass(frozen=True, kw_only=True) -class SolarInverter(Inverter): - """A solar inverter.""" +class PvInverter(Inverter): + """A PV inverter.""" - type: Literal[InverterType.SOLAR] = InverterType.SOLAR + type: Literal[InverterType.PV] = InverterType.PV """The type of this inverter. Note: @@ -150,7 +150,7 @@ class UnrecognizedInverter(Inverter): InverterTypes: TypeAlias = ( UnspecifiedInverter | BatteryInverter - | SolarInverter + | PvInverter | HybridInverter | UnrecognizedInverter ) diff --git a/src/frequenz/client/common/microgrid/electrical_components/_problematic.py b/src/frequenz/client/common/microgrid/electrical_components/_problematic.py index 17c8ceb3..b72f9c8e 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_problematic.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_problematic.py @@ -1,7 +1,7 @@ # License: MIT # Copyright © 2024 Frequenz Energy-as-a-Service GmbH -"""Unknown electrical component.""" +"""Problematic electrical components.""" import dataclasses from typing import Any, Literal, Self @@ -11,19 +11,19 @@ @dataclasses.dataclass(frozen=True, kw_only=True) -class ProblematicComponent(ElectricalComponent): +class ProblematicElectricalComponent(ElectricalComponent): """An abstract electrical component with a problem.""" # pylint: disable-next=unused-argument def __new__(cls, *args: Any, **kwargs: Any) -> Self: """Prevent instantiation of this class.""" - if cls is ProblematicComponent: + if cls is ProblematicElectricalComponent: raise TypeError(f"Cannot instantiate {cls.__name__} directly") return super().__new__(cls) @dataclasses.dataclass(frozen=True, kw_only=True) -class UnspecifiedComponent(ProblematicComponent): +class UnspecifiedElectricalComponent(ProblematicElectricalComponent): """An electrical component of unspecified type.""" category: Literal[ElectricalComponentCategory.UNSPECIFIED] = ( @@ -33,7 +33,7 @@ class UnspecifiedComponent(ProblematicComponent): @dataclasses.dataclass(frozen=True, kw_only=True) -class UnrecognizedComponent(ProblematicComponent): +class UnrecognizedElectricalComponent(ProblematicElectricalComponent): """An electrical component of an unrecognized type.""" category: int @@ -41,7 +41,7 @@ class UnrecognizedComponent(ProblematicComponent): @dataclasses.dataclass(frozen=True, kw_only=True) -class MismatchedCategoryComponent(ProblematicComponent): +class MismatchedCategoryElectricalComponent(ProblematicElectricalComponent): """An electrical component with a mismatch in the category. This electrical component declared a category but carries category specific diff --git a/src/frequenz/client/common/microgrid/electrical_components/_types.py b/src/frequenz/client/common/microgrid/electrical_components/_types.py index 6bfe54fe..fadb04eb 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/_types.py +++ b/src/frequenz/client/common/microgrid/electrical_components/_types.py @@ -6,6 +6,7 @@ from typing import TypeAlias from ._battery import BatteryTypes, UnrecognizedBattery, UnspecifiedBattery +from ._breaker import Breaker from ._chp import Chp from ._converter import Converter from ._crypto_miner import CryptoMiner @@ -18,36 +19,38 @@ from ._power_transformer import PowerTransformer from ._precharger import Precharger from ._problematic import ( - MismatchedCategoryComponent, - UnrecognizedComponent, - UnspecifiedComponent, + MismatchedCategoryElectricalComponent, + UnrecognizedElectricalComponent, + UnspecifiedElectricalComponent, ) -from ._relay import Relay from ._steam_boiler import SteamBoiler from ._wind_turbine import WindTurbine -UnspecifiedComponentTypes: TypeAlias = ( +UnspecifiedElectricalComponentTypes: TypeAlias = ( UnspecifiedBattery - | UnspecifiedComponent + | UnspecifiedElectricalComponent | UnspecifiedEvCharger | UnspecifiedInverter ) -"""All unspecified component types.""" +"""All unspecified electrical component types.""" -UnrecognizedComponentTypes: TypeAlias = ( +UnrecognizedElectricalComponentTypes: TypeAlias = ( UnrecognizedBattery - | UnrecognizedComponent + | UnrecognizedElectricalComponent | UnrecognizedEvCharger | UnrecognizedInverter ) -ProblematicComponentTypes: TypeAlias = ( - MismatchedCategoryComponent | UnrecognizedComponentTypes | UnspecifiedComponentTypes +ProblematicElectricalComponentTypes: TypeAlias = ( + MismatchedCategoryElectricalComponent + | UnrecognizedElectricalComponentTypes + | UnspecifiedElectricalComponentTypes ) -"""All possible component types that has a problem.""" +"""All possible electrical component types that have a problem.""" ComponentTypes: TypeAlias = ( BatteryTypes + | Breaker | Chp | Converter | CryptoMiner @@ -59,8 +62,7 @@ | Meter | PowerTransformer | Precharger - | ProblematicComponentTypes - | Relay + | ProblematicElectricalComponentTypes | SteamBoiler | WindTurbine ) diff --git a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/__init__.py b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/__init__.py index b2a2f042..63ca4b58 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/__init__.py +++ b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/__init__.py @@ -3,20 +3,26 @@ """Conversion of electrical component enums from/to protobuf v1alpha8.""" -from ._electrical_component import ( +from ._category import ( electrical_component_category_from_proto, electrical_component_category_to_proto, +) +from ._diagnostic_code import ( electrical_component_diagnostic_code_from_proto, electrical_component_diagnostic_code_to_proto, +) +from ._electrical_component import ( electrical_component_from_proto, electrical_component_from_proto_with_issues, - electrical_component_state_code_from_proto, - electrical_component_state_code_to_proto, ) from ._electrical_component_connection import ( electrical_component_connection_from_proto, electrical_component_connection_from_proto_with_issues, ) +from ._state_code import ( + electrical_component_state_code_from_proto, + electrical_component_state_code_to_proto, +) __all__ = [ "electrical_component_category_from_proto", diff --git a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py new file mode 100644 index 00000000..98176459 --- /dev/null +++ b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_category.py @@ -0,0 +1,42 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Conversion of electrical component categories to/from protobuf v1alpha8.""" + +from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( + electrical_components_pb2, +) + +from .....proto import enum_from_proto +from ... import ElectricalComponentCategory + + +def electrical_component_category_from_proto( + message: electrical_components_pb2.ElectricalComponentCategory.ValueType, +) -> ElectricalComponentCategory | int: + """Convert a protobuf ElectricalComponentCategory enum value to an enum member. + + Args: + message: A protobuf ElectricalComponentCategory enum value. + + Returns: + The corresponding ElectricalComponentCategory enum member, or the raw `int` + if the protobuf value is not recognized. + """ + return enum_from_proto(message, ElectricalComponentCategory) + + +def electrical_component_category_to_proto( + category: ElectricalComponentCategory, +) -> electrical_components_pb2.ElectricalComponentCategory.ValueType: + """Convert an ElectricalComponentCategory enum member to a protobuf enum value. + + Args: + category: An ElectricalComponentCategory enum member. + + Returns: + The corresponding protobuf ElectricalComponentCategory enum value. + """ + return electrical_components_pb2.ElectricalComponentCategory.ValueType( + category.value + ) diff --git a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_diagnostic_code.py b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_diagnostic_code.py new file mode 100644 index 00000000..9cc59b75 --- /dev/null +++ b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_diagnostic_code.py @@ -0,0 +1,42 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Conversion of electrical component diagnostic codes to/from protobuf v1alpha8.""" + +from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( + electrical_components_pb2, +) + +from .....proto import enum_from_proto +from ... import ElectricalComponentDiagnosticCode + + +def electrical_component_diagnostic_code_from_proto( + message: electrical_components_pb2.ElectricalComponentDiagnosticCode.ValueType, +) -> ElectricalComponentDiagnosticCode | int: + """Convert a protobuf ElectricalComponentDiagnosticCode value to an enum member. + + Args: + message: A protobuf ElectricalComponentDiagnosticCode enum value. + + Returns: + The corresponding ElectricalComponentDiagnosticCode enum member, or the raw + `int` if the protobuf value is not recognized. + """ + return enum_from_proto(message, ElectricalComponentDiagnosticCode) + + +def electrical_component_diagnostic_code_to_proto( + diagnostic_code: ElectricalComponentDiagnosticCode, +) -> electrical_components_pb2.ElectricalComponentDiagnosticCode.ValueType: + """Convert an ElectricalComponentDiagnosticCode enum member to a protobuf value. + + Args: + diagnostic_code: An ElectricalComponentDiagnosticCode enum member. + + Returns: + The corresponding protobuf ElectricalComponentDiagnosticCode enum value. + """ + return electrical_components_pb2.ElectricalComponentDiagnosticCode.ValueType( + diagnostic_code.value + ) diff --git a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_electrical_component.py b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_electrical_component.py index 6db59152..db3ac569 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_electrical_component.py +++ b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_electrical_component.py @@ -22,15 +22,14 @@ AcEvCharger, BatteryInverter, BatteryType, + Breaker, Chp, ComponentTypes, Converter, CryptoMiner, DcEvCharger, ElectricalComponentCategory, - ElectricalComponentDiagnosticCode, ElectricalComponentId, - ElectricalComponentStateCode, Electrolyzer, EvChargerType, GridConnectionPoint, @@ -40,19 +39,18 @@ InverterType, LiIonBattery, Meter, - MismatchedCategoryComponent, + MismatchedCategoryElectricalComponent, NaIonBattery, PowerTransformer, Precharger, - Relay, - SolarInverter, + PvInverter, SteamBoiler, UnrecognizedBattery, - UnrecognizedComponent, + UnrecognizedElectricalComponent, UnrecognizedEvCharger, UnrecognizedInverter, UnspecifiedBattery, - UnspecifiedComponent, + UnspecifiedElectricalComponent, UnspecifiedEvCharger, UnspecifiedInverter, WindTurbine, @@ -61,99 +59,6 @@ _logger = logging.getLogger(__name__) -def electrical_component_category_from_proto( - message: electrical_components_pb2.ElectricalComponentCategory.ValueType, -) -> ElectricalComponentCategory | int: - """Convert a protobuf ElectricalComponentCategory enum value to an enum member. - - Args: - message: A protobuf ElectricalComponentCategory enum value. - - Returns: - The corresponding ElectricalComponentCategory enum member, or the raw `int` - if the protobuf value is not recognized. - """ - return enum_from_proto(message, ElectricalComponentCategory) - - -def electrical_component_category_to_proto( - category: ElectricalComponentCategory, -) -> electrical_components_pb2.ElectricalComponentCategory.ValueType: - """Convert an ElectricalComponentCategory enum member to a protobuf enum value. - - Args: - category: An ElectricalComponentCategory enum member. - - Returns: - The corresponding protobuf ElectricalComponentCategory enum value. - """ - return electrical_components_pb2.ElectricalComponentCategory.ValueType( - category.value - ) - - -def electrical_component_state_code_from_proto( - message: electrical_components_pb2.ElectricalComponentStateCode.ValueType, -) -> ElectricalComponentStateCode | int: - """Convert a protobuf ElectricalComponentStateCode enum value to an enum member. - - Args: - message: A protobuf ElectricalComponentStateCode enum value. - - Returns: - The corresponding ElectricalComponentStateCode enum member, or the raw `int` - if the protobuf value is not recognized. - """ - return enum_from_proto(message, ElectricalComponentStateCode) - - -def electrical_component_state_code_to_proto( - state_code: ElectricalComponentStateCode, -) -> electrical_components_pb2.ElectricalComponentStateCode.ValueType: - """Convert an ElectricalComponentStateCode enum member to a protobuf enum value. - - Args: - state_code: An ElectricalComponentStateCode enum member. - - Returns: - The corresponding protobuf ElectricalComponentStateCode enum value. - """ - return electrical_components_pb2.ElectricalComponentStateCode.ValueType( - state_code.value - ) - - -def electrical_component_diagnostic_code_from_proto( - message: electrical_components_pb2.ElectricalComponentDiagnosticCode.ValueType, -) -> ElectricalComponentDiagnosticCode | int: - """Convert a protobuf ElectricalComponentDiagnosticCode value to an enum member. - - Args: - message: A protobuf ElectricalComponentDiagnosticCode enum value. - - Returns: - The corresponding ElectricalComponentDiagnosticCode enum member, or the raw - `int` if the protobuf value is not recognized. - """ - return enum_from_proto(message, ElectricalComponentDiagnosticCode) - - -def electrical_component_diagnostic_code_to_proto( - diagnostic_code: ElectricalComponentDiagnosticCode, -) -> electrical_components_pb2.ElectricalComponentDiagnosticCode.ValueType: - """Convert an ElectricalComponentDiagnosticCode enum member to a protobuf value. - - Args: - diagnostic_code: An ElectricalComponentDiagnosticCode enum member. - - Returns: - The corresponding protobuf ElectricalComponentDiagnosticCode enum value. - """ - return electrical_components_pb2.ElectricalComponentDiagnosticCode.ValueType( - diagnostic_code.value - ) - - # We disable the `too-many-arguments` check in the whole file because all _from_proto # functions are expected to take many arguments. # pylint: disable=too-many-arguments @@ -311,7 +216,7 @@ def electrical_component_from_proto_with_issues( ) if base_data.category_mismatched: - return MismatchedCategoryComponent( + return MismatchedCategoryElectricalComponent( id=base_data.component_id, microgrid_id=base_data.microgrid_id, name=base_data.name, @@ -325,7 +230,7 @@ def electrical_component_from_proto_with_issues( match base_data.category: case int(): - return UnrecognizedComponent( + return UnrecognizedElectricalComponent( id=base_data.component_id, microgrid_id=base_data.microgrid_id, name=base_data.name, @@ -463,15 +368,12 @@ def electrical_component_from_proto_with_issues( inverter_enum_to_class: dict[ InverterType, type[ - UnspecifiedInverter - | BatteryInverter - | SolarInverter - | HybridInverter + UnspecifiedInverter | BatteryInverter | PvInverter | HybridInverter ], ] = { InverterType.UNSPECIFIED: UnspecifiedInverter, InverterType.BATTERY: BatteryInverter, - InverterType.SOLAR: SolarInverter, + InverterType.PV: PvInverter, InverterType.HYBRID: HybridInverter, } inverter_type = enum_from_proto( @@ -481,7 +383,7 @@ def electrical_component_from_proto_with_issues( case ( InverterType.UNSPECIFIED | InverterType.BATTERY - | InverterType.SOLAR + | InverterType.PV | InverterType.HYBRID ): if inverter_type is InverterType.UNSPECIFIED: @@ -533,7 +435,7 @@ def electrical_component_from_proto_with_issues( f"category {base_data.category.name} has no specific electrical " "component type" ) - return UnrecognizedComponent( + return UnrecognizedElectricalComponent( id=base_data.component_id, microgrid_id=base_data.microgrid_id, name=base_data.name, @@ -550,7 +452,8 @@ def electrical_component_from_proto_with_issues( def _trivial_category_to_class( category: ElectricalComponentCategory, ) -> type[ - UnspecifiedComponent + UnspecifiedElectricalComponent + | Breaker | Chp | Converter | CryptoMiner @@ -558,13 +461,12 @@ def _trivial_category_to_class( | Hvac | Meter | Precharger - | Relay | SteamBoiler | WindTurbine ]: """Return the class corresponding to a trivial electrical component category.""" return { - ElectricalComponentCategory.UNSPECIFIED: UnspecifiedComponent, + ElectricalComponentCategory.UNSPECIFIED: UnspecifiedElectricalComponent, ElectricalComponentCategory.CHP: Chp, ElectricalComponentCategory.CONVERTER: Converter, ElectricalComponentCategory.CRYPTO_MINER: CryptoMiner, @@ -572,7 +474,7 @@ def _trivial_category_to_class( ElectricalComponentCategory.HVAC: Hvac, ElectricalComponentCategory.METER: Meter, ElectricalComponentCategory.PRECHARGER: Precharger, - ElectricalComponentCategory.BREAKER: Relay, + ElectricalComponentCategory.BREAKER: Breaker, ElectricalComponentCategory.STEAM_BOILER: SteamBoiler, ElectricalComponentCategory.WIND_TURBINE: WindTurbine, }[category] diff --git a/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_state_code.py b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_state_code.py new file mode 100644 index 00000000..03c08f73 --- /dev/null +++ b/src/frequenz/client/common/microgrid/electrical_components/proto/v1alpha8/_state_code.py @@ -0,0 +1,42 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Conversion of electrical component state codes to/from protobuf v1alpha8.""" + +from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( + electrical_components_pb2, +) + +from .....proto import enum_from_proto +from ... import ElectricalComponentStateCode + + +def electrical_component_state_code_from_proto( + message: electrical_components_pb2.ElectricalComponentStateCode.ValueType, +) -> ElectricalComponentStateCode | int: + """Convert a protobuf ElectricalComponentStateCode enum value to an enum member. + + Args: + message: A protobuf ElectricalComponentStateCode enum value. + + Returns: + The corresponding ElectricalComponentStateCode enum member, or the raw `int` + if the protobuf value is not recognized. + """ + return enum_from_proto(message, ElectricalComponentStateCode) + + +def electrical_component_state_code_to_proto( + state_code: ElectricalComponentStateCode, +) -> electrical_components_pb2.ElectricalComponentStateCode.ValueType: + """Convert an ElectricalComponentStateCode enum member to a protobuf enum value. + + Args: + state_code: An ElectricalComponentStateCode enum member. + + Returns: + The corresponding protobuf ElectricalComponentStateCode enum value. + """ + return electrical_components_pb2.ElectricalComponentStateCode.ValueType( + state_code.value + ) diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 3580765d..f5c456c8 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -20,28 +20,17 @@ Keep the `proto//` nesting intact (currently only `v1alpha8`). ## ENUM TESTS = ONE-LINE SUBCLASS -Never re-scaffold enum/proto checks. Subclass the shared base and pin attributes: - -```python -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 -from frequenz.client.common.test.enum_parity import EnumParityTest - -class TestMetricParity(EnumParityTest): - python_enum = Metric - proto_enum = metrics_pb2.Metric - name_prefix = "METRIC_" - from_proto = staticmethod(metric_from_proto) # MUST wrap in staticmethod(...) - to_proto = staticmethod(metric_to_proto) -``` - -`EnumParityTest` (`src/.../test/enum_parity.py`) auto-parametrizes and checks name/value -parity both ways, `from_proto` known + unknown-int handling, and `to_proto`. Class name MUST -start with `Test` for collection. +Never re-scaffold enum/proto checks. Subclass +`frequenz.client.common.test.enum_parity.EnumParityTest`. Class name MUST start +with `Test` for collection. ## CONVENTIONS +- Imports for the tested code are always absolute — this verifies the real + public import path works. Target the **public** package path whenever the + symbol is publicly exported. Import from an internal `_`-module only when + testing an internal symbol not exposed publicly. +- Imports from test utilities in `tests/` are always relative. - `pytest` with `asyncio_mode = "auto"` — `async def test_*` needs no decorator. - Property-based tests use `hypothesis`; mocking via `pytest-mock`. - Warnings are errors (`pyproject.toml`); a test emitting an unexpected warning fails. diff --git a/tests/microgrid/electrical_components/proto/v1alpha8/test_category.py b/tests/microgrid/electrical_components/proto/v1alpha8/test_category.py new file mode 100644 index 00000000..27dc7271 --- /dev/null +++ b/tests/microgrid/electrical_components/proto/v1alpha8/test_category.py @@ -0,0 +1,27 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Tests for electrical component category to/from protobuf v1alpha8 conversion.""" + +from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( + electrical_components_pb2, +) + +from frequenz.client.common.microgrid.electrical_components import ( + ElectricalComponentCategory, +) +from frequenz.client.common.microgrid.electrical_components.proto.v1alpha8 import ( + electrical_component_category_from_proto, + electrical_component_category_to_proto, +) +from frequenz.client.common.test.enum_parity import EnumParityTest + + +class TestElectricalComponentCategoryParity(EnumParityTest): + """Parity tests for the `ElectricalComponentCategory` enum.""" + + python_enum = ElectricalComponentCategory + proto_enum = electrical_components_pb2.ElectricalComponentCategory + name_prefix = "ELECTRICAL_COMPONENT_CATEGORY_" + from_proto = staticmethod(electrical_component_category_from_proto) + to_proto = staticmethod(electrical_component_category_to_proto) diff --git a/tests/microgrid/electrical_components/proto/v1alpha8/test_diagnostic_code.py b/tests/microgrid/electrical_components/proto/v1alpha8/test_diagnostic_code.py new file mode 100644 index 00000000..2310ab5f --- /dev/null +++ b/tests/microgrid/electrical_components/proto/v1alpha8/test_diagnostic_code.py @@ -0,0 +1,27 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Tests for electrical component diagnostic code to/from protobuf v1alpha8 conversion.""" + +from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( + electrical_components_pb2, +) + +from frequenz.client.common.microgrid.electrical_components import ( + ElectricalComponentDiagnosticCode, +) +from frequenz.client.common.microgrid.electrical_components.proto.v1alpha8 import ( + electrical_component_diagnostic_code_from_proto, + electrical_component_diagnostic_code_to_proto, +) +from frequenz.client.common.test.enum_parity import EnumParityTest + + +class TestElectricalComponentDiagnosticCodeParity(EnumParityTest): + """Parity tests for the `ElectricalComponentDiagnosticCode` enum.""" + + python_enum = ElectricalComponentDiagnosticCode + proto_enum = electrical_components_pb2.ElectricalComponentDiagnosticCode + name_prefix = "ELECTRICAL_COMPONENT_DIAGNOSTIC_CODE_" + from_proto = staticmethod(electrical_component_diagnostic_code_from_proto) + to_proto = staticmethod(electrical_component_diagnostic_code_to_proto) diff --git a/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component.py b/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component.py deleted file mode 100644 index d33d4e71..00000000 --- a/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component.py +++ /dev/null @@ -1,53 +0,0 @@ -# License: MIT -# Copyright © 2026 Frequenz Energy-as-a-Service GmbH - -"""Tests for electrical component enum to/from protobuf v1alpha8 conversion.""" - -from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( - electrical_components_pb2, -) - -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentDiagnosticCode, - ElectricalComponentStateCode, -) -from frequenz.client.common.microgrid.electrical_components.proto.v1alpha8 import ( - electrical_component_category_from_proto, - electrical_component_category_to_proto, - electrical_component_diagnostic_code_from_proto, - electrical_component_diagnostic_code_to_proto, - electrical_component_state_code_from_proto, - electrical_component_state_code_to_proto, -) -from frequenz.client.common.test.enum_parity import EnumParityTest - - -class TestElectricalComponentCategoryParity(EnumParityTest): - """Parity tests for the `ElectricalComponentCategory` enum.""" - - python_enum = ElectricalComponentCategory - proto_enum = electrical_components_pb2.ElectricalComponentCategory - name_prefix = "ELECTRICAL_COMPONENT_CATEGORY_" - from_proto = staticmethod(electrical_component_category_from_proto) - to_proto = staticmethod(electrical_component_category_to_proto) - - -class TestElectricalComponentStateCodeParity(EnumParityTest): - """Parity tests for the `ElectricalComponentStateCode` enum.""" - - python_enum = ElectricalComponentStateCode - proto_enum = electrical_components_pb2.ElectricalComponentStateCode - name_prefix = "ELECTRICAL_COMPONENT_STATE_CODE_" - from_proto = staticmethod(electrical_component_state_code_from_proto) - to_proto = staticmethod(electrical_component_state_code_to_proto) - - -class TestElectricalComponentDiagnosticCodeParity(EnumParityTest): - """Parity tests for the `ElectricalComponentDiagnosticCode` enum.""" - - python_enum = ElectricalComponentDiagnosticCode - proto_enum = electrical_components_pb2.ElectricalComponentDiagnosticCode - name_prefix = "ELECTRICAL_COMPONENT_DIAGNOSTIC_CODE_" - from_proto = staticmethod(electrical_component_diagnostic_code_from_proto) - to_proto = staticmethod(electrical_component_diagnostic_code_to_proto) diff --git a/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_simple.py b/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_simple.py index 29a7aec1..ba892b0f 100644 --- a/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_simple.py +++ b/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_simple.py @@ -12,6 +12,7 @@ ) from frequenz.client.common.microgrid.electrical_components import ( + Breaker, Chp, Converter, CryptoMiner, @@ -21,13 +22,12 @@ GridConnectionPoint, Hvac, Meter, - MismatchedCategoryComponent, + MismatchedCategoryElectricalComponent, PowerTransformer, Precharger, - Relay, SteamBoiler, - UnrecognizedComponent, - UnspecifiedComponent, + UnrecognizedElectricalComponent, + UnspecifiedElectricalComponent, WindTurbine, ) from frequenz.client.common.microgrid.electrical_components.proto.v1alpha8 import ( @@ -53,7 +53,7 @@ def test_unspecified(default_component_base_data: _ElectricalComponentBaseData) assert major_issues == ["category is unspecified"] assert not minor_issues - assert isinstance(component, UnspecifiedComponent) + assert isinstance(component, UnspecifiedElectricalComponent) assert_base_data(default_component_base_data, component) assert component.category == ElectricalComponentCategory.UNSPECIFIED @@ -73,7 +73,7 @@ def test_unrecognized( assert major_issues == ["category 999 is unrecognized"] assert not minor_issues - assert isinstance(component, UnrecognizedComponent) + assert isinstance(component, UnrecognizedElectricalComponent) assert_base_data(base_data, component) assert component.category == 999 @@ -81,7 +81,7 @@ def test_unrecognized( def test_category_mismatch( default_component_base_data: _ElectricalComponentBaseData, ) -> None: - """Test MismatchedCategoryComponent for category GRID and battery specific info.""" + """Test mismatched category handling for category GRID and battery info.""" major_issues: list[str] = [] minor_issues: list[str] = [] base_data = default_component_base_data._replace( @@ -103,7 +103,7 @@ def test_category_mismatch( "category_specific_info.kind (battery) does not match the category (grid_connection_point)" ] assert not minor_issues - assert isinstance(component, MismatchedCategoryComponent) + assert isinstance(component, MismatchedCategoryElectricalComponent) assert_base_data(base_data, component) assert component.category == ElectricalComponentCategory.GRID_CONNECTION_POINT @@ -111,6 +111,7 @@ def test_category_mismatch( @pytest.mark.parametrize( "category,component_class", [ + pytest.param(ElectricalComponentCategory.BREAKER, Breaker, id="Breaker"), pytest.param(ElectricalComponentCategory.CHP, Chp, id="Chp"), pytest.param(ElectricalComponentCategory.CONVERTER, Converter, id="Converter"), pytest.param( @@ -124,7 +125,6 @@ def test_category_mismatch( pytest.param( ElectricalComponentCategory.PRECHARGER, Precharger, id="Precharger" ), - pytest.param(ElectricalComponentCategory.BREAKER, Relay, id="Relay"), pytest.param( ElectricalComponentCategory.STEAM_BOILER, SteamBoiler, id="SteamBoiler" ), @@ -151,7 +151,7 @@ def test_trivial( assert not major_issues assert not minor_issues assert isinstance(component, component_class) - assert not isinstance(component, UnrecognizedComponent) + assert not isinstance(component, UnrecognizedElectricalComponent) assert_base_data(base_data, component) diff --git a/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_with_type.py b/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_with_type.py index 347392a7..8e364013 100644 --- a/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_with_type.py +++ b/tests/microgrid/electrical_components/proto/v1alpha8/test_electrical_component_with_type.py @@ -23,7 +23,7 @@ InverterType, LiIonBattery, NaIonBattery, - SolarInverter, + PvInverter, UnrecognizedBattery, UnrecognizedEvCharger, UnrecognizedInverter, @@ -179,11 +179,11 @@ def test_ev_charger( id="BATTERY", ), pytest.param( - SolarInverter, - InverterType.SOLAR, + PvInverter, + InverterType.PV, electrical_components_pb2.INVERTER_TYPE_PV, [], - id="SOLAR", + id="PV", ), pytest.param( HybridInverter, diff --git a/tests/microgrid/electrical_components/proto/v1alpha8/test_state_code.py b/tests/microgrid/electrical_components/proto/v1alpha8/test_state_code.py new file mode 100644 index 00000000..65f0a04f --- /dev/null +++ b/tests/microgrid/electrical_components/proto/v1alpha8/test_state_code.py @@ -0,0 +1,27 @@ +# License: MIT +# Copyright © 2026 Frequenz Energy-as-a-Service GmbH + +"""Tests for electrical component state code to/from protobuf v1alpha8 conversion.""" + +from frequenz.api.common.v1alpha8.microgrid.electrical_components import ( + electrical_components_pb2, +) + +from frequenz.client.common.microgrid.electrical_components import ( + ElectricalComponentStateCode, +) +from frequenz.client.common.microgrid.electrical_components.proto.v1alpha8 import ( + electrical_component_state_code_from_proto, + electrical_component_state_code_to_proto, +) +from frequenz.client.common.test.enum_parity import EnumParityTest + + +class TestElectricalComponentStateCodeParity(EnumParityTest): + """Parity tests for the `ElectricalComponentStateCode` enum.""" + + python_enum = ElectricalComponentStateCode + proto_enum = electrical_components_pb2.ElectricalComponentStateCode + name_prefix = "ELECTRICAL_COMPONENT_STATE_CODE_" + from_proto = staticmethod(electrical_component_state_code_from_proto) + to_proto = staticmethod(electrical_component_state_code_to_proto) diff --git a/tests/microgrid/electrical_components/test_chp.py b/tests/microgrid/electrical_components/test_chp.py deleted file mode 100644 index 10fa2c94..00000000 --- a/tests/microgrid/electrical_components/test_chp.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for CHP component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - Chp, - ElectricalComponentCategory, - ElectricalComponentId, -) - - -def test_init() -> None: - """Test CHP component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Chp( - id=component_id, - microgrid_id=microgrid_id, - name="chp_test", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "chp_test" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.CHP diff --git a/tests/microgrid/electrical_components/test_converter.py b/tests/microgrid/electrical_components/test_converter.py deleted file mode 100644 index bcdd6558..00000000 --- a/tests/microgrid/electrical_components/test_converter.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for Converter component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - Converter, - ElectricalComponentCategory, - ElectricalComponentId, -) - - -def test_init() -> None: - """Test Converter component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Converter( - id=component_id, - microgrid_id=microgrid_id, - name="test_converter", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "test_converter" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.CONVERTER diff --git a/tests/microgrid/electrical_components/test_crypto_miner.py b/tests/microgrid/electrical_components/test_crypto_miner.py deleted file mode 100644 index 622940c4..00000000 --- a/tests/microgrid/electrical_components/test_crypto_miner.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for CryptoMiner component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - CryptoMiner, - ElectricalComponentCategory, - ElectricalComponentId, -) - - -def test_init() -> None: - """Test CryptoMiner component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = CryptoMiner( - id=component_id, - microgrid_id=microgrid_id, - name="test_crypto_miner", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "test_crypto_miner" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.CRYPTO_MINER diff --git a/tests/microgrid/electrical_components/test_electrolyzer.py b/tests/microgrid/electrical_components/test_electrolyzer.py deleted file mode 100644 index 04da6b5b..00000000 --- a/tests/microgrid/electrical_components/test_electrolyzer.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for Electrolyzer component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - Electrolyzer, -) - - -def test_init() -> None: - """Test Electrolyzer component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Electrolyzer( - id=component_id, - microgrid_id=microgrid_id, - name="test_electrolyzer", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "test_electrolyzer" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.ELECTROLYZER diff --git a/tests/microgrid/electrical_components/test_hvac.py b/tests/microgrid/electrical_components/test_hvac.py deleted file mode 100644 index f6602b92..00000000 --- a/tests/microgrid/electrical_components/test_hvac.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for HVAC component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - Hvac, -) - - -def test_init() -> None: - """Test HVAC component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Hvac( - id=component_id, - microgrid_id=microgrid_id, - name="test_hvac", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "test_hvac" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.HVAC diff --git a/tests/microgrid/electrical_components/test_inverter.py b/tests/microgrid/electrical_components/test_inverter.py index ccddaa13..e36b22f8 100644 --- a/tests/microgrid/electrical_components/test_inverter.py +++ b/tests/microgrid/electrical_components/test_inverter.py @@ -15,7 +15,7 @@ HybridInverter, Inverter, InverterType, - SolarInverter, + PvInverter, UnrecognizedInverter, UnspecifiedInverter, ) @@ -25,7 +25,7 @@ class InverterTestCase: """Test case for Inverter components.""" - cls: type[UnspecifiedInverter | BatteryInverter | SolarInverter | HybridInverter] + cls: type[UnspecifiedInverter | BatteryInverter | PvInverter | HybridInverter] expected_type: InverterType name: str @@ -68,9 +68,7 @@ def test_abstract_inverter_cannot_be_instantiated( InverterTestCase( cls=BatteryInverter, expected_type=InverterType.BATTERY, name="battery" ), - InverterTestCase( - cls=SolarInverter, expected_type=InverterType.SOLAR, name="solar" - ), + InverterTestCase(cls=PvInverter, expected_type=InverterType.PV, name="pv"), InverterTestCase( cls=HybridInverter, expected_type=InverterType.HYBRID, name="hybrid" ), diff --git a/tests/microgrid/electrical_components/test_meter.py b/tests/microgrid/electrical_components/test_meter.py deleted file mode 100644 index 273b318e..00000000 --- a/tests/microgrid/electrical_components/test_meter.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for Meter component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - Meter, -) - - -def test_init() -> None: - """Test Meter component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Meter( - id=component_id, - microgrid_id=microgrid_id, - name="meter_test", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "meter_test" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.METER diff --git a/tests/microgrid/electrical_components/test_precharger.py b/tests/microgrid/electrical_components/test_precharger.py deleted file mode 100644 index 99d34170..00000000 --- a/tests/microgrid/electrical_components/test_precharger.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for Precharger component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - Precharger, -) - - -def test_init() -> None: - """Test Precharger component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Precharger( - id=component_id, - microgrid_id=microgrid_id, - name="precharger_test", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "precharger_test" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.PRECHARGER diff --git a/tests/microgrid/electrical_components/test_problematic.py b/tests/microgrid/electrical_components/test_problematic.py index 48ef0021..76742e27 100644 --- a/tests/microgrid/electrical_components/test_problematic.py +++ b/tests/microgrid/electrical_components/test_problematic.py @@ -1,7 +1,7 @@ # License: MIT # Copyright © 2025 Frequenz Energy-as-a-Service GmbH -"""Tests for ProblematicComponent electrical components.""" +"""Tests for problematic electrical components.""" import pytest @@ -9,10 +9,10 @@ from frequenz.client.common.microgrid.electrical_components import ( ElectricalComponentCategory, ElectricalComponentId, - MismatchedCategoryComponent, - ProblematicComponent, - UnrecognizedComponent, - UnspecifiedComponent, + MismatchedCategoryElectricalComponent, + ProblematicElectricalComponent, + UnrecognizedElectricalComponent, + UnspecifiedElectricalComponent, ) @@ -28,14 +28,14 @@ def microgrid_id() -> MicrogridId: return MicrogridId(1) -def test_abstract_problematic_component_cannot_be_instantiated( +def test_abstract_problematic_electrical_component_cannot_be_instantiated( component_id: ElectricalComponentId, microgrid_id: MicrogridId ) -> None: - """Test that ProblematicComponent base class cannot be instantiated.""" + """Test that ProblematicElectricalComponent cannot be instantiated.""" with pytest.raises( - TypeError, match="Cannot instantiate ProblematicComponent directly" + TypeError, match="Cannot instantiate ProblematicElectricalComponent directly" ): - ProblematicComponent( + ProblematicElectricalComponent( id=component_id, microgrid_id=microgrid_id, name="test_problematic", @@ -48,8 +48,8 @@ def test_abstract_problematic_component_cannot_be_instantiated( def test_unspecified_component( component_id: ElectricalComponentId, microgrid_id: MicrogridId ) -> None: - """Test initialization and properties of UnspecifiedComponent.""" - component = UnspecifiedComponent( + """Test initialization and properties of UnspecifiedElectricalComponent.""" + component = UnspecifiedElectricalComponent( id=component_id, microgrid_id=microgrid_id, name="unspecified_component", @@ -68,9 +68,9 @@ def test_unspecified_component( def test_mismatched_category_component_with_known_category( component_id: ElectricalComponentId, microgrid_id: MicrogridId ) -> None: - """Test MismatchedCategoryComponent with a known ElectricalComponentCategory.""" + """Test MismatchedCategoryElectricalComponent with a known category.""" expected_category = ElectricalComponentCategory.BATTERY - component = MismatchedCategoryComponent( + component = MismatchedCategoryElectricalComponent( id=component_id, microgrid_id=microgrid_id, name="mismatched_battery", @@ -90,9 +90,9 @@ def test_mismatched_category_component_with_known_category( def test_mismatched_category_component_with_unrecognized_category( component_id: ElectricalComponentId, microgrid_id: MicrogridId ) -> None: - """Test MismatchedCategoryComponent with an unrecognized integer category.""" + """Test MismatchedCategoryElectricalComponent with an unrecognized category.""" expected_category = 999 - component = MismatchedCategoryComponent( + component = MismatchedCategoryElectricalComponent( id=component_id, microgrid_id=microgrid_id, name="mismatched_unrecognized", @@ -112,8 +112,8 @@ def test_mismatched_category_component_with_unrecognized_category( def test_unrecognized_component_type( component_id: ElectricalComponentId, microgrid_id: MicrogridId ) -> None: - """Test initialization and properties of UnrecognizedComponent type.""" - component = UnrecognizedComponent( + """Test initialization and properties of UnrecognizedElectricalComponent.""" + component = UnrecognizedElectricalComponent( id=component_id, microgrid_id=microgrid_id, name="unrecognized_component", diff --git a/tests/microgrid/electrical_components/test_relay.py b/tests/microgrid/electrical_components/test_relay.py deleted file mode 100644 index 643e87d1..00000000 --- a/tests/microgrid/electrical_components/test_relay.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for Relay component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - Relay, -) - - -def test_init() -> None: - """Test Relay component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = Relay( - id=component_id, - microgrid_id=microgrid_id, - name="relay_test", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "relay_test" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.BREAKER diff --git a/tests/microgrid/electrical_components/test_simple_components.py b/tests/microgrid/electrical_components/test_simple_components.py new file mode 100644 index 00000000..29217a4b --- /dev/null +++ b/tests/microgrid/electrical_components/test_simple_components.py @@ -0,0 +1,83 @@ +# License: MIT +# Copyright © 2025 Frequenz Energy-as-a-Service GmbH + +"""Tests for simple leaf electrical components. + +These components are plain :class:`ElectricalComponent` subclasses that only fix +their :attr:`category` and add no extra fields. Their behaviour is identical, so +a single parametrized test covers all of them instead of one copy per component. +""" + +import pytest + +from frequenz.client.common.microgrid import MicrogridId +from frequenz.client.common.microgrid.electrical_components import ( + Breaker, + Chp, + Converter, + CryptoMiner, + ElectricalComponent, + ElectricalComponentCategory, + ElectricalComponentId, + Electrolyzer, + Hvac, + Meter, + Precharger, + SteamBoiler, + WindTurbine, +) + + +@pytest.fixture +def component_id() -> ElectricalComponentId: + """Provide a test component ID.""" + return ElectricalComponentId(42) + + +@pytest.fixture +def microgrid_id() -> MicrogridId: + """Provide a test microgrid ID.""" + return MicrogridId(1) + + +@pytest.mark.parametrize( + "cls, expected_category", + [ + (Breaker, ElectricalComponentCategory.BREAKER), + (Chp, ElectricalComponentCategory.CHP), + (Converter, ElectricalComponentCategory.CONVERTER), + (CryptoMiner, ElectricalComponentCategory.CRYPTO_MINER), + (Electrolyzer, ElectricalComponentCategory.ELECTROLYZER), + (Hvac, ElectricalComponentCategory.HVAC), + (Meter, ElectricalComponentCategory.METER), + (Precharger, ElectricalComponentCategory.PRECHARGER), + (SteamBoiler, ElectricalComponentCategory.STEAM_BOILER), + (WindTurbine, ElectricalComponentCategory.WIND_TURBINE), + ], + ids=lambda value: value.__name__ if isinstance(value, type) else value.name, +) +def test_init( + cls: type[ElectricalComponent], + expected_category: ElectricalComponentCategory, + component_id: ElectricalComponentId, + microgrid_id: MicrogridId, +) -> None: + """Test initialization and category of a simple leaf electrical component.""" + # We need to ignore call-arg because otherwise mypy complains about a missing + # category argument. It seems by doing this `cls` trick, mypy can't figure out + # the concrete class we are instantiating has a default category specified, so + # we don't really need to specify the category explicitly. + component = cls( # type: ignore[call-arg] + id=component_id, + microgrid_id=microgrid_id, + name="test_component", + manufacturer="test_manufacturer", + model_name="test_model", + ) + + assert component.id == component_id + assert component.microgrid_id == microgrid_id + assert component.name == "test_component" + assert component.manufacturer == "test_manufacturer" + assert component.model_name == "test_model" + assert component.category == expected_category diff --git a/tests/microgrid/electrical_components/test_steam_boiler.py b/tests/microgrid/electrical_components/test_steam_boiler.py deleted file mode 100644 index 0234a71d..00000000 --- a/tests/microgrid/electrical_components/test_steam_boiler.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2026 Frequenz Energy-as-a-Service GmbH - -"""Tests for steam boiler component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - SteamBoiler, -) - - -def test_init() -> None: - """Test steam boiler component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = SteamBoiler( - id=component_id, - microgrid_id=microgrid_id, - name="test_steam_boiler", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "test_steam_boiler" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.STEAM_BOILER diff --git a/tests/microgrid/electrical_components/test_wind_turbine.py b/tests/microgrid/electrical_components/test_wind_turbine.py deleted file mode 100644 index ca09d66c..00000000 --- a/tests/microgrid/electrical_components/test_wind_turbine.py +++ /dev/null @@ -1,31 +0,0 @@ -# License: MIT -# Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -"""Tests for WindTurbine component.""" - -from frequenz.client.common.microgrid import MicrogridId -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentId, - WindTurbine, -) - - -def test_init() -> None: - """Test WindTurbine component initialization.""" - component_id = ElectricalComponentId(1) - microgrid_id = MicrogridId(1) - component = WindTurbine( - id=component_id, - microgrid_id=microgrid_id, - name="wind_turbine_test", - manufacturer="test_manufacturer", - model_name="test_model", - ) - - assert component.id == component_id - assert component.microgrid_id == microgrid_id - assert component.name == "wind_turbine_test" - assert component.manufacturer == "test_manufacturer" - assert component.model_name == "test_model" - assert component.category == ElectricalComponentCategory.WIND_TURBINE