Source code for runtimepy.enum

"""
A module implementing a runtime enumeration interface.
"""

# built-in
from enum import IntEnum as _IntEnum
from typing import Optional as _Optional
from typing import Union as _Union
from typing import cast as _cast

# third-party
from vcorelib.io.types import JsonObject as _JsonObject
from vcorelib.io.types import JsonValue as _JsonValue
from vcorelib.python import StrToBool

# internal
from runtimepy.enum.types import EnumType as _EnumType
from runtimepy.mapping import BoolMappingData as _BoolMappingData
from runtimepy.mapping import IntMappingData as _IntMappingData
from runtimepy.registry.bool import BooleanRegistry as _BooleanRegistry
from runtimepy.registry.item import RegistryItem as _RegistryItem
from runtimepy.registry.name import NameRegistry as _NameRegistry


[docs] class RuntimeEnum(_RegistryItem): """A class implementing a runtime enumeration.""" @property def is_boolean(self) -> bool: """Determine if this is a boolean enumeration.""" return self._bools is not None @property def is_integer(self) -> bool: """Determine if this is an integer enumeration.""" return self._ints is not None @property def ints(self) -> _NameRegistry: """Get the underlying integer enumeration.""" assert self._ints is not None, "Not an integer enumeration!" return self._ints @property def bools(self) -> _BooleanRegistry: """Get the underlying boolean enumeration.""" assert self._bools is not None, "Not a boolean enumeration!" return self._bools
[docs] def init(self, data: _JsonObject) -> None: """Perform implementation-specific initialization.""" super().init(data) self.type = _EnumType.normalize(str(data["type"])) self.primitive = str(data["primitive"]) # Use distinct storage attributes for each kind of underlying # enumeration. self._ints: _Optional[_NameRegistry] = None self._bools: _Optional[_BooleanRegistry] = None # It's not required for an enumeration to start with entries. data.setdefault("items", {}) if self.type is _EnumType.INT: self._ints = _NameRegistry.int_from_dict( _cast(_IntMappingData, data["items"]) ) else: self._bools = _BooleanRegistry.bool_from_dict( _cast(_BoolMappingData, data["items"]) ) self.default: _Optional[str] = None if "default" in data: self.default = self.as_str(_cast(int, data["default"]))
[docs] def asdict(self) -> _JsonObject: """Obtain a dictionary representing this instance.""" result: _JsonObject = { "id": self.id, "type": str(self.type), "primitive": self.primitive, } if self.default is not None: result["default"] = self.default if self.is_integer: result["items"] = _cast(_JsonValue, self.ints.asdict()) else: assert self._bools is not None result["items"] = _cast(_JsonValue, self.bools.asdict()) return result
[docs] def as_str(self, value: _Union[str, bool, int]) -> _Optional[str]: """Attempt to get an enumeration string.""" if self.is_integer: result = self.ints.name(value) else: result = self.bools.name(_cast(bool, value)) return result
[docs] def get_str(self, value: _Union[str, bool, int]) -> str: """Get an enumeration string.""" result = self.as_str(value) if result is None: raise KeyError(f"No enum entry for '{value}'") return result
[docs] def as_int(self, value: _Union[str, int]) -> _Optional[int]: """Attempt to get an enumeration integer.""" result = self.ints.identifier(value) # Try 'value' as a converted integer. if result is None and isinstance(value, str): try: result = self.ints.identifier(int(value)) except ValueError: pass return result
[docs] def get_int(self, value: _Union[str, int, bool]) -> int: """Get an enumeration integer.""" result: _Union[int, bool, None] = None if self._ints is not None: result = self.as_int(value) # Allow a boolean enumeration to also resolve integer values. if self._bools is not None: result = self.as_bool(_cast(bool, value)) if result is None: raise KeyError(f"No enum entry for '{value}'") return int(result)
[docs] def as_bool(self, value: _Union[str, bool]) -> _Optional[bool]: """Attempt to get an enumeration boolean.""" ident = self.bools.identifier(value) if ident is None and isinstance(value, str): parsed = StrToBool.parse(value) if parsed.valid: ident = parsed.result return ident
[docs] def get_bool(self, value: _Union[str, bool]) -> bool: """Get an enumeration boolean.""" result = self.as_bool(value) if result is None: raise KeyError(f"No enum entry for '{value}'") return result
[docs] def register_int(self, name: str) -> _Optional[int]: """Register an integer enumeration.""" return self.ints.register_name(name)
[docs] def register_bool(self, name: str, value: bool) -> bool: """Register a boolean enumeration.""" return self.bools.register(name, value)
[docs] @staticmethod def data_from_enum(enum: type[_IntEnum]) -> _JsonObject: """Get JSON data from an enumeration class.""" return { "type": "int", "items": {x.name.lower(): x.value for x in enum}, }
[docs] @staticmethod def from_enum( enum: type[_IntEnum], identifier: int, default: _Union[str, bool, int] = None, ) -> "RuntimeEnum": """Create a runtime enumeration from an enum class.""" data = RuntimeEnum.data_from_enum(enum) data["id"] = identifier if default is not None: data["default"] = default return RuntimeEnum.create(data)