Source code for openfisca_core.indexed_enums.enum

from __future__ import annotations

from collections.abc import Sequence

import numpy

from . import types as t
from ._enum_type import EnumType
from ._errors import EnumEncodingError, EnumMemberNotFoundError
from ._guards import (
    _is_enum_array,
    _is_enum_array_like,
    _is_int_array,
    _is_int_array_like,
    _is_str_array,
    _is_str_array_like,
)
from ._utils import _enum_to_index, _int_to_index, _str_to_index
from .enum_array import EnumArray


[docs] class Enum(t.Enum, metaclass=EnumType): """Enum based on `enum34 <https://pypi.python.org/pypi/enum34/>`_. Its items have an :class:`int` index, useful and performant when running :mod:`~openfisca_core.simulations` on large :mod:`~openfisca_core.populations`. Examples: >>> from openfisca_core import indexed_enums as enum >>> class Housing(enum.Enum): ... OWNER = "Owner" ... TENANT = "Tenant" ... FREE_LODGER = "Free lodger" ... HOMELESS = "Homeless" >>> repr(Housing) "<enum 'Housing'>" >>> repr(Housing.TENANT) 'Housing.TENANT' >>> str(Housing.TENANT) 'Housing.TENANT' >>> dict([(Housing.TENANT, Housing.TENANT.value)]) {Housing.TENANT: 'Tenant'} >>> list(Housing) [Housing.OWNER, Housing.TENANT, Housing.FREE_LODGER, Housing.HOMELESS] >>> Housing["TENANT"] Housing.TENANT >>> Housing("Tenant") Housing.TENANT >>> Housing.TENANT in Housing True >>> len(Housing) 4 >>> Housing.TENANT == Housing.TENANT True >>> Housing.TENANT != Housing.TENANT False >>> Housing.TENANT.index 1 >>> Housing.TENANT.name 'TENANT' >>> Housing.TENANT.value 'Tenant' """ #: The :attr:`index` of the :class:`.Enum` member. index: int def __init__(self, *__args: object, **__kwargs: object) -> None: """Tweak :class:`enum.Enum` to add an :attr:`.index` to each enum item. When the enum is initialised, ``_member_names_`` contains the names of the already initialized items, so its length is the index of this item. Args: *__args: Positional arguments. **__kwargs: Keyword arguments. Examples: >>> import numpy >>> from openfisca_core import indexed_enums as enum >>> Housing = enum.Enum("Housing", "owner tenant") >>> Housing.tenant.index 1 >>> class Housing(enum.Enum): ... OWNER = "Owner" ... TENANT = "Tenant" >>> Housing.TENANT.index 1 >>> array = numpy.array([[1, 2], [3, 4]]) >>> array[Housing.TENANT.index] array([3, 4]) Note: ``_member_names_`` is undocumented in upstream :class:`enum.Enum`. """ self.index = len(self._member_names_) def __repr__(self) -> str: return f"{self.__class__.__name__}.{self.name}" def __hash__(self) -> int: return object.__hash__(self.__class__.__name__ + self.name)
[docs] def __eq__(self, other: object) -> bool: if ( isinstance(other, Enum) and self.__class__.__name__ == other.__class__.__name__ ): return self.index == other.index return NotImplemented
[docs] def __ne__(self, other: object) -> bool: if ( isinstance(other, Enum) and self.__class__.__name__ == other.__class__.__name__ ): return self.index != other.index return NotImplemented
[docs] @classmethod def encode(cls, array: t.VarArray | t.ArrayLike[object]) -> t.EnumArray: """Encode an encodable array into an :class:`.EnumArray`. Args: array: :class:`~numpy.ndarray` to encode. Returns: EnumArray: An :class:`.EnumArray` with the encoded input values. Examples: >>> import numpy >>> from openfisca_core import indexed_enums as enum >>> class Housing(enum.Enum): ... OWNER = "Owner" ... TENANT = "Tenant" # EnumArray >>> array = numpy.array([1]) >>> enum_array = enum.EnumArray(array, Housing) >>> Housing.encode(enum_array) EnumArray([Housing.TENANT]) # Array of Enum >>> array = numpy.array([Housing.TENANT]) >>> enum_array = Housing.encode(array) >>> enum_array == Housing.TENANT array([ True]) # Array of integers >>> array = numpy.array([1]) >>> enum_array = Housing.encode(array) >>> enum_array == Housing.TENANT array([ True]) # Array of strings >>> array = numpy.array(["TENANT"]) >>> enum_array = Housing.encode(array) >>> enum_array == Housing.TENANT array([ True]) # Array of bytes >>> array = numpy.array([b"TENANT"]) >>> enum_array = Housing.encode(array) Traceback (most recent call last): EnumEncodingError: Failed to encode "[b'TENANT']" of type 'bytes... .. seealso:: :meth:`.EnumArray.decode` for decoding. """ if isinstance(array, EnumArray): return array if len(array) == 0: return EnumArray(numpy.asarray(array, t.EnumDType), cls) if isinstance(array, Sequence): return cls._encode_array_like(array) return cls._encode_array(array)
@classmethod def _encode_array(cls, value: t.VarArray) -> t.EnumArray: if _is_int_array(value): indices = _int_to_index(cls, value) elif _is_str_array(value): # type: ignore[unreachable] indices = _str_to_index(cls, value) elif _is_enum_array(value) and cls.__name__ is value[0].__class__.__name__: indices = _enum_to_index(value) else: raise EnumEncodingError(cls, value) if indices.size != len(value): raise EnumMemberNotFoundError(cls) return EnumArray(indices, cls) @classmethod def _encode_array_like(cls, value: t.ArrayLike[object]) -> t.EnumArray: if _is_int_array_like(value): indices = _int_to_index(cls, value) elif _is_str_array_like(value): # type: ignore[unreachable] indices = _str_to_index(cls, value) elif _is_enum_array_like(value): indices = _enum_to_index(value) else: raise EnumEncodingError(cls, value) if indices.size != len(value): raise EnumMemberNotFoundError(cls) return EnumArray(indices, cls)
__all__ = ["Enum"]