Skip to content

enum

frequenz.core.enum ¤

Enum utilities with support for deprecated members.

This module provides an Enum base class that extends the standard library's enum.Enum to support marking certain members as deprecated.

See the class documentation for details and examples.

Attributes¤

frequenz.core.enum.EnumT module-attribute ¤

EnumT = TypeVar('EnumT', bound=Enum)

Type variable for enum types.

frequenz.core.enum.ValueT module-attribute ¤

ValueT = TypeVar('ValueT')

Type variable for enum member values.

Classes¤

frequenz.core.enum.DeprecatedMember ¤

Class to mark members as deprecated.

This class should not be used directly, use deprecated_member instead.

Please read the Enum documentation for details and examples.

Source code in frequenz/core/enum.py
class DeprecatedMember:
    """Class to mark members as deprecated.

    This class should not be used directly, use
    [`deprecated_member`][frequenz.core.enum.deprecated_member] instead.

    Please read the [`Enum`][frequenz.core.enum.Enum] documentation for details and
    examples.
    """

    # Using slots is just an optimization to make the class more lightweight and avoid
    # the creation of a `__dict__` for each instance and its corresponding lookup.
    __slots__ = ("value", "message")

    def __init__(self, value: Any, message: str) -> None:
        """Initialize this instance."""
        self.value = value
        self.message = message
Functions¤
__init__ ¤
__init__(value: Any, message: str) -> None

Initialize this instance.

Source code in frequenz/core/enum.py
def __init__(self, value: Any, message: str) -> None:
    """Initialize this instance."""
    self.value = value
    self.message = message

frequenz.core.enum.DeprecatedMemberWarning ¤

Bases: DeprecationWarning

Warning category for deprecated enum members.

Source code in frequenz/core/enum.py
class DeprecatedMemberWarning(DeprecationWarning):
    """Warning category for deprecated enum members."""

frequenz.core.enum.DeprecatingEnumType ¤

Bases: EnumType

Enum metaclass that supports deprecated members.

Tip

Normally it is not necessary to use this class directly, use Enum instead.

Behavior:

  • In the class body, members may be declared as NAME = DeprecatedMember(value, msg).
  • During class creation, these wrappers are replaced with value so that a normal enum member or alias is created by EnumType.
  • The deprecated names are recorded so that:

    • MyEnum.NAME warns (attribute access by name)
    • MyEnum["NAME"] warns (lookup by name)
    • MyEnum(value) warns only if the resolved member has no non-deprecated aliases (all names for that member are deprecated).
Source code in frequenz/core/enum.py
class DeprecatingEnumType(enum.EnumType):
    """Enum metaclass that supports deprecated members.

    Tip:
        Normally it is not necessary to use this class directly, use
        [`Enum`][frequenz.core.enum.Enum] instead.

    Behavior:

    - In the class body, members may be declared as `NAME = DeprecatedMember(value, msg)`.
    - During class creation, these wrappers are replaced with `value` so that
      a normal enum member or alias is created by [`EnumType`][enum.EnumType].
    - The deprecated names are recorded so that:

        * `MyEnum.NAME` warns (attribute access by name)
        * `MyEnum["NAME"]` warns (lookup by name)
        * `MyEnum(value)` warns **only if** the resolved member has **no**
            non-deprecated aliases (all names for that member are deprecated).
    """

    def __new__(  # pylint: disable=too-many-locals
        mcs,
        name: str,
        bases: tuple[type[EnumT], ...],
        classdict: Mapping[str, Any],
        **kw: Any,
    ) -> type[EnumT]:
        """Create the new enum class, rewriting `DeprecatedMember` instances."""
        deprecated_names: dict[str, str] = {}
        prepared = super().__prepare__(name, bases, **kw)

        # Unwrap DeprecatedMembers and record them as deprecated
        for key, value in classdict.items():
            if isinstance(value, DeprecatedMember):
                deprecated_names[key] = value.message
                prepared[key] = value.value
            else:
                prepared[key] = value

        cls = cast(type[EnumT], super().__new__(mcs, name, bases, prepared, **kw))

        # Build alias groups: member -> list of names
        member_to_names: dict[EnumT, list[str]] = {}
        member: EnumT
        for member_name, member in cls.__members__.items():
            member_to_names.setdefault(member, []).append(member_name)

        warned_by_member: dict[EnumT, str] = {}
        for member, names in member_to_names.items():
            # warn on value only if all alias names are deprecated
            deprecated_aliases = [n for n in names if n in deprecated_names]
            if deprecated_aliases and len(deprecated_aliases) == len(names):
                warned_by_member[member] = deprecated_names[deprecated_aliases[0]]

        # Inject maps quietly
        type.__setattr__(cls, "__deprecated_names__", deprecated_names)
        type.__setattr__(cls, "__deprecated_value_map__", warned_by_member)

        return cls

    @staticmethod
    def _name_map(cls_: type[Any]) -> Mapping[str, str]:
        """Map from member names to deprecation messages."""
        return cast(
            Mapping[str, str],
            type.__getattribute__(cls_, "__dict__").get("__deprecated_names__", {}),
        )

    @staticmethod
    def _value_map(cls_: type[Any]) -> Mapping[Any, str]:
        """Map from enum members to deprecation messages."""
        return cast(
            Mapping[Any, str],
            type.__getattribute__(cls_, "__dict__").get("__deprecated_value_map__", {}),
        )

    def __getattribute__(cls, name: str) -> Any:
        """Resolve `name` to a member, warning if the member is deprecated."""
        if name in ("__deprecated_names__", "__deprecated_value_map__"):
            return type.__getattribute__(cls, name)
        deprecated = DeprecatingEnumType._name_map(cls)
        if name in deprecated:
            warnings.warn(deprecated[name], DeprecatedMemberWarning, stacklevel=2)
        return super().__getattribute__(name)

    def __getitem__(cls, name: str) -> Any:
        """Resolve `name` to a member, warning if the member is deprecated."""
        deprecated = DeprecatingEnumType._name_map(cls)
        if name in deprecated:
            warnings.warn(deprecated[name], DeprecatedMemberWarning, stacklevel=2)
        return super().__getitem__(name)

    def __call__(cls, value: Any, *args: Any, **kwargs: Any) -> Any:
        """Resolve `value` to a member, warning if the member is purely deprecated."""
        member = super().__call__(value, *args, **kwargs)
        value_map: Mapping[Any, str] = DeprecatingEnumType._value_map(cls)
        msg = value_map.get(member)
        if msg is not None:
            warnings.warn(msg, DeprecatedMemberWarning, stacklevel=2)
        return member
Functions¤
__call__ ¤
__call__(value: Any, *args: Any, **kwargs: Any) -> Any

Resolve value to a member, warning if the member is purely deprecated.

Source code in frequenz/core/enum.py
def __call__(cls, value: Any, *args: Any, **kwargs: Any) -> Any:
    """Resolve `value` to a member, warning if the member is purely deprecated."""
    member = super().__call__(value, *args, **kwargs)
    value_map: Mapping[Any, str] = DeprecatingEnumType._value_map(cls)
    msg = value_map.get(member)
    if msg is not None:
        warnings.warn(msg, DeprecatedMemberWarning, stacklevel=2)
    return member
__getattribute__ ¤
__getattribute__(name: str) -> Any

Resolve name to a member, warning if the member is deprecated.

Source code in frequenz/core/enum.py
def __getattribute__(cls, name: str) -> Any:
    """Resolve `name` to a member, warning if the member is deprecated."""
    if name in ("__deprecated_names__", "__deprecated_value_map__"):
        return type.__getattribute__(cls, name)
    deprecated = DeprecatingEnumType._name_map(cls)
    if name in deprecated:
        warnings.warn(deprecated[name], DeprecatedMemberWarning, stacklevel=2)
    return super().__getattribute__(name)
__getitem__ ¤
__getitem__(name: str) -> Any

Resolve name to a member, warning if the member is deprecated.

Source code in frequenz/core/enum.py
def __getitem__(cls, name: str) -> Any:
    """Resolve `name` to a member, warning if the member is deprecated."""
    deprecated = DeprecatingEnumType._name_map(cls)
    if name in deprecated:
        warnings.warn(deprecated[name], DeprecatedMemberWarning, stacklevel=2)
    return super().__getitem__(name)
__new__ ¤
__new__(
    mcs,
    name: str,
    bases: tuple[type[EnumT], ...],
    classdict: Mapping[str, Any],
    **kw: Any
) -> type[EnumT]

Create the new enum class, rewriting DeprecatedMember instances.

Source code in frequenz/core/enum.py
def __new__(  # pylint: disable=too-many-locals
    mcs,
    name: str,
    bases: tuple[type[EnumT], ...],
    classdict: Mapping[str, Any],
    **kw: Any,
) -> type[EnumT]:
    """Create the new enum class, rewriting `DeprecatedMember` instances."""
    deprecated_names: dict[str, str] = {}
    prepared = super().__prepare__(name, bases, **kw)

    # Unwrap DeprecatedMembers and record them as deprecated
    for key, value in classdict.items():
        if isinstance(value, DeprecatedMember):
            deprecated_names[key] = value.message
            prepared[key] = value.value
        else:
            prepared[key] = value

    cls = cast(type[EnumT], super().__new__(mcs, name, bases, prepared, **kw))

    # Build alias groups: member -> list of names
    member_to_names: dict[EnumT, list[str]] = {}
    member: EnumT
    for member_name, member in cls.__members__.items():
        member_to_names.setdefault(member, []).append(member_name)

    warned_by_member: dict[EnumT, str] = {}
    for member, names in member_to_names.items():
        # warn on value only if all alias names are deprecated
        deprecated_aliases = [n for n in names if n in deprecated_names]
        if deprecated_aliases and len(deprecated_aliases) == len(names):
            warned_by_member[member] = deprecated_names[deprecated_aliases[0]]

    # Inject maps quietly
    type.__setattr__(cls, "__deprecated_names__", deprecated_names)
    type.__setattr__(cls, "__deprecated_value_map__", warned_by_member)

    return cls

frequenz.core.enum.Enum ¤

Bases: Enum

Base class for enums that support deprecated members.

This class extends the standard library's enum.Enum to support marking certain members as deprecated. Deprecated members can be accessed, but doing so will emit a DeprecationWarning, specifically a DeprecatedMemberWarning.

To declare a deprecated member, use the deprecated_member() function.

When using the enum constructor (i.e. MyEnum(value)), a warning is only emitted if the resolved member has no non-deprecated aliases. If there is at least one non-deprecated alias for the member, no warning is emitted.

Example
from frequenz.core.enum import Enum, deprecated_member

class TaskStatus(Enum):
    OPEN = 1
    IN_PROGRESS = 2
    PENDING = deprecated_member(1, "PENDING is deprecated, use OPEN instead")
    DONE = deprecated_member(3, "DONE is deprecated, use FINISHED instead")
    FINISHED = 4

# Accessing deprecated members:
status1 = TaskStatus.PENDING  # Warns: "PENDING is deprecated, use OPEN instead"
assert status1 is TaskStatus.OPEN

status2 = TaskStatus["DONE"]  # Warns: "DONE is deprecated, use FINISHED instead"
assert status2 is TaskStatus.FINISHED

status3 = TaskStatus(1)  # No warning, resolves to OPEN which has a non-deprecated alias
assert status3 is TaskStatus.OPEN

status4 = TaskStatus(3)  # Warns: "DONE is deprecated, use FINISHED instead"
assert status4 is TaskStatus.FINISHED
Source code in frequenz/core/enum.py
class Enum(enum.Enum, metaclass=DeprecatingEnumType):
    """Base class for enums that support deprecated members.

    This class extends the standard library's [`enum.Enum`][] to support marking
    certain members as deprecated. Deprecated members can be accessed, but doing so
    will emit a [`DeprecationWarning`][], specifically
    a [`DeprecatedMemberWarning`][frequenz.core.enum.DeprecatedMemberWarning].

    To declare a deprecated member, use the
    [`deprecated_member()`][frequenz.core.enum.deprecated_member] function.

    When using the enum constructor (i.e. `MyEnum(value)`), a warning is only emitted if
    the resolved member has no non-deprecated aliases. If there is at least one
    non-deprecated alias for the member, no warning is emitted.

    Example:
        ```python
        from frequenz.core.enum import Enum, deprecated_member

        class TaskStatus(Enum):
            OPEN = 1
            IN_PROGRESS = 2
            PENDING = deprecated_member(1, "PENDING is deprecated, use OPEN instead")
            DONE = deprecated_member(3, "DONE is deprecated, use FINISHED instead")
            FINISHED = 4

        # Accessing deprecated members:
        status1 = TaskStatus.PENDING  # Warns: "PENDING is deprecated, use OPEN instead"
        assert status1 is TaskStatus.OPEN

        status2 = TaskStatus["DONE"]  # Warns: "DONE is deprecated, use FINISHED instead"
        assert status2 is TaskStatus.FINISHED

        status3 = TaskStatus(1)  # No warning, resolves to OPEN which has a non-deprecated alias
        assert status3 is TaskStatus.OPEN

        status4 = TaskStatus(3)  # Warns: "DONE is deprecated, use FINISHED instead"
        assert status4 is TaskStatus.FINISHED
        ```
    """

    __deprecated_names__: ClassVar[Mapping[str, str]]
    __deprecated_value_map__: ClassVar[Mapping[Self, str]]

Functions¤

frequenz.core.enum.deprecated_member ¤

deprecated_member(value: ValueT, message: str) -> ValueT

Mark an enum member as deprecated.

Please read the Enum documentation for details and examples.

PARAMETER DESCRIPTION
value

The value of the enum member to mark as deprecated.

TYPE: ValueT

message

The deprecation message to be shown when the member is accessed.

TYPE: str

RETURNS DESCRIPTION
ValueT

The wrapped value, to mark the enum member as deprecated.

Source code in frequenz/core/enum.py
def deprecated_member(value: ValueT, message: str) -> ValueT:
    """Mark an enum member as deprecated.

    Please read the [`Enum`][frequenz.core.enum.Enum] documentation for details and
    examples.

    Args:
        value: The value of the enum member to mark as deprecated.
        message: The deprecation message to be shown when the member is accessed.

    Returns:
        The wrapped value, to mark the enum member as deprecated.
    """
    return cast(ValueT, DeprecatedMember(value, message))

frequenz.core.enum.unique ¤

unique(enumeration: type[EnumT]) -> type[EnumT]

Class decorator for enums that ensures unique non-deprecated values.

This works similarly to @enum.unique, but it only enforces uniqueness for members that are not deprecated. This allows deprecated members to be aliases for non-deprecated members without causing a ValueError.

If you need strict uniqueness for all deprecated and non-deprecated members, use @enum.unique instead.

Example
from frequenz.core.enum import Enum, deprecated_member, unique

@unique
class TaskStatus(Enum):
    OPEN = 1
    IN_PROGRESS = 2
    # This is okay, as PENDING is a deprecated alias.
    PENDING = deprecated_member(1, "Use OPEN instead")
PARAMETER DESCRIPTION
enumeration

The enum class to decorate.

TYPE: type[EnumT]

RETURNS DESCRIPTION
type[EnumT]

The decorated enum class.

RAISES DESCRIPTION
ValueError

If duplicate values are found among non-deprecated members.

Source code in frequenz/core/enum.py
def unique(enumeration: type[EnumT]) -> type[EnumT]:
    """Class decorator for enums that ensures unique non-deprecated values.

    This works similarly to [`@enum.unique`][enum.unique], but it only enforces
    uniqueness for members that are not deprecated. This allows deprecated members to
    be aliases for non-deprecated members without causing a `ValueError`.

    If you need strict uniqueness for all deprecated and non-deprecated members, use
    [`@enum.unique`][enum.unique] instead.

    Example:
        ```python
        from frequenz.core.enum import Enum, deprecated_member, unique

        @unique
        class TaskStatus(Enum):
            OPEN = 1
            IN_PROGRESS = 2
            # This is okay, as PENDING is a deprecated alias.
            PENDING = deprecated_member(1, "Use OPEN instead")
        ```

    Args:
        enumeration: The enum class to decorate.

    Returns:
        The decorated enum class.

    Raises:
        ValueError: If duplicate values are found among non-deprecated members.
    """
    # Retrieve the map of deprecated names created by the metaclass.
    deprecated_names = enumeration.__dict__.get("__deprecated_names__", {})

    duplicates = []
    seen_values: dict[Any, str] = {}
    for member_name, member in enumeration.__members__.items():
        # Ignore members that are marked as deprecated.
        if member_name in deprecated_names:
            continue

        value = member.value
        if value in seen_values:
            duplicates.append((member_name, seen_values[value]))
        else:
            seen_values[value] = member_name

    if duplicates:
        alias_details = ", ".join(
            f"{name!r} -> {alias!r}" for name, alias in duplicates
        )
        raise ValueError(
            f"duplicate values found in {enumeration.__name__}: {alias_details}"
        )

    return enumeration