Source code for named_enum.enum

# -*- coding: utf-8 -*-
"""Module for the classes extending the default `Enum` class. It contains four
classes:
`ExtendedEnum`, `ExtendedEnum`, `LabeledEnum`, `PairEnum` and one function
`namedenum`.
"""
import sys as _sys
from enum import Enum
from typing import (
    Any, Optional, Sequence, Tuple, Union
)
from .meta import NamedEnumMeta

__all__ = [
    'NamedEnum', 'ExtendedEnum', 'LabeledEnum', 'PairEnum', 'namedenum'
]


[docs]class NamedEnum(Enum, metaclass=NamedEnumMeta): """Through the value of variable `_field_names_` to control its subclass for different use cases: 1. value of `_field_names_` is `None` or empty. In this case, its subclass works like an extended Enum class with extra function: `names`, `values`, `as_dict`, `as_list`, `as_set`, `as_tuple`, `as_ordereddict`, `describe`. 2. value of `_field_names_` is neither `None` or empty. In this case, its subclass keeps the extra functions mentioned in **1**, and gives each element in the enumeration item's value a name and provides functions for each attribute/field, like: `<field_name>s`, `from_<field_name>`, `has_<field_name>`. Instead of the setting the attributes to the enumeration instance, it uses the function `__getattr__` to achieve it. Examples: >>> class TripleEnum(NamedEnum): ... _field_names_ = ("first", "second", "third") >>> class Triangle(TripleEnum): ... EQUILATERAL = (6, 6, 6) ... RIGHT = (3, 4, 5) >>> Triangle._fields() ('first', 'second', 'third') >>> Triangle.names() ('EQUILATERAL', 'RIGHT') >>> Triangle.values() (NamedTuple(first=6, second=6, third=6), NamedTuple(first=3, second=4, third=5)) >>> Triangle.describe() Class: Triangle Name | First | Second | Third ------------------------------------ EQUILATERAL | 6 | 6 | 6 RIGHT | 3 | 4 | 5 <BLANKLINE> >>> Triangle.as_dict() {'EQUILATERAL': NamedTuple(first=6, second=6, third=6), 'RIGHT': NamedTuple(first=3, second=4, third=5)} >>> Triangle.as_list() [('EQUILATERAL', NamedTuple(first=6, second=6, third=6)), ('RIGHT', NamedTuple(first=3, second=4, third=5))] >>> Triangle.as_set() == {('RIGHT', Triangle._tuple_cls(first=3, second=4, third=5)), ... ('EQUILATERAL', Triangle._tuple_cls(first=6, second=6, third=6))} True >>> Triangle.as_tuple() (('EQUILATERAL', NamedTuple(first=6, second=6, third=6)), ('RIGHT', NamedTuple(first=3, second=4, third=5))) >>> Triangle.as_ordereddict() OrderedDict([('EQUILATERAL', NamedTuple(first=6, second=6, third=6)), ('RIGHT', NamedTuple(first=3, second=4, third=5))]) >>> Triangle.firsts() (6, 3) >>> Triangle.seconds() (6, 4) >>> Triangle.thirds() (6, 5) >>> Triangle.from_first(6) (<Triangle.EQUILATERAL: NamedTuple(first=6, second=6, third=6)>,) >>> Triangle.from_first(66) () >>> Triangle.from_second(6) (<Triangle.EQUILATERAL: NamedTuple(first=6, second=6, third=6)>,) >>> Triangle.from_second(66) () >>> Triangle.from_third(6) (<Triangle.EQUILATERAL: NamedTuple(first=6, second=6, third=6)>,) >>> Triangle.from_third(66) () >>> Triangle.has_first(6) True >>> Triangle.has_first(66) False >>> Triangle.has_second(6) True >>> Triangle.has_second(66) False >>> Triangle.has_third(6) True >>> Triangle.has_third(66) False >>> Triangle.RIGHT <Triangle.RIGHT: NamedTuple(first=3, second=4, third=5)> >>> Triangle.RIGHT.first 3 >>> Triangle.RIGHT.second 4 >>> Triangle.RIGHT.third 5 >>> Triangle.RIGHT.name 'RIGHT' >>> Triangle.RIGHT.value NamedTuple(first=3, second=4, third=5) >>> print(Triangle.RIGHT) Triangle.RIGHT: NamedTuple(first=3, second=4, third=5) """ _field_names_: Union[Tuple[str], None] = None """The place to define the field names of the enumeration class. It accepts the same format as the parameter `field_names` in function `namedtuple` from `collections` package. It's used in the NamedEnumMeta class's `__new__` function to generate the corresponding functions for each field. If it's value is `None` or empty, then the enumeration class behaves like a normal `Enum` class, but with some extended functions to simplify the usages of enumerations. **Attention**: this variable should not be used to get the field_names, to do so you can use the class method `_fields`. Because it also accept the comma separated string.""" def __getattr__(self, item: str) -> Any: """Hijacks the default `__getattr__` function, such that every time when the user wants to get the value of a field in an enumeration item, it returns the corresponding field's value from the value of enumeration. Args: item (str): name of the field or attribute. Returns: Any: corresponding value. """ if item in self.__class__._fields(): return getattr(self._value_, item) return super().__getattribute__(item) def __str__(self) -> str: """Displays the value as well. Returns: str: string represents the enumeration item. """ return "%s.%s: %r" % ( self.__class__.__name__, self._name_, self._value_)
[docs]class ExtendedEnum(NamedEnum): """An alias for the class `NamedEnum`. The goal is explicit directly providing the users an Enum class with extra functions. Examples: >>> from types import GeneratorType >>> class TVCouple(ExtendedEnum): ... GALLAGHERS = ("FRANK", "MONICA") ... MIKE_AND_MOLLY = ("Mike", "Molly") >>> TVCouple.names() ('GALLAGHERS', 'MIKE_AND_MOLLY') >>> isinstance(TVCouple.names(as_tuple=False), GeneratorType) True >>> list(TVCouple.names(as_tuple=False)) ['GALLAGHERS', 'MIKE_AND_MOLLY'] >>> TVCouple.values() (('FRANK', 'MONICA'), ('Mike', 'Molly')) >>> isinstance(TVCouple.values(as_tuple=False), GeneratorType) True >>> list(TVCouple.values(as_tuple=False)) [('FRANK', 'MONICA'), ('Mike', 'Molly')] >>> TVCouple.describe() Class: TVCouple Name | Value ------------------------------------ GALLAGHERS | ('FRANK', 'MONICA') MIKE_AND_MOLLY | ('Mike', 'Molly') <BLANKLINE> >>> isinstance(TVCouple.gen(), GeneratorType) True >>> tuple(TVCouple.gen()) (('GALLAGHERS', ('FRANK', 'MONICA')), ('MIKE_AND_MOLLY', ('Mike', 'Molly'))) >>> isinstance(TVCouple.gen(name_value_pair=False), GeneratorType) True >>> tuple(TVCouple.gen(name_value_pair=False)) (<TVCouple.GALLAGHERS: ('FRANK', 'MONICA')>, <TVCouple.MIKE_AND_MOLLY: ('Mike', 'Molly')>) >>> TVCouple.as_dict() {'GALLAGHERS': ('FRANK', 'MONICA'), 'MIKE_AND_MOLLY': ('Mike', 'Molly')} >>> isinstance(TVCouple.as_set(), set) True >>> sorted(list(TVCouple.as_set())) [('GALLAGHERS', ('FRANK', 'MONICA')), ('MIKE_AND_MOLLY', ('Mike', 'Molly'))] >>> TVCouple.as_tuple() (('GALLAGHERS', ('FRANK', 'MONICA')), ('MIKE_AND_MOLLY', ('Mike', 'Molly'))) >>> TVCouple.as_list() [('GALLAGHERS', ('FRANK', 'MONICA')), ('MIKE_AND_MOLLY', ('Mike', 'Molly'))] >>> TVCouple.as_ordereddict() OrderedDict([('GALLAGHERS', ('FRANK', 'MONICA')), ('MIKE_AND_MOLLY', ('Mike', 'Molly'))]) """ pass
[docs]class LabeledEnum(NamedEnum): """An enumeration class with two attributes `key` and `label`. It can be used in the Django project as the choices of a field in model or form. Examples: >>> from types import GeneratorType >>> class NBALegendary(LabeledEnum): ... JOHNSON = ("Johnson", "Magic Johnson") ... Jordan = ("Jordan", "Air Jordan") >>> NBALegendary.names() ('JOHNSON', 'Jordan') >>> isinstance(NBALegendary.names(as_tuple=False), GeneratorType) True >>> list(NBALegendary.names(as_tuple=False)) ['JOHNSON', 'Jordan'] >>> NBALegendary.values() (NamedTuple(key='Johnson', label='Magic Johnson'), NamedTuple(key='Jordan', label='Air Jordan')) >>> isinstance(NBALegendary.values(as_tuple=False), GeneratorType) True >>> list(NBALegendary.values(as_tuple=False)) [NamedTuple(key='Johnson', label='Magic Johnson'), NamedTuple(key='Jordan', label='Air Jordan')] >>> NBALegendary.describe() Class: NBALegendary Name | Key | Label --------------------------------- JOHNSON | Johnson | Magic Johnson Jordan | Jordan | Air Jordan <BLANKLINE> >>> isinstance(NBALegendary.gen(), GeneratorType) True >>> tuple(NBALegendary.gen()) (('JOHNSON', NamedTuple(key='Johnson', label='Magic Johnson')), ('Jordan', NamedTuple(key='Jordan', label='Air Jordan'))) >>> isinstance(NBALegendary.gen(name_value_pair=False), GeneratorType) True >>> tuple(NBALegendary.gen(name_value_pair=False)) (<NBALegendary.JOHNSON: NamedTuple(key='Johnson', label='Magic Johnson')>, <NBALegendary.Jordan: NamedTuple(key='Jordan', label='Air Jordan')>) >>> NBALegendary.as_dict() {'JOHNSON': NamedTuple(key='Johnson', label='Magic Johnson'), 'Jordan': NamedTuple(key='Jordan', label='Air Jordan')} >>> isinstance(NBALegendary.as_set(), set) True >>> sorted(list(NBALegendary.as_set())) [('JOHNSON', NamedTuple(key='Johnson', label='Magic Johnson')), ('Jordan', NamedTuple(key='Jordan', label='Air Jordan'))] >>> NBALegendary.as_tuple() (('JOHNSON', NamedTuple(key='Johnson', label='Magic Johnson')), ('Jordan', NamedTuple(key='Jordan', label='Air Jordan'))) >>> NBALegendary.as_list() [('JOHNSON', NamedTuple(key='Johnson', label='Magic Johnson')), ('Jordan', NamedTuple(key='Jordan', label='Air Jordan'))] >>> NBALegendary.as_ordereddict() OrderedDict([('JOHNSON', NamedTuple(key='Johnson', label='Magic Johnson')), ('Jordan', NamedTuple(key='Jordan', label='Air Jordan'))]) >>> NBALegendary.keys() ('Johnson', 'Jordan') >>> NBALegendary.labels() ('Magic Johnson', 'Air Jordan') >>> isinstance(NBALegendary.keys(as_tuple=False), GeneratorType) True >>> list(NBALegendary.keys(as_tuple=False)) ['Johnson', 'Jordan'] >>> isinstance(NBALegendary.labels(as_tuple=False), GeneratorType) True >>> list(NBALegendary.labels(as_tuple=False)) ['Magic Johnson', 'Air Jordan'] >>> NBALegendary.from_key('Johnson') (<NBALegendary.JOHNSON: NamedTuple(key='Johnson', label='Magic Johnson')>,) >>> NBALegendary.from_key('Jordan') (<NBALegendary.Jordan: NamedTuple(key='Jordan', label='Air Jordan')>,) >>> NBALegendary.from_label('Magic Johnson') (<NBALegendary.JOHNSON: NamedTuple(key='Johnson', label='Magic Johnson')>,) >>> NBALegendary.from_label('Air Jordan') (<NBALegendary.Jordan: NamedTuple(key='Jordan', label='Air Jordan')>,) >>> isinstance(NBALegendary.from_key('Johnson', as_tuple=False), GeneratorType) True >>> list(NBALegendary.from_key('Johnson', as_tuple=False)) [<NBALegendary.JOHNSON: NamedTuple(key='Johnson', label='Magic Johnson')>] >>> isinstance(NBALegendary.from_key('Jordan', as_tuple=False), GeneratorType) True >>> list(NBALegendary.from_key('Jordan', as_tuple=False)) [<NBALegendary.Jordan: NamedTuple(key='Jordan', label='Air Jordan')>] >>> isinstance(NBALegendary.from_label('Magic Johnson', as_tuple=False), GeneratorType) True >>> list(NBALegendary.from_label('Magic Johnson', as_tuple=False)) [<NBALegendary.JOHNSON: NamedTuple(key='Johnson', label='Magic Johnson')>] >>> isinstance(NBALegendary.from_label('Air Jordan', as_tuple=False), GeneratorType) True >>> list(NBALegendary.from_label('Air Jordan', as_tuple=False)) [<NBALegendary.Jordan: NamedTuple(key='Jordan', label='Air Jordan')>] >>> NBALegendary.has_key('Johnson') True >>> NBALegendary.has_key('John') False >>> NBALegendary.has_key('Jordan') True >>> NBALegendary.has_key('George') False >>> NBALegendary.has_label('Magic Johnson') True >>> NBALegendary.has_label('King James') False >>> NBALegendary.has_label('Air Jordan') True >>> NBALegendary.has_label('The Black Mamba') False """ _field_names_ = ("key", "label") # type: ignore """Each enumeration of LabeledEnum has two attributes: `key`, `label`"""
[docs]class PairEnum(NamedEnum): """Enumeration with two attributes `first`, `second`, the idea comes from the C++'s pair container. Examples: >>> from types import GeneratorType >>> class Pair(PairEnum): ... TOM_AND_JERRY = ("Tom", "Jerry") ... BULLS = ("Micheal", "Pippen") >>> Pair.names() ('TOM_AND_JERRY', 'BULLS') >>> isinstance(Pair.names(as_tuple=False), GeneratorType) True >>> list(Pair.names(as_tuple=False)) ['TOM_AND_JERRY', 'BULLS'] >>> Pair.values() (NamedTuple(first='Tom', second='Jerry'), NamedTuple(first='Micheal', second='Pippen')) >>> isinstance(Pair.values(as_tuple=False), GeneratorType) True >>> list(Pair.values(as_tuple=False)) [NamedTuple(first='Tom', second='Jerry'), NamedTuple(first='Micheal', second='Pippen')] >>> Pair.describe() Class: Pair Name | First | Second -------------------------------- TOM_AND_JERRY | Tom | Jerry BULLS | Micheal | Pippen <BLANKLINE> >>> isinstance(Pair.gen(), GeneratorType) True >>> tuple(Pair.gen()) (('TOM_AND_JERRY', NamedTuple(first='Tom', second='Jerry')), ('BULLS', NamedTuple(first='Micheal', second='Pippen'))) >>> isinstance(Pair.gen(name_value_pair=False), GeneratorType) True >>> tuple(Pair.gen(name_value_pair=False)) (<Pair.TOM_AND_JERRY: NamedTuple(first='Tom', second='Jerry')>, <Pair.BULLS: NamedTuple(first='Micheal', second='Pippen')>) >>> Pair.as_dict() {'TOM_AND_JERRY': NamedTuple(first='Tom', second='Jerry'), 'BULLS': NamedTuple(first='Micheal', second='Pippen')} >>> isinstance(Pair.as_set(), set) True >>> sorted(list(Pair.as_set())) [('BULLS', NamedTuple(first='Micheal', second='Pippen')), ('TOM_AND_JERRY', NamedTuple(first='Tom', second='Jerry'))] >>> Pair.as_tuple() (('TOM_AND_JERRY', NamedTuple(first='Tom', second='Jerry')), ('BULLS', NamedTuple(first='Micheal', second='Pippen'))) >>> Pair.as_list() [('TOM_AND_JERRY', NamedTuple(first='Tom', second='Jerry')), ('BULLS', NamedTuple(first='Micheal', second='Pippen'))] >>> Pair.as_ordereddict() OrderedDict([('TOM_AND_JERRY', NamedTuple(first='Tom', second='Jerry')), ('BULLS', NamedTuple(first='Micheal', second='Pippen'))]) >>> Pair.firsts() ('Tom', 'Micheal') >>> Pair.seconds() ('Jerry', 'Pippen') >>> isinstance(Pair.firsts(as_tuple=False), GeneratorType) True >>> list(Pair.firsts(as_tuple=False)) ['Tom', 'Micheal'] >>> isinstance(Pair.seconds(as_tuple=False), GeneratorType) True >>> list(Pair.seconds(as_tuple=False)) ['Jerry', 'Pippen'] >>> Pair.from_first("Tom") (<Pair.TOM_AND_JERRY: NamedTuple(first='Tom', second='Jerry')>,) >>> Pair.from_first("Micheal") (<Pair.BULLS: NamedTuple(first='Micheal', second='Pippen')>,) >>> Pair.from_second("Jerry") (<Pair.TOM_AND_JERRY: NamedTuple(first='Tom', second='Jerry')>,) >>> Pair.from_second("Pippen") (<Pair.BULLS: NamedTuple(first='Micheal', second='Pippen')>,) >>> isinstance(Pair.from_first("Tom", as_tuple=False), GeneratorType) True >>> list(Pair.from_first("Tom", as_tuple=False)) [<Pair.TOM_AND_JERRY: NamedTuple(first='Tom', second='Jerry')>] >>> isinstance(Pair.from_first("Micheal", as_tuple=False), GeneratorType) True >>> list(Pair.from_first("Micheal", as_tuple=False)) [<Pair.BULLS: NamedTuple(first='Micheal', second='Pippen')>] >>> isinstance(Pair.from_second("Jerry", as_tuple=False), GeneratorType) True >>> list(Pair.from_second("Jerry", as_tuple=False)) [<Pair.TOM_AND_JERRY: NamedTuple(first='Tom', second='Jerry')>] >>> isinstance(Pair.from_second("Pippen", as_tuple=False), GeneratorType) True >>> list(Pair.from_second("Pippen", as_tuple=False)) [<Pair.BULLS: NamedTuple(first='Micheal', second='Pippen')>] >>> Pair.has_first('Tom') True >>> Pair.has_first('Tommy') False >>> Pair.has_first('Micheal') True >>> Pair.has_first('Mike') False >>> Pair.has_second('Jerry') True >>> Pair.has_second('Jeremy') False >>> Pair.has_second('Pippen') True >>> Pair.has_second('Pepe') False """ _field_names_ = ("first", "second") # type: ignore """Each enumeration of PairEnum has two attributes: first, second"""
_class_template = """\ from named_enum import NamedEnum class {typename}(NamedEnum): _field_names_ = {field_names!r} """
[docs]def namedenum(typename: str, field_names: Optional[Sequence] = None, *, verbose: Optional[bool] = False, module: Optional[str] = None) -> object: """Creates an named enum class with the given typename as class name and field_names as the _field_names_ in named enum class. The implementation is similar to the namedtuple function. Args: typename (str): name for the created class. field_names (Optional[Sequence]): field names for the named enum class. verbose (Optional[bool]): displays the code for the named enum class creation, if True. module (Optional[str]): which module the new created enum class belongs to. Returns: object: subclass of NamedEnum Examples: >>> TripleEnum = namedenum("TripleEnum", ("first", "second", "third")) >>> TripleEnum <named enum 'TripleEnum'> """ # Fill-in the class template class_definition = _class_template.format( typename=typename, field_names=field_names ) # Execute the template string in a temporary namespace and support # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(__name__='%s' % typename) exec(class_definition, namespace) # nosec result = namespace[typename] result._source = class_definition # type: ignore if verbose: print(result._source) # type: ignore # For pickling to work, the __module__ variable needs to be set to the frame # where the named tuple is created. Bypass this step in environments where # sys._getframe is not defined (Jython for example) or sys._getframe is not # defined for arguments greater than 0 (IronPython), or where the user has # specified a particular module. if module is None: try: module = _sys._getframe(1).f_globals.get('__name__', '__main__') except (AttributeError, ValueError): pass if module is not None: result.__module__ = module return result