Source code for hbutils.model.enum

"""
Overview:
    Useful utilities for python enum class.
"""
from enum import IntEnum, unique
from functools import lru_cache
from types import MethodType
from typing import Type, Optional, Callable, TypeVar, Any

__all__ = [
    'AutoIntEnum', 'int_enum_loads'
]


[docs]class AutoIntEnum(IntEnum): """ Overview: An example from `official documentation <https://docs.python.org/3/library/enum.html#using-a-custom-new>`_. Examples:: >>> from hbutils.model import AutoIntEnum >>> class MyEnum(AutoIntEnum): ... def __init__(self, v): ... self.v = v ... A = 'a_v' ... B = 'b_vv' ... C = 'c_vvv' ... >>> MyEnum.A <MyEnum.A: 1> >>> MyEnum.A.value 1 >>> MyEnum.A.v 'a_v' >>> MyEnum.C <MyEnum.C: 3> >>> MyEnum.C.value 3 >>> MyEnum.C.v 'c_vvv' """ def __new__(cls, *args, **kwargs): value = len(cls.__members__) + 1 obj = int.__new__(cls, value) obj._value_ = value return obj
_EnumType = TypeVar('_EnumType', bound=IntEnum) def _default_value_preprocess(value: int): return value def _default_name_preprocess(name: str): return name def _get_default_external_preprocess(enum_class: Type[_EnumType]): def _default_external_preprocess(data): raise TypeError('Unknown type {type} for loads to {cls}.'.format( type=repr(type(data).__name__), cls=repr(enum_class.__name__), )) return _default_external_preprocess
[docs]def int_enum_loads(enable_int: bool = True, value_preprocess: Optional[Callable[[int, ], int]] = None, enable_str: bool = True, name_preprocess: Optional[Callable[[str, ], str]] = None, external_process: Optional[Callable[[Any, ], Optional[_EnumType]]] = None): """ Overview: Decorate a int enum class with a new `loads` class method. Arguments: - enable_int (:obj:`bool`): Enable int parse, default is `True`. - value_preprocess (:obj:`Optional[Callable[[int, ], int]]`): Preprocessor of value, \ default is `None` which means no change. - enable_str (:obj:`bool`): Enable str parse, default is `True`. - name_preprocess (:obj:`Optional[Callable[[str, ], str]]`): Preprocessor of name, \ default is `None` which means no change. - external_process (:obj:`Optional[Callable[[Any, ], Optional[_EnumType]]]`): External processor \ for the unprocessable data, default is `None` which means raise a `KeyError`. Examples: - Simple usage >>> from enum import IntEnum, unique >>> >>> @int_enum_loads() >>> @unique >>> class MyEnum(IntEnum): >>> A = 1 >>> B = 2 >>> >>> MyEnum.loads(1) # MyEnum.A >>> MyEnum.loads('A') # MyEnum.A >>> MyEnum.loads(2) # MyEnum.B >>> MyEnum.loads('B') # MyEnum.B >>> MyEnum.loads(-1) # KeyError >>> MyEnum.loads('a') # KeyError >>> MyEnum.loads('C') # KeyError - Preprocessors >>> from enum import IntEnum, unique >>> >>> @int_enum_loads(name_preprocess=str.upper, value_preprocess=abs) >>> @unique >>> class MyEnum(IntEnum): >>> A = 1 >>> B = 2 >>> >>> MyEnum.loads(1) # MyEnum.A >>> MyEnum.loads('A') # MyEnum.A >>> MyEnum.loads(2) # MyEnum.B >>> MyEnum.loads('B') # MyEnum.B >>> MyEnum.loads(-1) # MyEnum.A >>> MyEnum.loads('a') # MyEnum.A >>> MyEnum.loads('C') # KeyError - External processor >>> from enum import IntEnum, unique >>> >>> @int_enum_loads(external_process=lambda data: None) >>> @unique >>> class MyEnum(IntEnum): >>> A = 1 >>> B = 2 >>> >>> MyEnum.loads(1) # MyEnum.A >>> MyEnum.loads('A') # MyEnum.A >>> MyEnum.loads(2) # MyEnum.B >>> MyEnum.loads('B') # MyEnum.B >>> MyEnum.loads(-1) # None >>> MyEnum.loads('a') # None >>> MyEnum.loads('C') # None """ value_preprocess = value_preprocess or _default_value_preprocess name_preprocess = name_preprocess or _default_name_preprocess def _decorator(enum_class: Type[_EnumType]): if not issubclass(enum_class, IntEnum): raise TypeError('Int enum expected but {type} found.'.format(type=repr(enum_class.__name__))) enum_class = unique(enum_class) @lru_cache() def _dict_item(): return {key: value for key, value in enum_class.__members__.items()} @lru_cache() def _int_value_to_item(): return {value.value: value for _, value in _dict_item().items()} @lru_cache() def _str_name_to_item(): return {name_preprocess(key): value for key, value in _dict_item().items()} def _load_func(data) -> Optional[enum_class]: if isinstance(data, enum_class): return data elif enable_int and isinstance(data, int): return _int_value_to_item()[value_preprocess(data)] elif enable_str and isinstance(data, str): return _str_name_to_item()[name_preprocess(data)] else: return (external_process or _get_default_external_preprocess(enum_class))(data) def loads(cls, data) -> Optional[enum_class]: """ Overview: Load enum data from raw data. Arguments: - data (:obj:`Any`): Data which going to be parsed. Returns: - enum_data (:obj:): Parsed enum data """ return _load_func(data) loads.__qualname__ = f'{enum_class.__qualname__}.{loads.__name__}' enum_class.loads = MethodType(loads, enum_class) return enum_class return _decorator