Unverified Commit 00ca6a6d authored by ThereforeGames's avatar ThereforeGames Committed by GitHub

Add files via upload

parent 685f9631
3.1.8
=====
recalculate bits used after all flags created (sometimes needed when a custom
`__new__` is in place.
3.1.7
=====
update flag creation to (possibly) add bitwise operator methods to newly
created flags
update extend_enum() to work with 3.11 flags
3.1.6
=====
Update `dir()` on mixed enums to include mixed data type methods and
attributes.
Rename `enum_property` to `property` to match stdlib. Recommended usage is
`aenum.property` (prefix with module name).
Remove quadritic creation behavior.
BREAKING CHANGE BUG FIX that won't affect most people
Enums with a custom `__new__` that:
- use the enum machinery to generate the values; AND
- have keyword arguments set to a default (like `None`)
will fail to generate a missing value. To fix: remove the default value and
instead specify it on the member creation line.
BREAKING CHANGE
In Python 3.11 the `str()` of mixed enums will now match its `format()` which
will be the normal `str()` of the data type -- so for an IntEnum you'll see
`5` instead of `Perm.R|X`. This affects IntEnum, StrEnum, and IntFlag.
3.1.5
=====
fix support of `auto()` kwds
3.1.3
=====
rename `aenum.property` to `aenum.enum_property`
fix `enum_property` to work with `_init_` attributes
3.1.2
=====
fix `extend_enum()` for unhashable values
3.1.1
=====
fix `extend_enum()` for most cases
3.1.0
=====
AddValue is similar to the old AutoNumber: it will always activate, but
uses _generate_next_value_ to get the next value (so the user has some
control over the return data type instead of always getting an int).
BREAKING CHANGES
AutoValue is gone. It was superflous and its removal simplified the code.
Simply put the fields needed in an `_init_` and `_generate_next_value_`
will be called to supply the missing values (this is probably already what
is happening).
3.0.0
=====
standard Enum usage is unchanged
BREAKING CHANGES
Enum
- the more esoteric method of creating Enums have been modified or removed
- AutoNumber setting is gone, inherit from AutoNumberEnum instead
- creating members without specifying anything is removed (if you don't
know what this means, you weren't doing it)
Flag
- unique flags are canonical (i.e. flags with powers of two values such as
1, 2, 4, 8, 16, etc.)
- non-unique flags are aliases (i.e. values such as 3 or 7)
- iteration of Flag and flag members only uses canonical flags
ENHANCEMENTS
Member creation has been redone to match Python 3.10's methods. This also
allows all supported Pythons (2.7, 3.3+) to use the __set_name__ and
__init_subclass__ protocols (more robustly than in aenum 2.2.5)
CHANGES
enum_property() has been renamed to property() (old name still available, but
deprecated).
bin() replacement shows negative integers in twos-complement
2.2.5
=====
call __init_subclass__ after members have been added, and in Pythons < 3.6
call __set_name__ in Pythons < 3.6
do not convert/disallow private names
add iteration/len support to NamedConstant
2.2.4
=====
add support to Constant to retrieve members by value
--> class K(Constant):
... one = 1
... two = 2
--> K.one
<K.one: 1>
--> K(1)
<K.one: 1>
add pickle/deepcopy support to Constant
add support for Constant to use other Constant values
(resulting members /are not/ the same)
--> class C(Constant)
... one = K.one
... three = 3
--> C.one == K.one
True
--> C.one is K.one
False
AutoNumber and auto() now work together
Enum members are now added to the class as enum_property, which supports
unshadowing of parent class attributes when called on an Enum member:
--> class StrEnum(str, Enum):
... lower = 'lower'
... upper = 'upper'
... mixed = 'mixed'
--> StrEnum.lower
<StrEnum.lower: 'lower'>
--> StrEnum.lower.upper()
'LOWER'
--> StrEnum.upper
<StrEnum.upper: 'upper'>
--> StrEnum.upper.upper()
'UPPER'
2.2.3
=====
use members' type's methods __str__, __repr__, __format__, and
__reduce_ex__ if directly assigned in Enum class body; i.e.:
--> class Color(str, Enum):
... red = 'red'
... green = 'green'
... blue = 'blue'
... __str__ = str.__str__
--> print(repr(Color.green))
<Color.green: 'green'>
--> print(Color.green)
green
2.2.2
=====
replace _RouteClassAttributeToGetattr with enum_property (it is still
available as an alias)
support constant() and auto() being used together:
--> class Fruit(Flag):
... _order_ = 'apple banana lemon orange'
... apple = auto()
... banana = auto()
... lemon = auto()
... orange = auto()
... CitrusTypes = constant(lemon | orange)
--> list(Fruit)
[Fruit.apple, Fruit.banana, Fruit.lemon, Fruit.orange]
--> list(Fruit.CitrusTypes)
[Fruit.orange, Fruit.lemon]
--> Fruit.orange in Fruit.CitrusTypes
True
2.2.1
=====
allow Enums to be called without a value
class Color(Enum):
black = 0
red = 1
green = 2
blue = 3
#
@classmethod
def _missing_value_(cls, value):
if value is no_arg:
return cls.black
>>> Color()
<Color.black: 0>
allow Enum name use while constructing Enum (Python 3.4+ only)
--> class Color(Enum):
... _order_ = 'BLACK WHITE'
... BLACK = Color('black', '#000')
... WHITE = Color('white', '#fff')
... #
... def __init__(self, label, hex):
... self.label = label
... self.hex = hex
2.2.0
=====
BREAKING CHANGE
---------------
In Python 3+ classes defined inside an Enum no longer become members by
default; in Python 2 they still become members, but see below.
For cross-compatibility and full control two decorators are provided:
- @member --> forces item to become a member
- @nonmember --> excludes item from becoming a member
So to have an Enum that behaves the same in Python 2 and 3, use the
decorators (and other compatibility shims):
class Color(Enum):
_order_ = 'red green blue'
red = 1
green = 2
blue = 3
@nonmember
class Shades(Enum):
_order_ = 'light medium dark'
light = 1
medium = 2
dark = 3
2.1.4
=====
EnumMeta:
- change __member_new__ to __new_member__ (as the stdlib enum does)
- assign member name to enum() instances (an Enum helper for defining members)
- handle empty iterables when using functional API
- make auto() work with previous enum members
- keep searching mixins until base class is found
Enum:
- fix bug in Flag checks (ensure it is a Flag before checking the name)
- add multiple mixin support
- do not allow blank names (functional API)
- raise TypeError if _missing_* returns wrong type
- fix __format__ to honor custom __str__
extend_enum:
- support stdlib Enums
- use _generate_next_value_ if value not provided
general:
- standardize exception formatting
- use getfullargspec() in Python 3 (avoids deprecation warnings)
2.1.2
=====
when order is callable, save it for subclass use
2.1.1
=====
correctly raise TypeError for non-Enum containment checks
support combining names with | for Flag key access
support _order_ being a callable
2.1.0
=====
support Flags being combined with other data types:
- add _create_pseudo_member_values_
- add default __new__ and temporary _init_
2.0.10
======
ensure _ignore_ is set when _settings_ specified in body which includes
AutoValue
make Flag members iterable
2.0.9
=====
fix missing comma in __all__
fix extend_enum with custom __new__ methods
fix MultiValue with AutoNumber without _init_
2.0.8
=====
extend_enum now handles aliases and multivalues correctly
2.0.7
=====
support mixin types with extend_enum
init and AutoNumber can now work together
add test for new Enum using EnumMeta
add tests for variations of multivalue and init
prevent deletion of NamedConstant.constant
2.0.6
=====
constants cannot be deleted (they already couldn't be changed)
constants can be used to define other constants
2.0.5
=====
_init_ and MultiValue can now work together
2.0.4
=====
_init_ and AutoValue (and _generate_next_value_) can now work together to
supply missing values even when some of the required values per member are
absent
2.0.3
=====
add _missing_value_ and _missing_name_ methods, deprecate _missing_
make enum instances comparable
2.0.2
=====
both EnumMeta.__getattr__ and Enum.__new__ fall back to _missing_
2.0.1
=====
auto() now works with other data types
AutoNumber supports legacy Enums (fixed regression)
2.0.0
=====
Flag and IntFlag added.
1.4.7
=====
fix %-interpolation bug
defined SqlLiteEnum only if sqlite exists
support pyflakes
1.4.6
=====
version numbering error
1.4.5
=====
revert AutoNumberEnum to custom __new__ instead of AutoNumber
use _ignore_ to shield against AutoNumber magic
inherit start and init settings from base Enums
1.4.4
=====
enabled export as a decorator
enabled _order_ to replace __order__
enabled python2 support for settings, init, and start
1.4.3
=====
support _ignore_ for dynamically creating class bodies
1.4.2
=====
MultiValue, NoAlias, Unique, and init now work with Python 2
1.4.1
=====
Py3: added Enum creation flags: Auto, MultiValue, NoAlias, Unique
fixed extend_enum to honor Enum flags
1.4.0
=====
When possible aenum inherits from Python's own enum.
Breaking change: enum members now default to evaluating as True to maintain
compatibility with the stdlib.
Add your own __bool__ (__nonzero__ in Python 2) if need this behavior:
def __bool__(self):
return bool(self.value)
__nonzero__ = __bool__
Copyright (c) 2015, 2016, 2017, 2018 Ethan Furman.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
Neither the name Ethan Furman nor the names of any
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
This source diff could not be displayed because it is too large. You can view the blob instead.
from operator import div as _div_
from inspect import getargspec
def raise_with_traceback(exc, tb):
raise exc, None, tb
__all__ = ['_div_', 'getargspec', 'raise_with_traceback']
from inspect import getfullargspec as _getfullargspec
def getargspec(method):
args, varargs, keywords, defaults, _, _, _ = _getfullargspec(method)
return args, varargs, keywords, defaults
def raise_with_traceback(exc, tb):
raise exc.with_traceback(tb)
def raise_from_none(exc):
raise exc from None
``aenum`` --- support for advanced enumerations, namedtuples, and constants
===========================================================================
.. :synopsis:: enumerations are sets of symbolic names bound to unique,
constant values; namedtuples are fixed- or variable-length
tuples with the positions addressable by field name as well as by index;
constants are classes of named constants that cannot be rebound.
.. :moduleauthor:: Ethan Furman <ethan@stoneleaf.us>
----------------
An ``Enum`` is a set of symbolic names (members) bound to unique, constant
values. Within an enumeration, the members can be compared by identity, and
the enumeration itself can be iterated over.
A ``NamedTuple`` is a class-based, fixed-length tuple with a name for each
possible position accessible using attribute-access notation.
A ``NamedConstant`` is a class whose members cannot be rebound; it lacks all
other ``Enum`` capabilities, however; consequently, it can have duplicate
values. There is also a ``module`` function that can insert the
``NamedConstant`` class into ``sys.modules`` where it will appear to be a
module whose top-level names cannot be rebound.
.. note::
``constant`` refers to names not being rebound; mutable objects can be
mutated.
Module Contents
---------------
This module defines five enumeration classes that can be used to define unique
sets of names and values, one ``Enum`` class decorator, one ``NamedTuple``
class, one ``NamedConstant`` class, and several helpers.
``NamedConstant``
NamedConstant class for creating groups of constants. These names cannot be
rebound to other values.
``Enum``
Base class for creating enumerated constants. See section `Enum Functional API`_
for an alternate construction syntax.
``AddValue``
Flag specifying that ``_generate_next_value_`` should always be called to
provide the initial value for an enum member.
``MultiValue``
Flag specifying that each item of tuple value is a separate value for that
member; the first tuple item is the canonical one.
``NoAlias``
Flag specifying that duplicate valued members are distinct and not aliases;
by-value lookups are disabled.
``Unique``
Flag specifying that duplicate valued members are not allowed.
.. note::
The flags are inherited by the enumeration's subclasses. To use them in
Python 2 assign to ``_settings_`` in the class body.
``IntEnum``
Base class for creating enumerated constants that are also subclasses of ``int``.
``AutoNumberEnum``
Derived class that automatically assigns an ``int`` value to each member.
``OrderedEnum``
Derived class that adds ``<``, ``<=``, ``>=``, and ``>`` methods to an ``Enum``.
``UniqueEnum``
Derived class that ensures only one name is bound to any one value.
``unique``
Enum class decorator that ensures only one name is bound to any one value.
.. note::
the ``UniqueEnum`` class, the ``unique`` decorator, and the Unique
flag all do the same thing; you do not need to use more than one of
them at the same time.
``NamedTuple``
Base class for `creating NamedTuples`_, either by subclassing or via it's
functional API.
``constant``
Descriptor to add constant values to an ``Enum``, or advanced constants to
``NamedConstant``.
``convert``
Helper to transform target global variables into an ``Enum``.
``enum``
Helper for specifying keyword arguments when creating ``Enum`` members.
``export``
Helper for inserting ``Enum`` members and ``NamedConstant`` constants into a
namespace (usually ``globals()``.
``extend_enum``
Helper for adding new ``Enum`` members, both stdlib and aenum.
``module``
Function to take a ``NamedConstant`` or ``Enum`` class and insert it into
``sys.modules`` with the affect of a module whose top-level constant and
member names cannot be rebound.
``skip``
Descriptor to add a normal (non-``Enum`` member) attribute to an ``Enum``
or ``NamedConstant``.
Creating an Enum
----------------
Enumerations are created using the ``class`` syntax, which makes them
easy to read and write. An alternative creation method is described in
`Enum Functional API`_. To define an enumeration, subclass ``Enum`` as
follows::
>>> from aenum import Enum
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
*Nomenclature*
- The class ``Color`` is an *enumeration* (or *enum*)
- The attributes ``Color.red``, ``Color.green``, etc., are
*enumeration members* (or *enum members*).
- The enum members have *names* and *values* (the name of
``Color.red`` is ``red``, the value of ``Color.blue`` is
``3``, etc.)
.. note::
Even though we use the ``class`` syntax to create Enums, Enums
are not normal Python classes. See `How are Enums different?`_ for
more details.
Enumeration members have human readable string representations::
>>> print(Color.red)
Color.red
...while their ``repr`` has more information::
>>> print(repr(Color.red))
<Color.red: 1>
The *type* of an enumeration member is the enumeration it belongs to::
>>> type(Color.red)
<aenum 'Color'>
>>> isinstance(Color.green, Color)
True
Enumerations support iteration. In Python 3.x definition order is used; in
Python 2.x the definition order is not available, but class attribute
``_order_`` is supported; otherwise, value order is used if posible,
otherwise alphabetical name order is used::
>>> class Shake(Enum):
... _order_ = 'vanilla chocolate cookies mint' # only needed in 2.x
... vanilla = 7
... chocolate = 4
... cookies = 9
... mint = 3
...
>>> for shake in Shake:
... print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint
The ``_order_`` attribute is always removed, but in 3.x it is also used to
verify that definition order is the same (useful for py2&3 code bases);
however, in the stdlib version it will be ignored and not removed.
.. note::
To maintain compatibility with Python 3.4 and 3.5, use __order__
instead (double leading and trailing underscores).
Enumeration members are hashable, so they can be used in dictionaries and sets::
>>> apples = {}
>>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith'
>>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'}
True
In Python 3 the class syntax has a few extra advancements::
--> class Color(
... Enum,
... settings=(AddValue, MultiValue, NoAlias, Unique),
... init='field_name1 field_name2 ...',
... start=7,
... )
...
``start`` is used to specify the starting value for the first member::
--> class Count(Enum, start=11):
... eleven
... twelve
...
--> Count.twelve.value == 12
True
``init`` specifies the attribute names to store creation values to::
--> class Planet(Enum, init='mass radius'):
... MERCURY = (3.303e+23, 2.4397e6)
... EARTH = (5.976e+24, 6.37814e6)
...
--> Planet.EARTH.value
(5.976e+24, 6378140.0)
--> Planet.EARTH.radius
2.4397e6
The various settings enable special behavior:
- ``AddValue`` calls a user supplied ``_generate_next_value_`` to provide
the initial value
- ``MultiValue`` allows multiple values per member instead of the usual 1
- ``NoAlias`` allows different members to have the same value
- ``Unique`` disallows different members to have the same value
.. note::
To use these features in Python 2 use the _sundered_ versions of
the names in the class body: ``_start_``, ``_init_``, ``_settings_``.
Programmatic access to enumeration members and their attributes
---------------------------------------------------------------
Sometimes it's useful to access members in enumerations programmatically (i.e.
situations where ``Color.red`` won't do because the exact color is not known
at program-writing time). ``Enum`` allows such access::
>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>
If you want to access enum members by *name*, use item access::
>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>
If have an enum member and need its ``name`` or ``value``::
>>> member = Color.red
>>> member.name
'red'
>>> member.value
1
Duplicating enum members and values
-----------------------------------
Having two enum members (or any other attribute) with the same name is invalid;
in Python 3.x this would raise an error, but in Python 2.x the second member
simply overwrites the first::
# python 2.x
--> class Shape(Enum):
... square = 2
... square = 3
...
--> Shape.square
<Shape.square: 3>
# python 3.x
--> class Shape(Enum):
... square = 2
... square = 3
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'square'
However, two enum members are allowed to have the same value. Given two members
A and B with the same value (and A defined first), B is an alias to A. By-value
lookup of the value of A and B will return A. By-name lookup of B will also
return A::
>>> class Shape(Enum):
... _order_ = 'square diamond circle' # needed in 2.x
... square = 2
... diamond = 1
... circle = 3
... alias_for_square = 2
...
>>> Shape.square
<Shape.square: 2>
>>> Shape.alias_for_square
<Shape.square: 2>
>>> Shape(2)
<Shape.square: 2>
Allowing aliases is not always desirable. ``unique`` can be used to ensure
that none exist in a particular enumeration::
>>> from aenum import unique
>>> @unique
... class Mistake(Enum):
... _order_ = 'one two three' # only needed in 2.x
... one = 1
... two = 2
... three = 3
... four = 3
Traceback (most recent call last):
...
ValueError: duplicate names found in <aenum 'Mistake'>: four -> three
Iterating over the members of an enum does not provide the aliases::
>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]
The special attribute ``__members__`` is a dictionary mapping names to members.
It includes all names defined in the enumeration, including the aliases::
>>> for name, member in sorted(Shape.__members__.items()):
... name, member
...
('alias_for_square', <Shape.square: 2>)
('circle', <Shape.circle: 3>)
('diamond', <Shape.diamond: 1>)
('square', <Shape.square: 2>)
The ``__members__`` attribute can be used for detailed programmatic access to
the enumeration members. For example, finding all the aliases::
>>> [n for n, mbr in Shape.__members__.items() if mbr.name != n]
['alias_for_square']
Comparisons
-----------
Enumeration members are compared by identity::
>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True
Ordered comparisons between enumeration values are *not* supported. Enum
members are not integers (but see `IntEnum`_ below)::
>>> Color.red < Color.blue
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Color() < Color()
.. warning::
In Python 2 *everything* is ordered, even though the ordering may not
make sense. If you want your enumerations to have a sensible ordering
consider using an `OrderedEnum`_.
Equality comparisons are defined though::
>>> Color.blue == Color.red
False
>>> Color.blue != Color.red
True
>>> Color.blue == Color.blue
True
Comparisons against non-enumeration values will always compare not equal
(again, ``IntEnum`` was explicitly designed to behave differently, see
below)::
>>> Color.blue == 2
False
Allowed members and attributes of enumerations
----------------------------------------------
The examples above use integers for enumeration values. Using integers is
short and handy (and provided by default by the `Enum Functional API`_), but not
strictly enforced. In the vast majority of use-cases, one doesn't care what
the actual value of an enumeration is. But if the value *is* important,
enumerations can have arbitrary values.
Enumerations are Python classes, and can have methods and special methods as
usual. If we have this enumeration::
>>> class Mood(Enum):
... funky = 1
... happy = 3
...
... def describe(self):
... # self is the member here
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls here is the enumeration
... return cls.happy
Then::
>>> Mood.favorite_mood()
<Mood.happy: 3>
>>> Mood.happy.describe()
('happy', 3)
>>> str(Mood.funky)
'my custom str! 1'
The rules for what is allowed are as follows: _sunder_ names (starting and
ending with a single underscore) are reserved by enum and cannot be used;
all other attributes defined within an enumeration will become members of this
enumeration, with the exception of *__dunder__* names and descriptors (methods
are also descriptors).
.. note::
If your enumeration defines ``__new__`` and/or ``__init__`` then
whatever value(s) were given to the enum member will be passed into
those methods. See `Planet`_ for an example.
Restricted Enum subclassing
---------------------------
A new `Enum` class must have one base Enum class, up to one concrete
data type, and as many `object`-based mixin classes as needed. The
order of these base classes is::
def EnumName([mix-in, ...,] [data-type,] base-enum):
pass
Also, subclassing an enumeration is allowed only if the enumeration does not define
any members. So this is forbidden::
>>> class MoreColor(Color):
... pink = 17
Traceback (most recent call last):
...
TypeError: <aenum 'MoreColor'> cannot extend <aenum 'Color'>
But this is allowed::
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... happy = 1
... sad = 2
...
Allowing subclassing of enums that define members would lead to a violation of
some important invariants of types and instances. On the other hand, it makes
sense to allow sharing some common behavior between a group of enumerations.
(See `OrderedEnum`_ for an example.)
Pickling
--------
Enumerations can be pickled and unpickled::
>>> from aenum.test import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato, 2))
True
The usual restrictions for pickling apply: picklable enums must be defined in
the top level of a module, since unpickling requires them to be importable
from that module.
.. note::
With pickle protocol version 4 (introduced in Python 3.4) it is possible
to easily pickle enums nested in other classes.
Enum Functional API
-------------------
The ``Enum`` class is callable, providing the following functional API::
>>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal
<aenum 'Animal'>
>>> Animal.ant
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]
The semantics of this API resemble ``namedtuple``. The first argument
of the call to ``Enum`` is the name of the enumeration.
The second argument is the *source* of enumeration member names. It can be a
whitespace-separated string of names, a sequence of names, a sequence of
2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to
values. The last two options enable assigning arbitrary values to
enumerations; the others auto-assign increasing integers starting with 1. A
new class derived from ``Enum`` is returned. In other words, the above
assignment to ``Animal`` is equivalent to::
>>> class Animals(Enum):
... ant = 1
... bee = 2
... cat = 3
... dog = 4
Pickling enums created with the functional API can be tricky as frame stack
implementation details are used to try and figure out which module the
enumeration is being created in (e.g. it will fail if you use a utility
function in separate module, and also may not work on IronPython or Jython).
The solution is to specify the module name explicitly as follows::
>>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)
Derived Enumerations
--------------------
IntEnum
^^^^^^^
A variation of ``Enum`` is provided which is also a subclass of
``int``. Members of an ``IntEnum`` can be compared to integers;
by extension, integer enumerations of different types can also be compared
to each other::
>>> from aenum import IntEnum
>>> class Shape(IntEnum):
... circle = 1
... square = 2
...
>>> class Request(IntEnum):
... post = 1
... get = 2
...
>>> Shape == 1
False
>>> Shape.circle == 1
True
>>> Shape.circle == Request.post
True
However, they still can't be compared to standard ``Enum`` enumerations::
>>> class Shape(IntEnum):
... circle = 1
... square = 2
...
>>> class Color(Enum):
... red = 1
... green = 2
...
>>> Shape.circle == Color.red
False
``IntEnum`` values behave like integers in other ways you'd expect::
>>> int(Shape.circle)
1
>>> ['a', 'b', 'c'][Shape.circle]
'b'
>>> [i for i in range(Shape.square)]
[0, 1]
For the vast majority of code, ``Enum`` is strongly recommended,
since ``IntEnum`` breaks some semantic promises of an enumeration (by
being comparable to integers, and thus by transitivity to other
unrelated enumerations). It should be used only in special cases where
there's no other choice; for example, when integer constants are
replaced with enumerations and backwards compatibility is required with code
that still expects integers.
IntFlag
^^^^^^^
The next variation of ``Enum`` provided, ``IntFlag``, is also based
on ``int``. The difference being ``IntFlag`` members can be combined
using the bitwise operators (&, \|, ^, ~) and the result is still an
``IntFlag`` member. However, as the name implies, ``IntFlag``
members also subclass ``int`` and can be used wherever an ``int`` is
used. Any operation on an ``IntFlag`` member besides the bit-wise
operations will lose the ``IntFlag`` membership.
Sample ``IntFlag`` class::
>>> from aenum import IntFlag
>>> class Perm(IntFlag):
... _order_ = 'R W X'
... R = 4
... W = 2
... X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True
It is also possible to name the combinations::
>>> class Perm(IntFlag):
... _order_ = 'R W X'
... R = 4
... W = 2
... X = 1
... RWX = 7
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
Another important difference between ``IntFlag`` and ``Enum`` is that
if no flags are set (the value is 0), its boolean evaluation is ``False``::
>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False
Because ``IntFlag`` members are also subclasses of ``int`` they can
be combined with them::
>>> Perm.X | 4
<Perm.R|X: 5>
If the result is not a ``Flag`` then, depending on the ``_boundary_`` setting,
an exception is raised (``STRICT``), the extra bits are lost (``CONFORM``), or
it reverts to an int (``EJECT``):
>>> from aenum import STRICT, CONFORM, EJECT
>>> Perm._boundary_ = STRICT
>>> Perm.X | 8
Traceback (most recent call last):
...
ValueError: Perm: invalid value: 9
given 0b0 1001
allowed 0b0 0111
>>> Perm._boundary_ = EJECT
>>> Perm.X | 8
9
>>> Perm._boundary_ = CONFORM
>>> Perm.X | 8
<Perm.X: 1>
Flag
^^^^
The last variation is ``Flag``. Like ``IntFlag``, ``Flag``
members can be combined using the bitwise operators (&, \|, ^, ~). Unlike
``IntFlag``, they cannot be combined with, nor compared against, any
other ``Flag`` enumeration, nor ``int``. While it is possible to
specify the values directly it is recommended to use ``auto`` as the
value and let ``Flag`` select an appropriate value.
Like ``IntFlag``, if a combination of ``Flag`` members results in no
flags being set, the boolean evaluation is ``False``::
>>> from aenum import Flag, auto
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False
Individual flags should have values that are powers of two (1, 2, 4, 8, ...),
while combinations of flags won't::
--> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
... WHITE = RED | BLUE | GREEN
...
--> Color.WHITE
<Color.WHITE: 7>
Giving a name to the "no flags set" condition does not change its boolean
value::
>>> class Color(Flag):
... BLACK = 0
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False
Flags can be iterated over to retrieve the individual truthy flags in the value::
>>> class Color(Flag):
... _order_ = 'BLACK RED BLUE GREEN WHITE'
... BLACK = 0
... RED = auto()
... BLUE = auto()
... GREEN = auto()
... WHITE = RED | BLUE | GREEN
...
>>> list(Color.GREEN)
[<Color.GREEN: 4>]
>>> list(Color.WHITE)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 4>]
.. note::
For the majority of new code, ``Enum`` and ``Flag`` are strongly
recommended, since ``IntEnum`` and ``IntFlag`` break some
semantic promises of an enumeration (by being comparable to integers, and
thus by transitivity to other unrelated enumerations). ``IntEnum``
and ``IntFlag`` should be used only in cases where ``Enum`` and
``Flag`` will not do; for example, when integer constants are replaced
with enumerations, or for interoperability with other systems.
Others
^^^^^^
While ``IntEnum`` is part of the ``aenum`` module, it would be very
simple to implement independently::
class MyIntEnum(int, Enum):
pass
This demonstrates how similar derived enumerations can be defined; for example
a ``MyStrEnum`` that mixes in ``str`` instead of ``int``.
Some rules:
1. When subclassing ``Enum``, mix-in types must appear before
``Enum`` itself in the sequence of bases, as in the ``MyIntEnum``
example above.
2. While ``Enum`` can have members of any type, once you mix in an
additional type, all the members must have values of that type or be
convertible into that type. This restriction does not apply to mix-ins
which only add methods and don't specify another data type.
3. When another data type is mixed in, the ``value`` attribute is *not the
same* as the enum member itself, although it is equivalant and will compare
equal.
4. %-style formatting: ``%s`` and ``%r`` call ``Enum``'s ``__str__`` and
``__repr__`` respectively; other codes (such as ``%i`` or ``%h`` for
MyIntEnum) treat the enum member as its mixed-in type.
5. ``str.__format__`` (or ``format``) will use the mixed-in
type's ``__format__``. If the ``Enum``'s ``str`` or ``repr`` is desired
use the ``!s`` or ``!r`` ``str`` format codes.
.. note::
If you override the ``__str__`` method, then it will be used to provide the
string portion of the ``format()`` call.
.. note::
Prior to Python 3.4 there is a bug in ``str``'s %-formatting: ``int``
subclasses are printed as strings and not numbers when the ``%d``, ``%i``,
or ``%u`` codes are used.
Extra Goodies
-------------
aenum supports a few extra techniques not found in the stdlib version.
enum
^^^^
If you have several items to initialize your ``Enum`` members with and
would like to use keyword arguments, the ``enum`` helper is for you::
>>> from aenum import enum
>>> class Presidents(Enum):
... Washington = enum('George Washington', circa=1776, death=1797)
... Jackson = enum('Andrew Jackson', circa=1830, death=1837)
... Lincoln = enum('Abraham Lincoln', circa=1860, death=1865)
...
>>> Presidents.Lincoln
<Presidents.Lincoln: enum('Abraham Lincoln', circa=1860, death=1865)>
extend_enum
^^^^^^^^^^^
For those rare cases when you need to create your ``Enum`` in pieces, you
can use ``extend_enum`` to add new members after the initial creation
(the new member is returned)::
>>> from aenum import extend_enum
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
...
>>> list(Color)
[<Color.red: 1>, <Color.green: 2>, <Color.blue: 3>]
>>> extend_enum(Color, 'opacity', 4)
<Color.opacity: 4>
>>> list(Color)
[<Color.red: 1>, <Color.green: 2>, <Color.blue: 3>, <Color.opacity: 4>]
>>> Color.opacity in Color
True
>>> Color.opacity.name == 'opacity'
True
>>> Color.opacity.value == 4
True
>>> Color(4)
<Color.opacity: 4>
>>> Color['opacity']
<Color.opacity: 4>
--> Color.__members__
OrderedDict([
('red', <Color.red: 1>),
('green', <Color.green: 2>),
('blue', <Color.blue: 3>),
('opacity', <Color.opacity: 4>)
])
constant
^^^^^^^^
If you need to have some constant value in your ``Enum`` that isn't a member,
use ``constant``::
>>> from aenum import constant
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... EARTH = (5.976e+24, 6.37814e6)
... JUPITER = (1.9e+27, 7.1492e7)
... URANUS = (8.686e+25, 2.5559e7)
... G = constant(6.67300E-11)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... return self.G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
>>> Planet.G
6.673e-11
>>> Planet.G = 9
Traceback (most recent call last):
...
AttributeError: Planet: cannot rebind constant 'G'
skip
^^^^
If you need a standard attribute that is not converted into an ``Enum``
member, use ``skip``::
>>> from aenum import skip
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
... opacity = skip(0.45)
...
>>> Color.opacity
0.45
>>> Color.opacity = 0.77
>>> Color.opacity
0.77
start
^^^^^
``start`` can be used to turn on auto-numbering (useful for when you don't
care which numbers are assigned as long as they are consistent and in order)
The Python 3 version can look like this::
>>> class Color(Enum, start=1): # doctest: +SKIP
... red, green, blue
...
>>> Color.blue
<Color.blue: 3>
This can also be done in Python 2, albeit not as elegantly (this also works in
Python 3)::
>>> class Color(Enum): # doctest: +SKIP
... _start_ = 1
... red = auto()
... green = auto()
... blue = auto()
...
>>> Color.blue
<Color.blue: 3>
init
^^^^
If you need an ``__init__`` method that does nothing besides save its
arguments, ``init`` is for you::
>>> class Planet(Enum, init='mass radius'): # doctest: +SKIP
... MERCURY = (3.303e+23, 2.4397e6)
... EARTH = (5.976e+24, 6.37814e6)
... JUPITER = (1.9e+27, 7.1492e7)
... URANUS = (8.686e+25, 2.5559e7)
... G = constant(6.67300E-11)
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... return self.G * self.mass / (self.radius * self.radius)
...
>>> Planet.JUPITER.value
(1.9e+27, 71492000.0)
>>> Planet.JUPITER.mass
1.9e+27
.. note::
Just as with ``start`` above, in Python 2 you must put the keyword as a
_sunder_ in the class body -- ``_init_ = 'mass radius'``.
init and missing values
^^^^^^^^^^^^^^^^^^^^^^^
If ``_init_`` calls for values that are not supplied, ``_generate_next_value_``
will be called in an effort to generate them. Here is an example in Python 2::
>>> from aenum import Enum
>>> class SelectionEnum(Enum):
... _init_ = 'db user'
... def __new__(cls, *args, **kwds):
... count = len(cls.__members__)
... obj = object.__new__(cls)
... obj._count = count
... obj._value_ = args
... return obj
... @staticmethod
... def _generate_next_value_(name, start, count, values, *args, **kwds):
... return (name, ) + args
...
>>> class NotificationType(SelectionEnum):
... # usually, name is the same as db
... # but not for blanks
... blank = '', ''
... C = 'Catalog'
... S = 'Sheet'
... B = 'Both'
...
>>> NotificationType.blank
<NotificationType.blank: ('', '')>
>>> NotificationType.B
<NotificationType.B: ('B', 'Both')>
>>> NotificationType.B.db
'B'
>>> NotificationType.B.user
'Both'
combining Flag with other data types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Flag does support being combined with other data types. To support this you
need to provide a ``_create_pseudo_member_values_`` method which will be called
with the members in a composite flag. You may also need to provide a custom
``__new__`` method::
>>> class AnsiFlag(str, Flag):
... def __new__(cls, value, code):
... str_value = '\x1b[%sm' % code
... obj = str.__new__(cls, str_value)
... obj._value_ = value
... obj.code = code
... return obj
... @classmethod
... def _create_pseudo_member_values_(cls, members, *values):
... code = ';'.join(m.code for m in members)
... return values + (code, )
... _order_ = 'FG_Red FG_Green BG_Magenta BG_White'
... FG_Red = '31' # ESC [ 31 m # red
... FG_Green = '32' # ESC [ 32 m # green
... BG_Magenta = '45' # ESC [ 35 m # magenta
... BG_White = '47' # ESC [ 37 m # white
...
>>> color = AnsiFlag.BG_White | AnsiFlag.FG_Red
>>> repr(color)
'<AnsiFlag.FG_Red|BG_White: 9>'
>>> str.__repr__(color)
"'\\x1b[31;47m'"
.. note::
If you do not provide your own ``_create_pseudo_member_values_`` the flags
may still combine, but may be missing functionality.
Decorators
----------
unique
^^^^^^
A ``class`` decorator specifically for enumerations. It searches an
enumeration's ``__members__`` gathering any aliases it finds; if any are
found ``ValueError`` is raised with the details::
>>> @unique
... class NoDupes(Enum):
... first = 'one'
... second = 'two'
... third = 'two'
Traceback (most recent call last):
...
ValueError: duplicate names found in <aenum 'NoDupes'>: third -> second
Interesting examples
--------------------
While ``Enum`` and ``IntEnum`` are expected to cover the majority of
use-cases, they cannot cover them all. Here are recipes for some different
types of enumerations that can be used directly (the first three are included
in the module), or as examples for creating one's own.
AutoNumber
^^^^^^^^^^
Avoids having to specify the value for each enumeration member::
>>> class AutoNumber(Enum):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... _order_ = "red green blue" # only needed in 2.x
... red = ()
... green = ()
... blue = ()
...
>>> Color.green.value == 2
True
.. note::
The `__new__` method, if defined, is used during creation of the Enum
members; it is then replaced by Enum's `__new__` which is used after
class creation for lookup of existing members. Due to the way Enums are
supposed to behave, there is no way to customize Enum's `__new__` without
modifying the class after it is created.
UniqueEnum
^^^^^^^^^^
Raises an error if a duplicate member name is found instead of creating an
alias::
>>> class UniqueEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in UniqueEnum: %r --> %r"
... % (a, e))
...
>>> class Color(UniqueEnum):
... _order_ = 'red green blue'
... red = 1
... green = 2
... blue = 3
... grene = 2
Traceback (most recent call last):
...
ValueError: aliases not allowed in UniqueEnum: 'grene' --> 'green'
OrderedEnum
^^^^^^^^^^^
An ordered enumeration that is not based on ``IntEnum`` and so maintains
the normal ``Enum`` invariants (such as not being comparable to other
enumerations)::
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self._value_ >= other._value_
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self._value_ > other._value_
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self._value_ <= other._value_
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self._value_ < other._value_
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... __ordered__ = 'A B C D F'
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
Planet
^^^^^^
If ``__new__`` or ``__init__`` is defined the value of the enum member
will be passed to those methods::
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... VENUS = (4.869e+24, 6.0518e6)
... EARTH = (5.976e+24, 6.37814e6)
... MARS = (6.421e+23, 3.3972e6)
... JUPITER = (1.9e+27, 7.1492e7)
... SATURN = (5.688e+26, 6.0268e7)
... URANUS = (8.686e+25, 2.5559e7)
... NEPTUNE = (1.024e+26, 2.4746e7)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... G = 6.67300E-11
... return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
How are Enums different?
------------------------
Enums have a custom metaclass that affects many aspects of both derived Enum
classes and their instances (members).
Enum Classes
^^^^^^^^^^^^
The ``EnumMeta`` metaclass is responsible for providing the
``__contains__``, ``__dir__``, ``__iter__`` and other methods that
allow one to do things with an ``Enum`` class that fail on a typical
class, such as ``list(Color)`` or ``some_var in Color``. ``EnumMeta`` is
responsible for ensuring that various other methods on the final ``Enum``
class are correct (such as ``__new__``, ``__getnewargs__``,
``__str__`` and ``__repr__``).
.. note::
``__dir__`` is not changed in the Python 2 line as it messes up some
of the decorators included in the stdlib.
Enum Members (aka instances)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The most interesting thing about Enum members is that they are singletons.
``EnumMeta`` creates them all while it is creating the ``Enum``
class itself, and then puts a custom ``__new__`` in place to ensure
that no new ones are ever instantiated by returning only the existing
member instances.
Finer Points
^^^^^^^^^^^^
``Enum`` members are instances of an ``Enum`` class, but are not
accessible as `EnumClass.member1.member2`.
(changed in version 1.1.1 to be accessible)
(changed in version 2.2.4 to be inaccessible)::
>>> class FieldTypes(Enum):
... name = 1
... value = 2
... size = 3
...
>>> FieldTypes.size.value
3
>>> FieldTypes.size
<FieldTypes.size: 3>
>>> FieldTypes.value.size
Traceback (most recent call last):
...
AttributeError: <aenum 'FieldTypes'> member has no attribute 'size'
The ``__members__`` attribute is only available on the class.
``__members__`` is always an ``OrderedDict``, with the order being the
definition order in Python 3.x or the order in ``_order_`` in Python 2.7;
if no ``_order_`` was specified in Python 2.7 then the order of
``__members__`` is either increasing value or alphabetically by name.
If you give your ``Enum`` subclass extra methods, like the `Planet`_
class above, those methods will show up in a `dir` of the member,
but not of the class (in Python 3.x)::
--> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS',
'VENUS', '__class__', '__doc__', '__members__', '__module__']
--> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
A ``__new__`` method will only be used for the creation of the
``Enum`` members -- after that it is replaced. This means if you wish to
change how ``Enum`` members are looked up you either have to write a
helper function or a ``classmethod``.
.. note::
If you create your own ``__new__`` you should set the ``_value_`` in it;
if you do not, aenum will try to, but will raise a ``TypeError`` if it
cannot.
If the stdlib ``enum`` is available (Python 3.4+ and it hasn't been shadowed
by, for example, ``enum34``) then aenum will be a subclass of it.
To use the ``AddValue``, ``MultiValue``, ``NoAlias``, and ``Unique`` flags
in Py2 or Py2/Py3 codebases, use ``_settings_ = ...`` in the class body.
To use ``init`` in Py2 or Py2/Py3 codebases use ``_init_`` in the class body.
To use ``start`` in Py2 or Py2/Py3 codebases use ``_start_`` in the class body.
When creating class bodies dynamically, put any variables you need to use into
``_ignore_``::
>>> from datetime import timedelta
>>> from aenum import NoAlias
>>> class Period(timedelta, Enum):
... '''
... different lengths of time
... '''
... _init_ = 'value period'
... _settings_ = NoAlias
... _ignore_ = 'Period i'
... Period = vars()
... for i in range(31):
... Period['day_%d' % i] = i, 'day'
... for i in range(15):
... Period['week_%d' % i] = i*7, 'week'
...
>>> hasattr(Period, '_ignore_')
False
>>> hasattr(Period, 'Period')
False
>>> hasattr(Period, 'i')
False
The name listed in ``_ignore_``, as well as ``_ignore_`` itself, will not be
present in the final enumeration as neither attributes nor members.
.. note::
except for __dunder__ attributes/methods, all _sunder_ attributes must
be before any thing else in the class body
.. note::
all _sunder_ attributes that affect member creation are only looked up in
the last ``Enum`` class listed in the class header
Creating NamedTuples
--------------------
Simple
^^^^^^
The most common way to create a new NamedTuple will be via the functional API::
>>> from aenum import NamedTuple
>>> Book = NamedTuple('Book', 'title author genre', module=__name__)
This creates a ``NamedTuple`` called ``Book`` that will always contain three
items, each of which is also addressable as ``title``, ``author``, or ``genre``.
``Book`` instances can be created using positional or keyword argements or a
mixture of the two::
>>> b1 = Book('Lord of the Rings', 'J.R.R. Tolkien', 'fantasy')
>>> b2 = Book(title='Jhereg', author='Steven Brust', genre='fantasy')
>>> b3 = Book('Empire', 'Orson Scott Card', genre='scifi')
If too few or too many arguments are used a ``TypeError`` will be raised::
>>> b4 = Book('Hidden Empire')
Traceback (most recent call last):
...
TypeError: values not provided for field(s): author, genre
>>> b5 = Book(genre='business')
Traceback (most recent call last):
...
TypeError: values not provided for field(s): title, author
As a ``class`` the above ``Book`` ``NamedTuple`` would look like::
>>> class Book(NamedTuple):
... title = 0
... author = 1
... genre = 2
...
For compatibility with the stdlib ``namedtuple``, NamedTuple also has the
``_asdict``, ``_make``, and ``_replace`` methods, and the ``_fields``
attribute, which all function similarly::
>>> class Point(NamedTuple):
... x = 0, 'horizontal coordinate', 1
... y = 1, 'vertical coordinate', -1
...
>>> class Color(NamedTuple):
... r = 0, 'red component', 11
... g = 1, 'green component', 29
... b = 2, 'blue component', 37
...
>>> Pixel = NamedTuple('Pixel', Point+Color, module=__name__)
>>> pixel = Pixel(99, -101, 255, 128, 0)
>>> pixel._asdict()
OrderedDict([('x', 99), ('y', -101), ('r', 255), ('g', 128), ('b', 0)])
>>> Point._make((4, 5))
Point(x=4, y=5)
>>> purple = Color(127, 0, 127)
>>> mid_gray = purple._replace(g=127)
>>> mid_gray
Color(r=127, g=127, b=127)
>>> pixel._fields
['x', 'y', 'r', 'g', 'b']
>>> Pixel._fields
['x', 'y', 'r', 'g', 'b']
Advanced
^^^^^^^^
The simple method of creating ``NamedTuples`` requires always specifying all
possible arguments when creating instances; failure to do so will raise
exceptions::
>>> class Point(NamedTuple):
... x = 0
... y = 1
...
>>> Point()
Traceback (most recent call last):
...
TypeError: values not provided for field(s): x, y
>>> Point(1)
Traceback (most recent call last):
...
TypeError: values not provided for field(s): y
>>> Point(y=2)
Traceback (most recent call last):
...
TypeError: values not provided for field(s): x
However, it is possible to specify both docstrings and default values when
creating a ``NamedTuple`` using the class method::
>>> class Point(NamedTuple):
... x = 0, 'horizontal coordinate', 0
... y = 1, 'vertical coordinate', 0
...
>>> Point()
Point(x=0, y=0)
>>> Point(1)
Point(x=1, y=0)
>>> Point(y=2)
Point(x=0, y=2)
It is also possible to create ``NamedTuples`` that only have named attributes
for certain fields; any fields without names can still be accessed by index::
>>> class Person(NamedTuple):
... fullname = 2
... phone = 5
...
>>> p = Person('Ethan', 'Furman', 'Ethan Furman',
... 'ethan at stoneleaf dot us',
... 'ethan.furman', '999.555.1212')
>>> p
Person('Ethan', 'Furman', 'Ethan Furman', 'ethan at stoneleaf dot us',
'ethan.furman', '999.555.1212')
>>> p.fullname
'Ethan Furman'
>>> p.phone
'999.555.1212'
>>> p[0]
'Ethan'
In the above example the last named field was also the last field possible; in
those cases where you don't need to have the last possible field named, you can
provide a ``_size_`` of ``TupleSize.minimum`` to declare that more fields are
okay::
>>> from aenum import TupleSize
>>> class Person(NamedTuple):
... _size_ = TupleSize.minimum
... first = 0
... last = 1
...
or, optionally if using Python 3::
>>> class Person(NamedTuple, size=TupleSize.minimum): # doctest: +SKIP
... first = 0
... last = 1
and in use::
>>> Person('Ethan', 'Furman')
Person(first='Ethan', last='Furman')
>>> Person('Ethan', 'Furman', 'ethan.furman')
Person('Ethan', 'Furman', 'ethan.furman')
>>> Person('Ethan', 'Furman', 'ethan.furman', 'yay Python!')
Person('Ethan', 'Furman', 'ethan.furman', 'yay Python!')
>>> Person('Ethan')
Traceback (most recent call last):
...
TypeError: values not provided for field(s): last
Also, for those cases where even named fields may not be present, you can
specify ``TupleSize.variable``::
>>> class Person(NamedTuple):
... _size_ = TupleSize.variable
... first = 0
... last = 1
...
>>> Person('Ethan')
Person('Ethan')
>>> Person(last='Furman')
Traceback (most recent call last):
...
TypeError: values not provided for field(s): first
Creating new ``NamedTuples`` from existing ``NamedTuples`` is simple::
>>> Point = NamedTuple('Point', 'x y')
>>> Color = NamedTuple('Color', 'r g b')
>>> Pixel = NamedTuple('Pixel', Point+Color, module=__name__)
>>> Pixel
<NamedTuple 'Pixel'>
The existing fields in the bases classes are renumbered to fit the new class,
but keep their doc strings and default values. If you use standard
subclassing::
>>> Point = NamedTuple('Point', 'x y')
>>> class Pixel(Point):
... r = 2, 'red component', 11
... g = 3, 'green component', 29
... b = 4, 'blue component', 37
...
>>> Pixel.__fields__
['x', 'y', 'r', 'g', 'b']
You must manage the numbering yourself.
Creating NamedConstants
-----------------------
A ``NamedConstant`` class is created much like an ``Enum``::
>>> from aenum import NamedConstant
>>> class Konstant(NamedConstant):
... PI = 3.14159
... TAU = 2 * PI
>>> Konstant.PI
<Konstant.PI: 3.14159>
>> print(Konstant.PI)
3.14159
>>> Konstant.PI = 'apple'
Traceback (most recent call last):
...
AttributeError: cannot rebind constant <Konstant.PI>
>>> del Konstant.PI
Traceback (most recent call last):
...
AttributeError: cannot delete constant <Konstant.PI>
This source diff could not be displayed because it is too large. You can view the blob instead.
from . import EnumMeta, Enum, IntEnum, Flag, IntFlag, StrEnum, UniqueEnum, AutoEnum, AddValueEnum
from . import NamedTuple, TupleSize, MagicValue, AddValue, NoAlias, Unique, MultiValue
from . import AutoNumberEnum,MultiValueEnum, OrderedEnum, unique, skip, extend_enum, auto
from . import StdlibEnumMeta, StdlibEnum, StdlibIntEnum, StdlibFlag, StdlibIntFlag, StdlibStrEnum
from . import pyver, PY3_3, PY3_4, PY3_5, PY3_6, PY3_11
from . import add_stdlib_integration, remove_stdlib_integration
from collections import OrderedDict
from datetime import timedelta
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
from unittest import TestCase, main
import os
import sys
import tempfile
import textwrap
import unittest
try:
import pyparsing
except (ImportError, SyntaxError):
pyparsing = None
try:
RecursionError
except NameError:
# python3.4
RecursionError = RuntimeError
class TestEnumV3(TestCase):
def setUp(self):
class Season(Enum):
SPRING = 1
SUMMER = 2
AUTUMN = 3
WINTER = 4
self.Season = Season
class Konstants(float, Enum):
E = 2.7182818
PI = 3.1415926
TAU = 2 * PI
self.Konstants = Konstants
class Grades(IntEnum):
A = 5
B = 4
C = 3
D = 2
F = 0
self.Grades = Grades
class Directional(str, Enum):
EAST = 'east'
WEST = 'west'
NORTH = 'north'
SOUTH = 'south'
self.Directional = Directional
from datetime import date
class Holiday(date, Enum):
NEW_YEAR = 2013, 1, 1
IDES_OF_MARCH = 2013, 3, 15
self.Holiday = Holiday
@unittest.skipUnless(StdlibEnumMeta, 'Stdlib enum not available')
def test_stdlib_inheritence(self):
# 3.4
self.assertTrue(issubclass(self.Season, StdlibEnum))
self.assertTrue(isinstance(self.Season.SPRING, StdlibEnum))
#
if pyver >= PY3_6:
class AFlag(Flag):
one = 1
self.assertTrue(issubclass(AFlag, StdlibEnum))
self.assertTrue(isinstance(AFlag.one, StdlibEnum))
self.assertTrue(issubclass(AFlag, StdlibFlag))
self.assertTrue(isinstance(AFlag.one, StdlibFlag))
#
class AnIntFlag(IntFlag):
one = 1
self.assertTrue(issubclass(AnIntFlag, StdlibEnum))
self.assertTrue(isinstance(AnIntFlag.one, StdlibEnum))
self.assertTrue(issubclass(AnIntFlag, StdlibFlag))
self.assertTrue(isinstance(AnIntFlag.one, StdlibFlag))
self.assertTrue(issubclass(AnIntFlag, StdlibIntFlag))
self.assertTrue(isinstance(AnIntFlag.one, StdlibIntFlag))
#
if pyver >= PY3_11:
class AStrEnum(StrFlag):
one = '1'
self.assertTrue(issubclass(AStrEnum, StdlibEnum))
self.assertTrue(isinstance(AStrEnum.one, StdlibEnum))
self.assertTrue(issubclass(AStrEnum, StdlibStrEnum))
self.assertTrue(isinstance(AStrEnum.one, StdlibStrEnum))
@unittest.skipUnless(StdlibEnumMeta, 'Stdlib enum not available')
def test_stdlib_bad_getattribute(self):
class BadEnumType(StdlibEnumMeta):
def __getattribute__(cls, name):
obj = super().__getattribute__(name)
if isinstance(obj, cls):
obj.deprecate()
return obj
with self.assertRaisesRegex(RecursionError, 'endless recursion'):
class BaseEnum(StdlibEnum):
pass
class BadEnum(BaseEnum, metaclass=BadEnumType):
FOO = 'bar'
try:
remove_stdlib_integration()
class OkayEnum(StdlibEnum, metaclass=BadEnumType):
FOO = 'bar'
finally:
add_stdlib_integration()
@unittest.skipUnless(pyver >= PY3_5, '__qualname__ requires python 3.5 or greater')
def test_pickle_enum_function_with_qualname(self):
Theory = Enum('Theory', 'rule law supposition', qualname='spanish_inquisition')
globals()['spanish_inquisition'] = Theory
test_pickle_dump_load(self.assertTrue, Theory.rule)
test_pickle_dump_load(self.assertTrue, Theory)
def test_auto_init(self):
class Planet(Enum, init='mass radius'):
MERCURY = (3.303e+23, 2.4397e6)
VENUS = (4.869e+24, 6.0518e6)
EARTH = (5.976e+24, 6.37814e6)
MARS = (6.421e+23, 3.3972e6)
JUPITER = (1.9e+27, 7.1492e7)
SATURN = (5.688e+26, 6.0268e7)
URANUS = (8.686e+25, 2.5559e7)
NEPTUNE = (1.024e+26, 2.4746e7)
@property
def surface_gravity(self):
# universal gravitational constant (m3 kg-1 s-2)
G = 6.67300E-11
return G * self.mass / (self.radius * self.radius)
self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80)
self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
def test_auto_init_with_value(self):
class Color(Enum, init='value, rgb'):
RED = 1, (1, 0, 0)
BLUE = 2, (0, 1, 0)
GREEN = 3, (0, 0, 1)
self.assertEqual(Color.RED.value, 1)
self.assertEqual(Color.BLUE.value, 2)
self.assertEqual(Color.GREEN.value, 3)
self.assertEqual(Color.RED.rgb, (1, 0, 0))
self.assertEqual(Color.BLUE.rgb, (0, 1, 0))
self.assertEqual(Color.GREEN.rgb, (0, 0, 1))
def test_auto_turns_off(self):
with self.assertRaises(NameError):
class Color(Enum, settings=MagicValue):
red
green
blue
def hello(self):
print('Hello! My serial is %s.' % self.value)
rose
with self.assertRaises(NameError):
class Color(Enum, settings=MagicValue):
red
green
blue
def __init__(self, *args):
pass
rose
def test_magic(self):
class Color(Enum, settings=MagicValue):
red, green, blue
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
self.assertEqual(Color.red.value, 1)
def test_ignore_not_overridden(self):
with self.assertRaisesRegex(TypeError, 'object is not callable'):
class Color(Flag):
_ignore_ = 'irrelevent'
_settings_ = MagicValue
@property
def shade(self):
print('I am light', self.name.lower())
def test_magic_start(self):
class Color(Enum, settings=MagicValue, start=0):
red, green, blue
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
self.assertEqual(Color.red.value, 0)
def test_dir_on_class(self):
Season = self.Season
self.assertEqual(
set(dir(Season)),
set(['__class__', '__doc__', '__members__', '__module__',
'SPRING', 'SUMMER', 'AUTUMN', 'WINTER',
'__init_subclass__', '__name__', '__getitem__', '__len__',
'__contains__', '__iter__', '__qualname__',
]))
def test_dir_on_item(self):
Season = self.Season
self.assertEqual(
set(dir(Season.WINTER)),
set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value', 'values']),
)
def test_dir_with_added_behavior(self):
class Test(Enum):
this = 'that'
these = 'those'
def wowser(self):
return ("Wowser! I'm %s!" % self.name)
self.assertEqual(
set(dir(Test)),
set([
'__class__', '__doc__', '__members__', '__module__', 'this', 'these',
'__init_subclass__', '__name__', '__getitem__', '__len__',
'__contains__', '__iter__', '__qualname__',
]))
self.assertEqual(
set(dir(Test.this)),
set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value', 'values', 'wowser']),
)
def test_dir_on_sub_with_behavior_on_super(self):
# see issue22506
class SuperEnum(Enum):
def invisible(self):
return "did you see me?"
class SubEnum(SuperEnum):
sample = 5
self.assertEqual(
set(dir(SubEnum.sample)),
set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value', 'values', 'invisible']),
)
def test_members_are_always_ordered(self):
class AlwaysOrdered(Enum):
first = 1
second = 2
third = 3
self.assertTrue(type(AlwaysOrdered.__members__) is OrderedDict)
def test_comparisons(self):
def bad_compare():
Season.SPRING > 4
Season = self.Season
self.assertNotEqual(Season.SPRING, 1)
self.assertRaises(TypeError, bad_compare)
class Part(Enum):
SPRING = 1
CLIP = 2
BARREL = 3
self.assertNotEqual(Season.SPRING, Part.SPRING)
def bad_compare():
Season.SPRING < Part.CLIP
self.assertRaises(TypeError, bad_compare)
def test_duplicate_name(self):
with self.assertRaises(TypeError):
class Color1(Enum):
red = 1
green = 2
blue = 3
red = 4
with self.assertRaises(TypeError):
class Color2(Enum):
red = 1
green = 2
blue = 3
def red(self):
return 'red'
with self.assertRaises(TypeError):
class Color3(Enum):
@property
def red(self):
return 'redder'
red = 1
green = 2
blue = 3
def test_duplicate_value_with_unique(self):
with self.assertRaises(ValueError):
class Color(Enum, settings=Unique):
red = 1
green = 2
blue = 3
rojo = 1
def test_duplicate_value_with_noalias(self):
class Color(Enum, settings=NoAlias):
red = 1
green = 2
blue = 3
rojo = 1
self.assertFalse(Color.red is Color.rojo)
self.assertEqual(Color.red.value, 1)
self.assertEqual(Color.rojo.value, 1)
self.assertEqual(len(Color), 4)
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue, Color.rojo])
def test_noalias_value_lookup(self):
class Color(Enum, settings=NoAlias):
red = 1
green = 2
blue = 3
rojo = 1
self.assertRaises(TypeError, Color, 2)
def test_multivalue(self):
class Color(Enum, settings=MultiValue):
red = 1, 'red'
green = 2, 'green'
blue = 3, 'blue'
self.assertEqual(Color.red.value, 1)
self.assertIs(Color('green'), Color.green)
self.assertEqual(Color.blue.values, (3, 'blue'))
def test_multivalue_with_duplicate_values(self):
with self.assertRaises(ValueError):
class Color(Enum, settings=MultiValue):
red = 1, 'red'
green = 2, 'green'
blue = 3, 'blue', 'red'
def test_multivalue_with_duplicate_values_and_noalias(self):
with self.assertRaises(TypeError):
class Color(Enum, settings=(MultiValue, NoAlias)):
red = 1, 'red'
green = 2, 'green'
blue = 3, 'blue', 'red'
def test_multivalue_and_auto(self):
with self.assertRaisesRegex(TypeError, r'MultiValue and MagicValue are mutually exclusive'):
class Color(Enum, settings=(MultiValue, MagicValue)):
red
green = 3, 'green'
blue
def test_autonumber_and_init(self):
class Field(IntEnum, settings=AddValue, init='__doc__'):
TYPE = "Char, Date, Logical, etc."
START = "Field offset in record"
self.assertEqual(Field.TYPE, 1)
self.assertEqual(Field.START, 2)
self.assertEqual(Field.TYPE.__doc__, 'Char, Date, Logical, etc.')
self.assertEqual(Field.START.__doc__, 'Field offset in record')
self.assertFalse(hasattr(Field, '_order_'))
def test_autovalue_and_init(self):
class Field(IntEnum, init='value __doc__'):
TYPE = "Char, Date, Logical, etc."
START = "Field offset in record"
self.assertEqual(Field.TYPE, 1)
self.assertEqual(Field.START.__doc__, 'Field offset in record')
def test_autonumber_and_start(self):
class Field(IntEnum, init='__doc__', settings=AddValue, start=0):
TYPE = "Char, Date, Logical, etc."
START = "Field offset in record"
self.assertEqual(Field.TYPE, 0)
self.assertEqual(Field.START, 1)
self.assertEqual(Field.TYPE.__doc__, 'Char, Date, Logical, etc.')
self.assertEqual(Field.START.__doc__, 'Field offset in record')
def test_autonumber_and_init_and_some_values(self):
class Field(IntEnum, init='value __doc__'):
TYPE = "Char, Date, Logical, etc."
START = "Field offset in record"
BLAH = 5, "test blah"
BELCH = 'test belch'
self.assertEqual(Field.TYPE, 1)
self.assertEqual(Field.START, 2)
self.assertEqual(Field.BLAH, 5)
self.assertEqual(Field.BELCH, 6)
self.assertEqual(Field.TYPE.__doc__, 'Char, Date, Logical, etc.')
self.assertEqual(Field.START.__doc__, 'Field offset in record')
self.assertEqual(Field.BLAH.__doc__, 'test blah')
self.assertEqual(Field.BELCH.__doc__, 'test belch')
def test_autonumber_with_irregular_values(self):
class Point(AutoNumberEnum, init='x y'):
first = 7, 9
second = 11, 13
self.assertEqual(Point.first.value, 1)
self.assertEqual(Point.first.x, 7)
self.assertEqual(Point.first.y, 9)
self.assertEqual(Point.second.value, 2)
self.assertEqual(Point.second.x, 11)
self.assertEqual(Point.second.y, 13)
with self.assertRaisesRegex(TypeError, '.*number of fields provided do not match init ...x., .y.. != .3, 11, 13..'):
class Point(AutoNumberEnum, init='x y'):
first = 7, 9
second = 3, 11, 13
class Color(AutoNumberEnum, init='__doc__'):
# interactions between AutoNumberEnum and _generate_next_value_ may not be pretty
red = ()
green = 'red'
blue = ()
self.assertTrue(Color.red.__doc__, 1)
self.assertEqual(Color.green.__doc__, 'red')
self.assertTrue(Color.blue.__doc__, 2)
def test_autonumber_and_property(self):
with self.assertRaises(TypeError):
class Color(AutoEnum):
_ignore_ = ()
red = ()
green = ()
blue = ()
@property
def cap_name(self) -> str:
return self.name.title()
def test_autoenum(self):
class Color(AutoEnum):
red
green
blue
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
self.assertEqual([m.value for m in Color], [1, 2, 3])
self.assertEqual([m.name for m in Color], ['red', 'green', 'blue'])
def test_autoenum_with_str(self):
class Color(AutoEnum):
def _generate_next_value_(name, start, count, last_values):
return name
red
green
blue
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
self.assertEqual([m.value for m in Color], ['red', 'green', 'blue'])
self.assertEqual([m.name for m in Color], ['red', 'green', 'blue'])
def test_autoenum_and_default_ignore(self):
class Color(AutoEnum):
red
green
blue
@property
def cap_name(self):
return self.name.title()
self.assertEqual(Color.blue.cap_name, 'Blue')
def test_autonumber_and_overridden_ignore(self):
with self.assertRaises(TypeError):
class Color(AutoEnum):
_ignore_ = 'staticmethod'
red
green
blue
@property
def cap_name(self) -> str:
return self.name.title()
def test_autonumber_and_multiple_assignment(self):
class Color(AutoEnum):
_ignore_ = 'property'
red
green
blue = cyan
@property
def cap_name(self) -> str:
return self.name.title()
self.assertEqual(Color.blue.cap_name, 'Cyan')
def test_multivalue_and_autonumber_inherited(self):
class Measurement(int, Enum, settings=(MultiValue, AddValue), start=0):
one = "20110721"
two = "20120911"
three = "20110518"
M = Measurement
self.assertEqual(M.one, 0)
self.assertTrue(M.one is M(0) is M('20110721'))
def test_combine_new_settings_with_old_settings(self):
class Auto(Enum, settings=Unique):
pass
with self.assertRaises(ValueError):
class AutoUnique(Auto, settings=MagicValue):
BLAH
BLUH
ICK = 1
def test_timedelta(self):
class Period(timedelta, Enum):
'''
different lengths of time
'''
_init_ = 'value period'
_settings_ = NoAlias
_ignore_ = 'Period i'
Period = vars()
for i in range(31):
Period['day_%d' % i] = i, 'day'
for i in range(15):
Period['week_%d' % i] = i*7, 'week'
for i in range(12):
Period['month_%d' % i] = i*30, 'month'
OneDay = day_1
OneWeek = week_1
self.assertFalse(hasattr(Period, '_ignore_'))
self.assertFalse(hasattr(Period, 'Period'))
self.assertFalse(hasattr(Period, 'i'))
self.assertTrue(isinstance(Period.day_1, timedelta))
def test_extend_enum_plain(self):
class Color(UniqueEnum):
red = 1
green = 2
blue = 3
extend_enum(Color, 'brown', 4)
self.assertEqual(Color.brown.name, 'brown')
self.assertEqual(Color.brown.value, 4)
self.assertTrue(Color.brown in Color)
self.assertEqual(len(Color), 4)
def test_extend_enum_shadow(self):
class Color(UniqueEnum):
red = 1
green = 2
blue = 3
extend_enum(Color, 'value', 4)
self.assertEqual(Color.value.name, 'value')
self.assertEqual(Color.value.value, 4)
self.assertTrue(Color.value in Color)
self.assertEqual(len(Color), 4)
self.assertEqual(Color.red.value, 1)
def test_extend_enum_generate(self):
class Foo(AutoEnum):
def _generate_next_value_(name, start, count, values, *args, **kwds):
return name
a
b
#
extend_enum(Foo, 'c')
self.assertEqual(Foo.a.value, 'a')
self.assertEqual(Foo.b.value, 'b')
self.assertEqual(Foo.c.value, 'c')
def test_extend_enum_unique_with_duplicate(self):
with self.assertRaises(ValueError):
class Color(Enum, settings=Unique):
red = 1
green = 2
blue = 3
extend_enum(Color, 'value', 1)
def test_extend_enum_multivalue_with_duplicate(self):
with self.assertRaises(ValueError):
class Color(Enum, settings=MultiValue):
red = 1, 'rojo'
green = 2, 'verde'
blue = 3, 'azul'
extend_enum(Color, 'value', 2)
def test_extend_enum_noalias_with_duplicate(self):
class Color(Enum, settings=NoAlias):
red = 1
green = 2
blue = 3
extend_enum(Color, 'value', 3, )
self.assertRaises(TypeError, Color, 3)
self.assertFalse(Color.value is Color.blue)
self.assertTrue(Color.value.value, 3)
def test_no_duplicates(self):
def bad_duplicates():
class Color(UniqueEnum):
red = 1
green = 2
blue = 3
class Color(UniqueEnum):
red = 1
green = 2
blue = 3
grene = 2
self.assertRaises(ValueError, bad_duplicates)
def test_no_duplicates_kinda(self):
class Silly(UniqueEnum):
one = 1
two = 'dos'
name = 3
class Sillier(IntEnum, UniqueEnum):
single = 1
name = 2
triple = 3
value = 4
def test_auto_number(self):
class Color(Enum, settings=MagicValue):
red
blue
green
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])
self.assertEqual(Color.red.value, 1)
self.assertEqual(Color.blue.value, 2)
self.assertEqual(Color.green.value, 3)
def test_auto_name(self):
class Color(Enum, settings=MagicValue):
def _generate_next_value_(name, start, count, last):
return name
red
blue
green
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])
self.assertEqual(Color.red.value, 'red')
self.assertEqual(Color.blue.value, 'blue')
self.assertEqual(Color.green.value, 'green')
def test_auto_name_inherit(self):
class AutoNameEnum(Enum):
def _generate_next_value_(name, start, count, last):
return name
class Color(AutoNameEnum, settings=MagicValue):
red
blue
green
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])
self.assertEqual(Color.red.value, 'red')
self.assertEqual(Color.blue.value, 'blue')
self.assertEqual(Color.green.value, 'green')
def test_auto_garbage(self):
class Color(Enum):
_settings_ = MagicValue
red = 'red'
blue
self.assertEqual(Color.blue.value, 1)
def test_auto_garbage_corrected(self):
class Color(Enum, settings=MagicValue):
red = 'red'
blue = 2
green
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])
self.assertEqual(Color.red.value, 'red')
self.assertEqual(Color.blue.value, 2)
self.assertEqual(Color.green.value, 3)
def test_duplicate_auto(self):
class Dupes(Enum, settings=MagicValue):
first = primero
second
third
self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes))
def test_order_as_function(self):
# first with _init_
class TestSequence(Enum):
_init_ = 'value, sequence'
_order_ = lambda member: member.sequence
item_id = 'An$(1,6)', 0 # Item Code
company_id = 'An$(7,2)', 1 # Company Code
warehouse_no = 'An$(9,4)', 2 # Warehouse Number
company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY
key_type = 'Cn$(19,3)', 4 # Key Type = '1**'
available = 'Zn$(1,1)', 5 # Available?
contract_item = 'Bn(2,1)', 6 # Contract Item?
sales_category = 'Fn', 7 # Sales Category
gl_category = 'Rn$(5,1)', 8 # G/L Category
warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category
inv_units = 'Qn$(7,2)', 10 # Inv Units
for i, member in enumerate(TestSequence):
self.assertEqual(i, member.sequence)
ts = TestSequence
self.assertEqual(ts.item_id.name, 'item_id')
self.assertEqual(ts.item_id.value, 'An$(1,6)')
self.assertEqual(ts.item_id.sequence, 0)
self.assertEqual(ts.company_id.name, 'company_id')
self.assertEqual(ts.company_id.value, 'An$(7,2)')
self.assertEqual(ts.company_id.sequence, 1)
self.assertEqual(ts.warehouse_no.name, 'warehouse_no')
self.assertEqual(ts.warehouse_no.value, 'An$(9,4)')
self.assertEqual(ts.warehouse_no.sequence, 2)
self.assertEqual(ts.company.name, 'company')
self.assertEqual(ts.company.value, 'Hn$(13,6)')
self.assertEqual(ts.company.sequence, 3)
self.assertEqual(ts.key_type.name, 'key_type')
self.assertEqual(ts.key_type.value, 'Cn$(19,3)')
self.assertEqual(ts.key_type.sequence, 4)
self.assertEqual(ts.available.name, 'available')
self.assertEqual(ts.available.value, 'Zn$(1,1)')
self.assertEqual(ts.available.sequence, 5)
self.assertEqual(ts.contract_item.name, 'contract_item')
self.assertEqual(ts.contract_item.value, 'Bn(2,1)')
self.assertEqual(ts.contract_item.sequence, 6)
self.assertEqual(ts.sales_category.name, 'sales_category')
self.assertEqual(ts.sales_category.value, 'Fn')
self.assertEqual(ts.sales_category.sequence, 7)
self.assertEqual(ts.gl_category.name, 'gl_category')
self.assertEqual(ts.gl_category.value, 'Rn$(5,1)')
self.assertEqual(ts.gl_category.sequence, 8)
self.assertEqual(ts.warehouse_category.name, 'warehouse_category')
self.assertEqual(ts.warehouse_category.value, 'Sn$(6,1)')
self.assertEqual(ts.warehouse_category.sequence, 9)
self.assertEqual(ts.inv_units.name, 'inv_units')
self.assertEqual(ts.inv_units.value, 'Qn$(7,2)')
self.assertEqual(ts.inv_units.sequence, 10)
# and then without
class TestSequence(Enum):
_order_ = lambda member: member.value[1]
item_id = 'An$(1,6)', 0 # Item Code
company_id = 'An$(7,2)', 1 # Company Code
warehouse_no = 'An$(9,4)', 2 # Warehouse Number
company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY
key_type = 'Cn$(19,3)', 4 # Key Type = '1**'
available = 'Zn$(1,1)', 5 # Available?
contract_item = 'Bn(2,1)', 6 # Contract Item?
sales_category = 'Fn', 7 # Sales Category
gl_category = 'Rn$(5,1)', 8 # G/L Category
warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category
inv_units = 'Qn$(7,2)', 10 # Inv Units
for i, member in enumerate(TestSequence):
self.assertEqual(i, member.value[1])
ts = TestSequence
self.assertEqual(ts.item_id.name, 'item_id')
self.assertEqual(ts.item_id.value, ('An$(1,6)', 0))
self.assertEqual(ts.company_id.name, 'company_id')
self.assertEqual(ts.company_id.value, ('An$(7,2)', 1))
self.assertEqual(ts.warehouse_no.name, 'warehouse_no')
self.assertEqual(ts.warehouse_no.value, ('An$(9,4)', 2))
self.assertEqual(ts.company.name, 'company')
self.assertEqual(ts.company.value, ('Hn$(13,6)', 3))
self.assertEqual(ts.key_type.name, 'key_type')
self.assertEqual(ts.key_type.value, ('Cn$(19,3)', 4))
self.assertEqual(ts.available.name, 'available')
self.assertEqual(ts.available.value, ('Zn$(1,1)', 5))
self.assertEqual(ts.contract_item.name, 'contract_item')
self.assertEqual(ts.contract_item.value, ('Bn(2,1)', 6))
self.assertEqual(ts.sales_category.name, 'sales_category')
self.assertEqual(ts.sales_category.value, ('Fn', 7))
self.assertEqual(ts.gl_category.name, 'gl_category')
self.assertEqual(ts.gl_category.value, ('Rn$(5,1)', 8))
self.assertEqual(ts.warehouse_category.name, 'warehouse_category')
self.assertEqual(ts.warehouse_category.value, ('Sn$(6,1)', 9))
self.assertEqual(ts.inv_units.name, 'inv_units')
self.assertEqual(ts.inv_units.value, ('Qn$(7,2)', 10))
# then with _init_ but without value
with self.assertRaises(TypeError):
class TestSequence(Enum):
_init_ = 'sequence'
_order_ = lambda member: member.sequence
item_id = 'An$(1,6)', 0 # Item Code
company_id = 'An$(7,2)', 1 # Company Code
warehouse_no = 'An$(9,4)', 2 # Warehouse Number
company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY
key_type = 'Cn$(19,3)', 4 # Key Type = '1**'
available = 'Zn$(1,1)', 5 # Available?
contract_item = 'Bn(2,1)', 6 # Contract Item?
sales_category = 'Fn', 7 # Sales Category
gl_category = 'Rn$(5,1)', 8 # G/L Category
warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category
inv_units = 'Qn$(7,2)', 10 # Inv Units
# finally, out of order so Python 3 barfs
with self.assertRaises(TypeError):
class TestSequence(Enum):
_init_ = 'sequence'
_order_ = lambda member: member.sequence
item_id = 'An$(1,6)', 0 # Item Code
warehouse_no = 'An$(9,4)', 2 # Warehouse Number
company = 'Hn$(13,6)', 3 # 4 SPACES + COMPANY
company_id = 'An$(7,2)', 1 # Company Code
inv_units = 'Qn$(7,2)', 10 # Inv Units
available = 'Zn$(1,1)', 5 # Available?
contract_item = 'Bn(2,1)', 6 # Contract Item?
sales_category = 'Fn', 7 # Sales Category
key_type = 'Cn$(19,3)', 4 # Key Type = '1**'
gl_category = 'Rn$(5,1)', 8 # G/L Category
warehouse_category = 'Sn$(6,1)', 9 # Warehouse Category
if pyver >= PY3_3:
def test_missing(self):
class Color(Enum):
red = 1
green = 2
blue = 3
@classmethod
def _missing_(cls, item):
if item == 'three':
return cls.blue
elif item == 'bad return':
# trigger internal error
return 5
elif item == 'error out':
raise ZeroDivisionError
else:
# trigger not found
return None
self.assertIs(Color('three'), Color.blue)
self.assertRaises(ValueError, Color, 7)
try:
Color('bad return')
except TypeError as exc:
self.assertTrue(isinstance(exc.__cause__, ValueError))
else:
raise Exception('Exception not raised.')
try:
Color('error out')
except ZeroDivisionError as exc:
self.assertTrue(isinstance(exc.__cause__, ValueError))
else:
raise Exception('Exception not raised.')
def test_enum_of_types(self):
"""Support using Enum to refer to types deliberately."""
class MyTypes(Enum):
i = int
f = float
s = str
self.assertEqual(MyTypes.i.value, int)
self.assertEqual(MyTypes.f.value, float)
self.assertEqual(MyTypes.s.value, str)
class Foo:
pass
class Bar:
pass
class MyTypes2(Enum):
a = Foo
b = Bar
self.assertEqual(MyTypes2.a.value, Foo)
self.assertEqual(MyTypes2.b.value, Bar)
class SpamEnumNotInner:
pass
class SpamEnum(Enum):
spam = SpamEnumNotInner
self.assertEqual(SpamEnum.spam.value, SpamEnumNotInner)
def test_nested_classes_in_enum_do_not_create_members(self):
"""Support locally-defined nested classes."""
# manually set __qualname__ to remove testing framework noise
class Outer(Enum):
__qualname__ = "Outer"
a = 1
b = 2
class Inner(Enum):
__qualname__ = "Outer.Inner"
foo = 10
bar = 11
self.assertTrue(isinstance(Outer.Inner, type))
self.assertEqual(Outer.a.value, 1)
self.assertEqual(Outer.Inner.foo.value, 10)
self.assertEqual(
list(Outer.Inner),
[Outer.Inner.foo, Outer.Inner.bar],
)
self.assertEqual(
list(Outer),
[Outer.a, Outer.b],
)
if pyver == PY3_4:
def test_class_nested_enum_and_pickle_protocol_four(self):
# would normally just have this directly in the class namespace
class NestedEnum(Enum):
twigs = 'common'
shiny = 'rare'
self.__class__.NestedEnum = NestedEnum
self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__
test_pickle_exception(
self.assertRaises, PicklingError, self.NestedEnum.twigs,
protocol=(0, 3))
test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs,
protocol=(4, HIGHEST_PROTOCOL))
elif pyver >= PY3_5:
def test_class_nested_enum_and_pickle_protocol_four(self):
# would normally just have this directly in the class namespace
class NestedEnum(Enum):
twigs = 'common'
shiny = 'rare'
self.__class__.NestedEnum = NestedEnum
self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__
test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs,
protocol=(0, HIGHEST_PROTOCOL))
if pyver >= PY3_4:
def test_enum_injection(self):
class Color(Enum):
_order_ = 'BLACK WHITE'
BLACK = Color('black', '#000')
WHITE = Color('white', '#fff')
def __init__(self, label, hex):
self.label = label
self.hex = hex
self.assertEqual([Color.BLACK, Color.WHITE], list(Color))
self.assertEqual(Color.WHITE.hex, '#fff')
self.assertEqual(Color.BLACK.label, 'black')
def test_subclasses_with_getnewargs_ex(self):
class NamedInt(int):
__qualname__ = 'NamedInt' # needed for pickle protocol 4
def __new__(cls, *args):
_args = args
if len(args) < 2:
raise TypeError("name and value must be specified")
name, args = args[0], args[1:]
self = int.__new__(cls, *args)
self._intname = name
self._args = _args
return self
def __getnewargs_ex__(self):
return self._args, {}
@property
def __name__(self):
return self._intname
def __repr__(self):
# repr() is updated to include the name and type info
return "{}({!r}, {})".format(type(self).__name__,
self.__name__,
int.__repr__(self))
def __str__(self):
# str() is unchanged, even if it relies on the repr() fallback
base = int
base_str = base.__str__
if base_str.__objclass__ is object:
return base.__repr__(self)
return base_str(self)
# for simplicity, we only define one operator that
# propagates expressions
def __add__(self, other):
temp = int(self) + int( other)
if isinstance(self, NamedInt) and isinstance(other, NamedInt):
return NamedInt(
'({0} + {1})'.format(self.__name__, other.__name__),
temp )
else:
return temp
class NEI(NamedInt, Enum):
__qualname__ = 'NEI' # needed for pickle protocol 4
x = ('the-x', 1)
y = ('the-y', 2)
self.assertIs(NEI.__new__, Enum.__new__)
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
globals()['NamedInt'] = NamedInt
globals()['NEI'] = NEI
NI5 = NamedInt('test', 5)
self.assertEqual(NI5, 5)
test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, HIGHEST_PROTOCOL))
self.assertEqual(NEI.y.value, 2)
test_pickle_dump_load(self.assertTrue, NEI.y, protocol=(4, HIGHEST_PROTOCOL))
class TestOrderV3(TestCase):
"""
Test definition order versus _order_ order.
"""
def test_same_members(self):
class Color(Enum):
_order_ = 'red green blue'
red = 1
green = 2
blue = 3
def test_same_members_with_aliases(self):
class Color(Enum):
_order_ = 'red green blue'
red = 1
green = 2
blue = 3
verde = green
def test_same_members_wrong_order(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Enum):
_order_ = 'red green blue'
red = 1
blue = 3
green = 2
def test_order_has_extra_members(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Enum):
_order_ = 'red green blue purple'
red = 1
green = 2
blue = 3
def test_order_has_extra_members_with_aliases(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Enum):
_order_ = 'red green blue purple'
red = 1
green = 2
blue = 3
verde = green
def test_enum_has_extra_members(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Enum):
_order_ = 'red green blue'
red = 1
green = 2
blue = 3
purple = 4
def test_enum_has_extra_members_with_aliases(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Enum):
_order_ = 'red green blue'
red = 1
green = 2
blue = 3
purple = 4
verde = green
def test_same_members_flag(self):
class Color(Flag):
_order_ = 'red green blue'
red = 1
green = 2
blue = 4
def test_same_members_with_aliases_flag(self):
class Color(Flag):
_order_ = 'red green blue'
red = 1
green = 2
blue = 4
verde = green
def test_same_members_wrong_order_falg(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Flag):
_order_ = 'red green blue'
red = 1
blue = 4
green = 2
def test_order_has_extra_members_flag(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Flag):
_order_ = 'red green blue purple'
red = 1
green = 2
blue = 4
def test_order_has_extra_members_with_aliases_flag(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Flag):
_order_ = 'red green blue purple'
red = 1
green = 2
blue = 4
verde = green
def test_enum_has_extra_members_flag(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Flag):
_order_ = 'red green blue'
red = 1
green = 2
blue = 4
purple = 8
def test_enum_has_extra_members_with_aliases_flag(self):
with self.assertRaisesRegex(TypeError, 'member order does not match _order_'):
class Color(Flag):
_order_ = 'red green blue'
red = 1
green = 2
blue = 4
purple = 8
verde = green
class TestNamedTupleV3(TestCase):
def test_fixed_size(self):
class Book(NamedTuple, size=TupleSize.fixed):
title = 0
author = 1
genre = 2
b = Book('Teckla', 'Steven Brust', 'fantasy')
self.assertTrue('Teckla' in b)
self.assertTrue('Steven Brust' in b)
self.assertTrue('fantasy' in b)
self.assertEqual(b.title, 'Teckla')
self.assertEqual(b.author, 'Steven Brust')
self.assertRaises(TypeError, Book, 'Teckla', 'Steven Brust')
self.assertRaises(TypeError, Book, 'Teckla')
def test_minimum_size(self):
class Book(NamedTuple, size=TupleSize.minimum):
title = 0
author = 1
b = Book('Teckla', 'Steven Brust', 'fantasy')
self.assertTrue('Teckla' in b)
self.assertTrue('Steven Brust' in b)
self.assertTrue('fantasy' in b)
self.assertEqual(b.title, 'Teckla')
self.assertEqual(b.author, 'Steven Brust')
self.assertEqual(b[2], 'fantasy')
b = Book('Teckla', 'Steven Brust')
self.assertTrue('Teckla' in b)
self.assertTrue('Steven Brust' in b)
self.assertEqual(b.title, 'Teckla')
self.assertEqual(b.author, 'Steven Brust')
self.assertRaises(TypeError, Book, 'Teckla')
def test_variable_size(self):
class Book(NamedTuple, size=TupleSize.variable):
title = 0
author = 1
genre = 2
b = Book('Teckla', 'Steven Brust', 'fantasy')
self.assertTrue('Teckla' in b)
self.assertTrue('Steven Brust' in b)
self.assertTrue('fantasy' in b)
self.assertEqual(b.title, 'Teckla')
self.assertEqual(b.author, 'Steven Brust')
self.assertEqual(b.genre, 'fantasy')
b = Book('Teckla', 'Steven Brust')
self.assertTrue('Teckla' in b)
self.assertTrue('Steven Brust' in b)
self.assertEqual(b.title, 'Teckla')
self.assertEqual(b.author, 'Steven Brust')
self.assertRaises(AttributeError, getattr, b, 'genre')
self.assertRaises(TypeError, Book, title='Teckla', genre='fantasy')
self.assertRaises(TypeError, Book, author='Steven Brust')
class TestStackoverflowAnswersV3(TestCase):
def test_self_referential_directions(self):
# https://stackoverflow.com/a/64000706/208880
class Directions(Enum):
#
NORTH = 1, 0
WEST = 0, 1
SOUTH = -1, 0
EAST = 0, -1
#
def __init__(self, x, y):
self.x = x
self.y = y
if len(self.__class__):
# make links
all = list(self.__class__)
left, right = all[0], all[-1]
self.left = left
self.right = right
left.right = self
right.left = self
#
D = Directions
self.assertEqual(D.NORTH.value, (1, 0))
self.assertTrue(D.NORTH.left is D.WEST)
self.assertTrue(D.SOUTH.right is D.WEST)
def test_self_referential_rock_paper_scissors(self):
# https://stackoverflow.com/a/57085357/208880
class RPS(Enum):
#
Rock = "rock"
Paper = "paper"
Scissors = "scissors"
#
def __init__(self, value):
if len(self.__class__):
# make links
all = list(self.__class__)
first, previous = all[0], all[-1]
first.beats = self
self.beats = previous
#
self.assertTrue(RPS.Rock.beats is RPS.Scissors)
self.assertTrue(RPS.Scissors.beats is RPS.Paper)
self.assertTrue(RPS.Paper.beats is RPS.Rock)
def test_arduino_headers(self):
# https://stackoverflow.com/q/65048495/208880
class CHeader(Enum):
def __init_subclass__(cls, **kwds):
# write Enums to C header file
cls_name = cls.__name__
header_path = getattr(cls, '_%s__header' % cls_name)
with open(header_path, 'w') as fh:
fh.write('initial header stuff here\n')
for enum in cls:
fh.write('#define %s %r\n' % (enum.name, enum.value))
class Arduino(CHeader):
__header = os.path.join(tempdir, 'arduino.h')
ONE = 1
TWO = 2
with open(os.path.join(tempdir, 'arduino.h')) as fh:
data = fh.read()
self.assertEqual(textwrap.dedent("""\
initial header stuff here
#define ONE 1
#define TWO 2
"""),
data,
)
def test_create_C_like_Enum(self):
# https://stackoverflow.com/a/35965438/208880
class Id(Enum, settings=MagicValue, start=0):
#
NONE # 0x0
HEARTBEAT # 0x1
FLUID_TRANSFER_REQUEST
FLUID_TRANSFER_STATUS_MSG
FLUID_TRANSFER_ERROR_MSG
# ...
#
# Camera App Messages
START_SENDING_PICTURES = 0x010000
STOP_SENDING_PICTURES
START_RECORDING_VIDEO_REQ
STOP_RECORDING_VIDEO_REQ
# ...
#
# Sensor Calibration
VOLUME_REQUEST = 0x020000
START_CAL
CLI_COMMAND_REQUEST
CLI_COMMAND_RESPONSE
#
# File Mananger
NEW_DELIVERY_REQ = 0x30000
GET_DELIVERY_FILE_REQ
GET_FILE_REQ
#
ACK_NACK
RESPONSE
#
LAST_ID
#
self.assertEqual(Id.NONE.value, 0)
self.assertEqual(Id.FLUID_TRANSFER_ERROR_MSG.value, 4)
self.assertEqual(Id.START_SENDING_PICTURES.value, 0x010000)
self.assertEqual(Id.STOP_RECORDING_VIDEO_REQ.value, 0x010003)
self.assertEqual(Id.START_CAL.value, 0x020001)
self.assertEqual(Id.LAST_ID.value, 0x30005)
@unittest.skipUnless(pyparsing, 'pyparsing not installed')
def test_c_header_scanner(self):
# https://stackoverflow.com/questions/58732872/208880
with open(os.path.join(tempdir, 'c_plus_plus.h'), 'w') as fh:
fh.write("""
stuff before
enum hello {
Zero,
One,
Two,
Three,
Five=5,
Six,
Ten=10
};
in the middle
enum blah
{
alpha,
beta,
gamma = 10 ,
zeta = 50
};
at the end
""")
from pyparsing import Group, Optional, Suppress, Word, ZeroOrMore
from pyparsing import alphas, alphanums, nums
#
CPPEnum = None
class CPPEnumType(EnumMeta):
#
@classmethod
def __prepare__(metacls, clsname, bases, **kwds):
# return a standard dictionary for the initial processing
return {}
#
def __init__(clsname, *args , **kwds):
super(CPPEnumType, clsname).__init__(*args)
#
def __new__(metacls, clsname, bases, clsdict, **kwds):
if CPPEnum is None:
# first time through, ignore the rest
enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds)
enum_dict.update(clsdict)
return super(CPPEnumType, metacls).__new__(metacls, clsname, bases, enum_dict, **kwds)
members = []
#
# remove _file and _name using `pop()` as they will cause problems in EnumMeta
try:
file = clsdict.pop('_file')
except KeyError:
raise TypeError('_file not specified')
cpp_enum_name = clsdict.pop('_name', clsname.lower())
with open(file) as fh:
file_contents = fh.read()
#
# syntax we don't want to see in the final parse tree
LBRACE, RBRACE, EQ, COMMA = map(Suppress, "{}=,")
_enum = Suppress("enum")
identifier = Word(alphas, alphanums + "_")
integer = Word(nums)
enumValue = Group(identifier("name") + Optional(EQ + integer("value")))
enumList = Group(enumValue + ZeroOrMore(COMMA + enumValue))
enum = _enum + identifier("enum") + LBRACE + enumList("names") + RBRACE
#
# find the cpp_enum_name ignoring other syntax and other enums
for item, start, stop in enum.scanString(file_contents):
if item.enum != cpp_enum_name:
continue
id = 0
for entry in item.names:
if entry.value != "":
id = int(entry.value)
members.append((entry.name.upper(), id))
id += 1
#
# get the real EnumDict
enum_dict = super(CPPEnumType, metacls).__prepare__(clsname, bases, **kwds)
# transfer the original dict content, names starting with '_' first
items = list(clsdict.items())
items.sort(key=lambda p: (0 if p[0][0] == '_' else 1, p))
for name, value in items:
enum_dict[name] = value
# add the members
for name, value in members:
enum_dict[name] = value
return super(CPPEnumType, metacls).__new__(metacls, clsname, bases, enum_dict, **kwds)
#
class CPPEnum(IntEnum, metaclass=CPPEnumType):
pass
#
class Hello(CPPEnum):
_file = os.path.join(tempdir, 'c_plus_plus.h')
#
class Blah(CPPEnum):
_file = os.path.join(tempdir, 'c_plus_plus.h')
_name = 'blah'
#
self.assertEqual(
list(Hello),
[Hello.ZERO, Hello.ONE, Hello.TWO, Hello.THREE, Hello.FIVE, Hello.SIX, Hello.TEN],
)
self.assertEqual(Hello.ZERO.value, 0)
self.assertEqual(Hello.THREE.value, 3)
self.assertEqual(Hello.SIX.value, 6)
self.assertEqual(Hello.TEN.value, 10)
#
self.assertEqual(
list(Blah),
[Blah.ALPHA, Blah.BETA, Blah.GAMMA, Blah.ZETA],
)
self.assertEqual(Blah.ALPHA.value, 0)
self.assertEqual(Blah.BETA.value, 1)
self.assertEqual(Blah.GAMMA.value, 10)
self.assertEqual(Blah.ZETA.value, 50)
class TestIssuesV3(TestCase):
"""
Problems that were stated in issues.
"""
def test_auto_multi_int_1(self):
class Measurement(int, AddValueEnum, MultiValueEnum, start=0):
one = "20110721"
two = "20120911"
three = "20110518"
self.assertEqual([m.value for m in Measurement], [0, 1, 2])
self.assertEqual([m.name for m in Measurement], ['one', 'two', 'three'])
self.assertIs(Measurement(0), Measurement.one)
self.assertIs(Measurement('20110721'), Measurement.one)
self.assertIs(Measurement(1), Measurement.two)
self.assertIs(Measurement('20120911'), Measurement.two)
self.assertIs(Measurement(2), Measurement.three)
self.assertIs(Measurement('20110518'), Measurement.three)
def test_auto_multi_int_2(self):
class Measurement(int, Enum, settings=(MultiValue, AddValue), start=0):
one = "20110721"
two = "20120911"
three = "20110518"
self.assertEqual([m.value for m in Measurement], [0, 1, 2])
self.assertEqual([m.name for m in Measurement], ['one', 'two', 'three'])
self.assertIs(Measurement(0), Measurement.one)
self.assertIs(Measurement('20110721'), Measurement.one)
self.assertIs(Measurement(1), Measurement.two)
self.assertIs(Measurement('20120911'), Measurement.two)
self.assertIs(Measurement(2), Measurement.three)
self.assertIs(Measurement('20110518'), Measurement.three)
def test_extend_enum_with_init(self):
class Color(Enum, settings=MultiValue, init='foo bar'):
red = '1', 'yes'
green = '2', 'no'
blue = '3', 'maybe'
self.assertEqual(Color.red.value, '1')
self.assertEqual(Color.red.foo, '1')
self.assertEqual(Color.red.bar, 'yes')
extend_enum(Color, 'opacity', '4', 'never')
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue, Color.opacity])
self.assertEqual(Color.opacity.value, '4')
self.assertEqual(Color.opacity.name, 'opacity')
self.assertTrue(Color('4') is Color.opacity)
self.assertTrue(Color('never') is Color.opacity)
class TestExtendEnumV3(TestCase):
def test_extend_enum_plain(self):
class Color(Enum):
red = 1
green = 2
blue = 3
self.assertRaisesRegex(TypeError, '.blue. already in use as property..Color.blue: 3.', extend_enum, Color, 'blue', 5)
#
extend_enum(Color, 'brown', 4)
self.assertEqual(Color.brown.name, 'brown')
self.assertEqual(Color.brown.value, 4)
self.assertTrue(Color.brown in Color)
self.assertEqual(Color(4), Color.brown)
self.assertEqual(Color['brown'], Color.brown)
self.assertEqual(len(Color), 4)
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 5)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(5), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), 5)
def test_extend_enum_alias(self):
class Color(Enum):
red = 1
green = 2
blue = 3
extend_enum(Color, 'rojo', 1)
self.assertEqual(Color.rojo.name, 'red')
self.assertEqual(Color.rojo.value, 1)
self.assertTrue(Color.rojo in Color)
self.assertEqual(Color(1), Color.rojo)
self.assertEqual(Color['rojo'], Color.red)
self.assertEqual(len(Color), 3)
def test_extend_enum_unique(self):
class Color(UniqueEnum):
red = 1
green = 2
blue = 3
self.assertRaisesRegex(ValueError, r'<Color.rojo: 1> is a duplicate of <Color.red: 1>', extend_enum, Color, 'rojo', 1)
#
self.assertEqual(Color.red.name, 'red')
self.assertEqual(Color.red.value, 1)
self.assertTrue(Color.red in Color)
self.assertEqual(Color(1), Color.red)
self.assertEqual(Color['red'], Color.red)
self.assertEqual(Color.green.name, 'green')
self.assertEqual(Color.green.value, 2)
self.assertTrue(Color.green in Color)
self.assertEqual(Color(2), Color.green)
self.assertEqual(Color['blue'], Color.blue)
self.assertEqual(Color.blue.name, 'blue')
self.assertEqual(Color.blue.value, 3)
self.assertTrue(Color.blue in Color)
self.assertEqual(Color(3), Color.blue)
self.assertEqual(len(Color), 3)
#
extend_enum(Color, 'brown', 4)
self.assertEqual(Color.brown.name, 'brown')
self.assertEqual(Color.brown.value, 4)
self.assertTrue(Color.brown in Color)
self.assertEqual(Color(4), Color.brown)
self.assertEqual(Color['brown'], Color.brown)
self.assertEqual(len(Color), 4)
#
self.assertRaisesRegex(ValueError, '<Color.verde: 2> is a duplicate of <Color.green: 2>', extend_enum, Color, 'verde', 2)
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 5)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(5), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), 5)
def test_extend_enum_shadow_property(self):
class Color(Enum):
red = 1
green = 2
blue = 3
extend_enum(Color, 'value', 4)
self.assertEqual(Color.value.name, 'value')
self.assertEqual(Color.value.value, 4)
self.assertTrue(Color.value in Color)
self.assertEqual(Color(4), Color.value)
self.assertEqual(Color['value'], Color.value)
self.assertEqual(len(Color), 4)
self.assertEqual(Color.red.value, 1)
def test_extend_enum_shadow_base(self):
class hohum(object):
def cyan(self):
"cyanize a color"
return self.value
class Color(hohum, Enum):
red = 1
green = 2
blue = 3
self.assertRaisesRegex(TypeError, r'already in use in superclass', extend_enum, Color, 'cyan', 4)
self.assertEqual(len(Color), 3)
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
def test_extend_enum_multivalue(self):
class Color(MultiValueEnum):
red = 1, 4, 7
green = 2, 5, 8
blue = 3, 6, 9
extend_enum(Color, 'brown', 10, 20)
self.assertEqual(Color.brown.name, 'brown')
self.assertEqual(Color.brown.value, 10)
self.assertTrue(Color.brown in Color)
self.assertEqual(Color(10), Color.brown)
self.assertEqual(Color(20), Color.brown)
self.assertEqual(Color['brown'], Color.brown)
self.assertEqual(len(Color), 4)
#
self.assertRaisesRegex(ValueError, 'no values specified for MultiValue enum', extend_enum, Color, 'mauve')
def test_extend_enum_multivalue_alias(self):
class Color(MultiValueEnum):
red = 1, 4, 7
green = 2, 5, 8
blue = 3, 6, 9
self.assertRaisesRegex(ValueError, r'<Color.rojo: 7> is a duplicate of <Color.red: 1>', extend_enum, Color, 'rojo', 7)
self.assertEqual(Color.red.name, 'red')
self.assertEqual(Color.red.value, 1)
self.assertTrue(Color.red in Color)
self.assertEqual(Color(1), Color.red)
self.assertEqual(Color(4), Color.red)
self.assertEqual(Color(7), Color.red)
self.assertEqual(Color['red'], Color.red)
self.assertEqual(Color.green.name, 'green')
self.assertEqual(Color.green.value, 2)
self.assertTrue(Color.green in Color)
self.assertEqual(Color(2), Color.green)
self.assertEqual(Color(5), Color.green)
self.assertEqual(Color(8), Color.green)
self.assertEqual(Color['blue'], Color.blue)
self.assertEqual(Color.blue.name, 'blue')
self.assertEqual(Color.blue.value, 3)
self.assertTrue(Color.blue in Color)
self.assertEqual(Color(3), Color.blue)
self.assertEqual(Color(6), Color.blue)
self.assertEqual(Color(9), Color.blue)
self.assertEqual(len(Color), 3)
def test_extend_enum_multivalue_str(self):
class M(str, MultiValueEnum):
VALUE_1 = 'value_1', 'VALUE_1'
VALUE_2 = 'value_2', 'VALUE_2'
VALUE_3 = 'value_3', 'VALUE_3'
self.assertTrue(M._member_type_ is str)
extend_enum(M, 'VALUE_4', 'value_4', 'VALUE_4')
self.assertEqual(list(M), [M.VALUE_1, M.VALUE_2, M.VALUE_3, M.VALUE_4])
self.assertTrue(M('value_4') is M.VALUE_4)
self.assertTrue(M('VALUE_4') is M.VALUE_4)
self.assertTrue(M.VALUE_4.name == 'VALUE_4')
self.assertTrue(M.VALUE_4.value == 'value_4')
def test_extend_intenum(self):
class Index(IntEnum):
DeviceType = 0x1000
ErrorRegister = 0x1001
for name, value in (
('ControlWord', 0x6040),
('StatusWord', 0x6041),
('OperationMode', 0x6060),
):
extend_enum(Index, name, value)
self.assertEqual(len(Index), 5)
self.assertEqual(list(Index), [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode])
self.assertEqual(Index.DeviceType.value, 0x1000)
self.assertEqual(Index.StatusWord.value, 0x6041)
def test_extend_multi_init(self):
class HTTPStatus(IntEnum):
def __new__(cls, value, phrase, description):
obj = int.__new__(cls, value)
obj._value_ = value
obj.phrase = phrase
obj.description = description
return obj
CONTINUE = 100, 'Continue', 'Request received, please continue'
SWITCHING_PROTOCOLS = 101, 'Switching Protocols', 'Switching to new protocol; obey Upgrade header'
PROCESSING = 102, 'Processing', ''
length = 3
extend_enum(HTTPStatus, 'BAD_SPAM', 513, 'Too greasy', 'for a train')
extend_enum(HTTPStatus, 'BAD_EGGS', 514, 'Too green', '')
self.assertEqual(len(HTTPStatus), length+2)
self.assertEqual(
list(HTTPStatus)[-2:],
[HTTPStatus.BAD_SPAM, HTTPStatus.BAD_EGGS],
)
self.assertEqual(HTTPStatus.BAD_SPAM.value, 513)
self.assertEqual(HTTPStatus.BAD_SPAM.name, 'BAD_SPAM')
self.assertEqual(HTTPStatus.BAD_SPAM.phrase, 'Too greasy')
self.assertEqual(HTTPStatus.BAD_SPAM.description, 'for a train')
self.assertEqual(HTTPStatus.BAD_EGGS.value, 514)
self.assertEqual(HTTPStatus.BAD_EGGS.name, 'BAD_EGGS')
self.assertEqual(HTTPStatus.BAD_EGGS.phrase, 'Too green')
self.assertEqual(HTTPStatus.BAD_EGGS.description, '')
def test_extend_flag(self):
class Color(Flag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(8) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, 8)
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, Flag))
def test_extend_flag_backwards(self):
class Color(Flag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, Flag))
#
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(8) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, 8)
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 16)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(16), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), 5)
def test_extend_intflag(self):
class Color(IntFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(8) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, 8)
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, Flag))
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 16)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(16), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), 5)
def test_extend_intflag_backwards(self):
class Color(IntFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, Flag))
#
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(8) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, 8)
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 16)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(16), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), 5)
def test_extend_strenum(self):
class Color(StrEnum):
RED = auto()
GREEN = auto()
BLUE = auto()
extend_enum(Color, 'BLACK')
self.assertEqual(Color.BLACK.name, 'BLACK')
self.assertEqual(Color.BLACK.value, 'black')
self.assertEqual(len(Color), 4)
@unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available')
def test_extend_enum_stdlib(self):
class Color(StdlibEnum):
red = 1
green = 2
blue = 3
self.assertEqual(getattr(Color.red, '_values_', None), None)
extend_enum(Color, 'brown', 4)
self.assertEqual(Color.brown.name, 'brown')
self.assertEqual(Color.brown.value, 4)
self.assertTrue(Color.brown in Color)
self.assertEqual(Color(4), Color.brown)
self.assertEqual(Color['brown'], Color.brown)
self.assertEqual(len(Color), 4)
@unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available')
def test_extend_enum_plain_stdlib(self):
class Color(StdlibEnum):
red = 1
green = 2
blue = 3
self.assertRaisesRegex(TypeError, 'already in use as', extend_enum, Color, 'blue', 5)
#
extend_enum(Color, 'brown', 4)
self.assertEqual(Color.brown.name, 'brown')
self.assertEqual(Color.brown.value, 4)
self.assertTrue(Color.brown in Color)
self.assertEqual(Color(4), Color.brown)
self.assertEqual(Color['brown'], Color.brown)
self.assertEqual(len(Color), 4)
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue, Color.brown])
self.assertEqual([c.value for c in Color], [1, 2, 3, 4])
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 5)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(5), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), 5)
@unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available')
def test_extend_enum_alias_stdlib(self):
class Color(StdlibEnum):
red = 1
green = 2
blue = 3
extend_enum(Color, 'rojo', 1)
self.assertEqual(Color.rojo.name, 'red')
self.assertEqual(Color.rojo.value, 1)
self.assertTrue(Color.rojo in Color)
self.assertEqual(Color(1), Color.rojo)
self.assertEqual(Color['rojo'], Color.red)
self.assertEqual(len(Color), 3)
@unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available')
def test_extend_enum_shadow_property_stdlib(self):
class Color(StdlibEnum):
red = 1
green = 2
blue = 3
extend_enum(Color, 'value', 4)
self.assertEqual(Color.value.name, 'value')
self.assertEqual(Color.value.value, 4)
self.assertTrue(Color.value in Color)
self.assertEqual(Color(4), Color.value)
self.assertEqual(Color['value'], Color.value)
self.assertEqual(len(Color), 4)
self.assertEqual(Color.red.value, 1)
@unittest.skipUnless(StdlibEnum, 'Stdlib Enum not available')
def test_extend_enum_shadow_base_stdlib(self):
class hohum(object):
def cyan(self):
"cyanize a color"
return self.value
class Color(hohum, StdlibEnum):
red = 1
green = 2
blue = 3
self.assertRaisesRegex(TypeError, r'already in use in superclass', extend_enum, Color, 'cyan', 4)
self.assertEqual(len(Color), 3)
self.assertEqual(list(Color), [Color.red, Color.green, Color.blue])
@unittest.skipUnless(StdlibIntEnum, 'Stdlib IntEnum not available')
def test_extend_intenum_stdlib(self):
class Index(StdlibIntEnum):
DeviceType = 0x1000
ErrorRegister = 0x1001
for name, value in (
('ControlWord', 0x6040),
('StatusWord', 0x6041),
('OperationMode', 0x6060),
):
extend_enum(Index, name, value)
self.assertEqual(len(Index), 5)
self.assertEqual(list(Index), [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode])
self.assertEqual(Index.DeviceType.value, 0x1000)
self.assertEqual(Index.StatusWord.value, 0x6041)
@unittest.skipUnless(StdlibIntEnum, 'Stdlib IntEnum not available')
def test_extend_multi_init_stdlib(self):
class HTTPStatus(StdlibIntEnum):
def __new__(cls, value, phrase, description):
obj = int.__new__(cls, value)
obj._value_ = value
obj.phrase = phrase
obj.description = description
return obj
CONTINUE = 100, 'Continue', 'Request received, please continue'
SWITCHING_PROTOCOLS = 101, 'Switching Protocols', 'Switching to new protocol; obey Upgrade header'
PROCESSING = 102, 'Processing', ''
length = 3
extend_enum(HTTPStatus, 'BAD_SPAM', 513, 'Too greasy', 'for a train')
extend_enum(HTTPStatus, 'BAD_EGGS', 514, 'Too green', '')
self.assertEqual(len(HTTPStatus), length+2)
self.assertEqual(
list(HTTPStatus)[-2:],
[HTTPStatus.BAD_SPAM, HTTPStatus.BAD_EGGS],
)
self.assertEqual(HTTPStatus.BAD_SPAM.value, 513)
self.assertEqual(HTTPStatus.BAD_SPAM.name, 'BAD_SPAM')
self.assertEqual(HTTPStatus.BAD_SPAM.phrase, 'Too greasy')
self.assertEqual(HTTPStatus.BAD_SPAM.description, 'for a train')
self.assertEqual(HTTPStatus.BAD_EGGS.value, 514)
self.assertEqual(HTTPStatus.BAD_EGGS.name, 'BAD_EGGS')
self.assertEqual(HTTPStatus.BAD_EGGS.phrase, 'Too green')
self.assertEqual(HTTPStatus.BAD_EGGS.description, '')
@unittest.skipUnless(StdlibFlag, 'Stdlib Flag not available')
def test_extend_flag_stdlib(self):
class Color(StdlibFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(8) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, 8)
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, StdlibFlag))
@unittest.skipUnless(StdlibFlag, 'Stdlib Flag not available')
def test_extend_flag_backwards_stdlib(self):
class Color(StdlibFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, StdlibFlag))
#
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(16) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value,16)
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 32)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(32), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
@unittest.skipUnless(StdlibIntFlag, 'Stdlib IntFlag not available')
def test_extend_intflag_stdlib(self):
class Color(StdlibIntFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(8) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, 8)
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, StdlibFlag))
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, 16)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(16), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
@unittest.skipUnless(StdlibIntFlag, 'Stdlib IntFlag not available')
def test_extend_intflag_backwards_stdlib(self):
class Color(StdlibIntFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
if pyver >= PY3_11:
# flags make more sense in 3.11
length = 5
MAGENTA = 8
mauve = 16
else:
length = 7
MAGENTA = 16
mauve = 32
extend_enum(Color, 'PURPLE', 11)
self.assertTrue(Color(11) is Color.PURPLE)
self.assertTrue(isinstance(Color.PURPLE, Color))
self.assertEqual(Color.PURPLE.value, 11)
self.assertTrue(issubclass(Color, StdlibFlag))
#
extend_enum(Color, 'MAGENTA')
self.assertTrue(Color(MAGENTA) is Color.MAGENTA)
self.assertTrue(isinstance(Color.MAGENTA, Color))
self.assertEqual(Color.MAGENTA.value, MAGENTA)
#
extend_enum(Color, 'mauve')
self.assertEqual(Color.mauve.name, 'mauve')
self.assertEqual(Color.mauve.value, mauve)
self.assertTrue(Color.mauve in Color)
self.assertEqual(Color(mauve), Color.mauve)
self.assertEqual(Color['mauve'], Color.mauve)
self.assertEqual(len(Color), length, list(Color))
@unittest.skipUnless(StdlibStrEnum, 'Stdlib StrEnum not available')
def test_extend_strenum_stdlib(self):
class Color(StrEnum):
RED = auto()
GREEN = auto()
BLUE = auto()
extend_enum(Color, 'BLACK')
self.assertEqual(Color.BLACK.name, 'BLACK')
self.assertEqual(Color.BLACK.value, 'black')
self.assertEqual(len(Color), 4)
if __name__ == '__main__':
raise RuntimeError("'test_v3.py' should not be run by itself; it's included in 'test.py'")
"""Use this module to apply a number of blending modes to a background and foreground image
"""
"""Provide blending functions and types.
Adapted from https://github.com/addisonElliott/pypdn/blob/master/pypdn/reader.py
and https://gitlab.com/inklabapp/pyora/-/blob/master/pyora/BlendNonSep.py
MIT License Copyright (c) 2020 FredHappyface
Credits to:
MIT License Copyright (c) 2019 Paul Jewell
For implementing blending from the Open Raster Image Spec
MIT License Copyright (c) 2018 Addison Elliott
For implementing blending from Paint.NET
MIT License Copyright (c) 2017 pashango
For implementing a number of blending functions used by other popular image
editors
"""
from __future__ import annotations
import warnings
import numpy as np
from PIL import Image
from .blendtype import BlendType
def normal(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.NORMAL."""
del background # we don't care about this
return foreground
def multiply(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.MULTIPLY."""
return np.clip(foreground * background, 0.0, 1.0)
def additive(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.ADDITIVE."""
return np.minimum(background + foreground, 1.0)
def colourburn(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.COLOURBURN."""
with np.errstate(divide="ignore"):
return np.where(
foreground != 0.0, np.maximum(1.0 - ((1.0 - background) / foreground), 0.0), 0.0
)
def colourdodge(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.COLOURDODGE."""
with np.errstate(divide="ignore"):
return np.where(foreground != 1.0, np.minimum(background / (1.0 - foreground), 1.0), 1.0)
def reflect(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.REFLECT."""
with np.errstate(divide="ignore"):
return np.where(
foreground != 1.0, np.minimum((background ** 2) / (1.0 - foreground), 1.0), 1.0
)
def glow(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.GLOW."""
with np.errstate(divide="ignore"):
return np.where(
background != 1.0, np.minimum((foreground ** 2) / (1.0 - background), 1.0), 1.0
)
def overlay(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.OVERLAY."""
return np.where(
background < 0.5,
2 * background * foreground,
1.0 - (2 * (1.0 - background) * (1.0 - foreground)),
)
def difference(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.DIFFERENCE."""
return np.abs(background - foreground)
def negation(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.NEGATION."""
return np.maximum(background - foreground, 0.0)
def lighten(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.LIGHTEN."""
return np.maximum(background, foreground)
def darken(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.DARKEN."""
return np.minimum(background, foreground)
def screen(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.SCREEN."""
return background + foreground - background * foreground
def xor(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.XOR."""
# XOR requires int values so convert to uint8
with warnings.catch_warnings():
warnings.simplefilter("ignore")
return imageIntToFloat(imageFloatToInt(background) ^ imageFloatToInt(foreground))
def softlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.SOFTLIGHT."""
return (1.0 - background) * background * foreground + background * (
1.0 - (1.0 - background) * (1.0 - foreground)
)
def hardlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.HARDLIGHT."""
return np.where(
foreground < 0.5,
np.minimum(background * 2 * foreground, 1.0),
np.minimum(1.0 - ((1.0 - background) * (1.0 - (foreground - 0.5) * 2.0)), 1.0),
)
def grainextract(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.GRAINEXTRACT."""
return np.clip(background - foreground + 0.5, 0.0, 1.0)
def grainmerge(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.GRAINMERGE."""
return np.clip(background + foreground - 0.5, 0.0, 1.0)
def divide(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.DIVIDE."""
return np.minimum((256.0 / 255.0 * background) / (1.0 / 255.0 + foreground), 1.0)
def pinlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.PINLIGHT."""
return np.minimum(background, 2 * foreground) * (foreground < 0.5) + np.maximum(
background, 2 * (foreground - 0.5)
) * (foreground >= 0.5)
def vividlight(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.VIVIDLIGHT."""
return colourburn(background, foreground * 2) * (foreground < 0.5) + colourdodge(
background, 2 * (foreground - 0.5)
) * (foreground >= 0.5)
def exclusion(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.EXCLUSION."""
return background + foreground - (2.0 * background * foreground)
def _lum(colours: np.ndarray) -> np.ndarray:
"""Luminosity.
:param colours: x by x by 3 matrix of rgb color components of pixels
:return: x by x by 3 matrix of luminosity of pixels
"""
return (colours[:, :, 0] * 0.299) + (colours[:, :, 1] * 0.587) + (colours[:, :, 2] * 0.114)
def _setLum(originalColours: np.ndarray, newLuminosity: np.ndarray) -> np.ndarray:
"""Set a new luminosity value for the matrix of color."""
_colours = originalColours.copy()
_luminosity = _lum(_colours)
deltaLum = newLuminosity - _luminosity
_colours[:, :, 0] += deltaLum
_colours[:, :, 1] += deltaLum
_colours[:, :, 2] += deltaLum
_luminosity = _lum(_colours)
_minColours = np.min(_colours, axis=2)
_MaxColours = np.max(_colours, axis=2)
for i in range(_colours.shape[0]):
for j in range(_colours.shape[1]):
_colour = _colours[i][j]
newLuminosity = _luminosity[i, j]
minColour = _minColours[i, j]
maxColour = _MaxColours[i, j]
if minColour < 0:
_colours[i][j] = newLuminosity + (
((_colour - newLuminosity) * newLuminosity) / (newLuminosity - minColour)
)
if maxColour > 1:
_colours[i][j] = newLuminosity + (
((_colour - newLuminosity) * (1 - newLuminosity)) / (maxColour - newLuminosity)
)
return _colours
def _sat(colours: np.ndarray) -> np.ndarray:
"""Saturation.
:param colours: x by x by 3 matrix of rgb color components of pixels
:return: int of saturation of pixels
"""
return np.max(colours, axis=2) - np.min(colours, axis=2)
def _setSat(originalColours: np.ndarray, newSaturation: np.ndarray) -> np.ndarray:
"""Set a new saturation value for the matrix of color.
The current implementation cannot be vectorized in an efficient manner,
so it is very slow,
O(m*n) at least. This might be able to be improved with openCL if that is
the direction that the lib takes.
:param c: x by x by 3 matrix of rgb color components of pixels
:param s: int of the new saturation value for the matrix
:return: x by x by 3 matrix of luminosity of pixels
"""
_colours = originalColours.copy()
for i in range(_colours.shape[0]):
for j in range(_colours.shape[1]):
_colour = _colours[i][j]
minI = 0
midI = 1
maxI = 2
if _colour[midI] < _colour[minI]:
minI, midI = midI, minI
if _colour[maxI] < _colour[midI]:
midI, maxI = maxI, midI
if _colour[midI] < _colour[minI]:
minI, midI = midI, minI
if _colour[maxI] - _colour[minI] > 0.0:
_colours[i][j][midI] = ((_colour[midI] - _colour[minI]) * newSaturation[i, j]) / (
_colour[maxI] - _colour[minI]
)
_colours[i][j][maxI] = newSaturation[i, j]
else:
_colours[i][j][midI] = 0
_colours[i][j][maxI] = 0
_colours[i][j][minI] = 0
return _colours
def hue(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.HUE."""
return _setLum(_setSat(foreground, _sat(background)), _lum(background))
def saturation(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.SATURATION."""
return _setLum(_setSat(background, _sat(foreground)), _lum(background))
def colour(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.COLOUR."""
return _setLum(foreground, _lum(background))
def luminosity(background: np.ndarray, foreground: np.ndarray) -> np.ndarray:
"""BlendType.LUMINOSITY."""
return _setLum(background, _lum(foreground))
def destin(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""'clip' composite mode.
All parts of 'layer above' which are alpha in 'layer below' will be made
also alpha in 'layer above'
(to whatever degree of alpha they were)
Destination which overlaps the source, replaces the source.
Fa = 0; Fb = αs
co = αb x Cb x αs
αo = αb x αs
"""
del foregroundColour # Not used by function
outAlpha = backgroundAlpha * foregroundAlpha
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((backgroundAlpha * foregroundAlpha)[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def destout(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""Reverse 'Clip' composite mode.
All parts of 'layer below' which are alpha in 'layer above' will be made
also alpha in 'layer below'
(to whatever degree of alpha they were)
"""
del foregroundColour # Not used by function
outAlpha = backgroundAlpha * (1 - foregroundAlpha)
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((backgroundAlpha * (1 - foregroundAlpha))[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def destatop(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""Place the layer below above the 'layer above' in places where the 'layer above' exists...
where 'layer below' does not exist, but 'layer above' does, place 'layer-above'
"""
outAlpha = (foregroundAlpha * (1 - backgroundAlpha)) + (backgroundAlpha * foregroundAlpha)
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((foregroundAlpha * (1 - backgroundAlpha))[:, :, None], foregroundColour)
+ np.multiply((backgroundAlpha * foregroundAlpha)[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def srcatop(
backgroundAlpha: np.ndarray,
foregroundAlpha: np.ndarray,
backgroundColour: np.ndarray,
foregroundColour: np.ndarray,
):
"""Place the layer below above the 'layer above' in places where the 'layer above' exists."""
outAlpha = (foregroundAlpha * backgroundAlpha) + (backgroundAlpha * (1 - foregroundAlpha))
with np.errstate(divide="ignore", invalid="ignore"):
outRGB = np.divide(
np.multiply((foregroundAlpha * backgroundAlpha)[:, :, None], foregroundColour)
+ np.multiply((backgroundAlpha * (1 - foregroundAlpha))[:, :, None], backgroundColour),
outAlpha[:, :, None],
)
return outRGB, outAlpha
def imageIntToFloat(image: np.ndarray) -> np.ndarray:
"""Convert a numpy array representing an image to an array of floats.
Args:
image (np.ndarray): numpy array of ints
Returns:
np.ndarray: numpy array of floats
"""
return image / 255
def imageFloatToInt(image: np.ndarray) -> np.ndarray:
"""Convert a numpy array representing an image to an array of ints.
Args:
image (np.ndarray): numpy array of floats
Returns:
np.ndarray: numpy array of ints
"""
return (image * 255).astype(np.uint8)
def blend(background: np.ndarray, foreground: np.ndarray, blendType: BlendType) -> np.ndarray:
"""Blend pixels.
Args:
background (np.ndarray): background
foreground (np.ndarray): foreground
blendType (BlendType): the blend type
Returns:
np.ndarray: new array representing the image
background: np.ndarray,
foreground: np.ndarray and the return are in the form
[[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]
...
[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
...
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]
...
[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]]
"""
blendLookup = {
BlendType.NORMAL: normal,
BlendType.MULTIPLY: multiply,
BlendType.COLOURBURN: colourburn,
BlendType.COLOURDODGE: colourdodge,
BlendType.REFLECT: reflect,
BlendType.OVERLAY: overlay,
BlendType.DIFFERENCE: difference,
BlendType.LIGHTEN: lighten,
BlendType.DARKEN: darken,
BlendType.SCREEN: screen,
BlendType.SOFTLIGHT: softlight,
BlendType.HARDLIGHT: hardlight,
BlendType.GRAINEXTRACT: grainextract,
BlendType.GRAINMERGE: grainmerge,
BlendType.DIVIDE: divide,
BlendType.HUE: hue,
BlendType.SATURATION: saturation,
BlendType.COLOUR: colour,
BlendType.LUMINOSITY: luminosity,
BlendType.XOR: xor,
BlendType.NEGATION: negation,
BlendType.PINLIGHT: pinlight,
BlendType.VIVIDLIGHT: vividlight,
BlendType.EXCLUSION: exclusion,
}
if blendType not in blendLookup:
return normal(background, foreground)
return blendLookup[blendType](background, foreground)
def blendLayers(
background: Image.Image,
foreground: Image.Image,
blendType: BlendType | tuple[str, ...],
opacity: float = 1.0,
) -> Image.Image:
"""Blend layers using numpy array.
Args:
background (Image.Image): background layer
foreground (Image.Image): foreground layer (must be same size as background)
blendType (BlendType): The blendtype
opacity (float): The opacity of the foreground image
Returns:
Image.Image: combined image
"""
# Convert the Image.Image to a numpy array
npForeground: np.ndarray = imageIntToFloat(np.array(foreground.convert("RGBA")))
npBackground: np.ndarray = imageIntToFloat(np.array(background.convert("RGBA")))
# Get the alpha from the layers
backgroundAlpha = npBackground[:, :, 3]
foregroundAlpha = npForeground[:, :, 3] * opacity
combinedAlpha = backgroundAlpha * foregroundAlpha
# Get the colour from the layers
backgroundColor = npBackground[:, :, 0:3]
foregroundColor = npForeground[:, :, 0:3]
# Some effects require alpha
alphaFunc = {
BlendType.DESTIN: destin,
BlendType.DESTOUT: destout,
BlendType.SRCATOP: srcatop,
BlendType.DESTATOP: destatop,
}
if blendType in alphaFunc:
return Image.fromarray(
imageFloatToInt(
np.clip(
np.dstack(
alphaFunc[blendType](
backgroundAlpha, foregroundAlpha, backgroundColor, foregroundColor
)
),
a_min=0,
a_max=1,
)
)
)
# Get the colours and the alpha for the new image
colorComponents = (
(backgroundAlpha - combinedAlpha)[:, :, None] * backgroundColor
+ (foregroundAlpha - combinedAlpha)[:, :, None] * foregroundColor
+ combinedAlpha[:, :, None] * blend(backgroundColor, foregroundColor, blendType)
)
alphaComponent = backgroundAlpha + foregroundAlpha - combinedAlpha
return Image.fromarray(
imageFloatToInt(np.clip(np.dstack((colorComponents, alphaComponent)), a_min=0, a_max=1))
)
"""Specify supported blend types."""
from __future__ import annotations
from aenum import MultiValueEnum
class BlendType(str, MultiValueEnum):
"""Specify supported blend types.
NORMAL = "bm:normal", "normal"
MULTIPLY = "bm:multiply", "multiply"
ADDITIVE = "bm:additive", "additive"
COLOURBURN = "bm:colourburn", "colourburn"
COLOURDODGE = "bm:colourdodge", "colourdodge"
REFLECT = "bm:reflect", "reflect"
GLOW = "bm:glow", "glow"
OVERLAY = "bm:overlay", "overlay"
DIFFERENCE = "bm:difference", "difference"
NEGATION = "bm:negation", "negation"
LIGHTEN = "bm:lighten", "lighten"
DARKEN = "bm:darken", "darken"
SCREEN = "bm:screen", "screen"
XOR = "bm:xor", "xor"
SOFTLIGHT = "bm:softlight", "softlight"
HARDLIGHT = "bm:hardlight", "hardlight"
GRAINEXTRACT = "bm:grainextract", "grainextract"
GRAINMERGE = "bm:grainmerge", "grainmerge"
DIVIDE = "bm:divide", "divide"
HUE = "bm:hue", "hue"
SATURATION = "bm:saturation", "saturation"
COLOUR = "bm:colour", "colour"
LUMINOSITY = "bm:luminosity", "luminosity"
PINLIGHT = "bm:pinlight", "pinlight"
VIVIDLIGHT = "bm:vividlight", "vividlight"
EXCLUSION = "bm:exclusion", "exclusion"
DESTIN = "bm:destin", "destin"
DESTOUT = "bm:destout", "destout"
SRCATOP = "bm:srcatop", "srcatop"
DESTATOP = "bm:destatop", "destatop"
"""
NORMAL = "bm:normal", "normal"
MULTIPLY = "bm:multiply", "multiply"
ADDITIVE = "bm:additive", "additive"
COLOURBURN = "bm:colourburn", "colourburn"
COLOURDODGE = "bm:colourdodge", "colourdodge"
REFLECT = "bm:reflect", "reflect"
GLOW = "bm:glow", "glow"
OVERLAY = "bm:overlay", "overlay"
DIFFERENCE = "bm:difference", "difference"
NEGATION = "bm:negation", "negation"
LIGHTEN = "bm:lighten", "lighten"
DARKEN = "bm:darken", "darken"
SCREEN = "bm:screen", "screen"
XOR = "bm:xor", "xor"
SOFTLIGHT = "bm:softlight", "softlight"
HARDLIGHT = "bm:hardlight", "hardlight"
GRAINEXTRACT = "bm:grainextract", "grainextract"
GRAINMERGE = "bm:grainmerge", "grainmerge"
DIVIDE = "bm:divide", "divide"
HUE = "bm:hue", "hue"
SATURATION = "bm:saturation", "saturation"
COLOUR = "bm:colour", "colour"
LUMINOSITY = "bm:luminosity", "luminosity"
PINLIGHT = "bm:pinlight", "pinlight"
VIVIDLIGHT = "bm:vividlight", "vividlight"
EXCLUSION = "bm:exclusion", "exclusion"
DESTIN = "bm:destin", "destin"
DESTOUT = "bm:destout", "destout"
SRCATOP = "bm:srcatop", "srcatop"
DESTATOP = "bm:destatop", "destatop"
"""Do stuff to images to prepare them.
"""
from __future__ import annotations
import warnings
from deprecation import deprecated
from PIL import Image
@deprecated(deprecated_in="2021.1", removed_in="", details="use renderWAlphaOffset")
def rasterImageOA( # pylint:disable=missing-function-docstring
image: Image.Image, size: tuple[int, int], alpha: float = 1.0, offsets: tuple[int, int] = (0, 0)
) -> Image.Image:
warnings.warn(
"Call to deprecated function rasterImageOA.", category=DeprecationWarning, stacklevel=2
)
return renderWAlphaOffset(image, size, alpha, offsets)
@deprecated(deprecated_in="2021.1", removed_in="", details="use renderWAlphaOffset")
def rasterImageOffset( # pylint:disable=missing-function-docstring
image: Image.Image, size: tuple[int, int], offsets: tuple[int, int] = (0, 0)
) -> Image.Image:
warnings.warn(
"Call to deprecated function rasterImageOffset.", category=DeprecationWarning, stacklevel=2
)
return renderWAlphaOffset(image, size, 1, offsets)
def renderWAlphaOffset(
image: Image.Image, size: tuple[int, int], alpha: float = 1.0, offsets: tuple[int, int] = (0, 0)
) -> Image.Image:
"""Render an image with offset and alpha to a given size.
Args:
image (Image.Image): pil image to draw
size (tuple[int, int]): width, height as a tuple
alpha (float, optional): alpha transparency. Defaults to 1.0.
offsets (tuple[int, int], optional): x, y offsets as a tuple.
Defaults to (0, 0).
Returns:
Image.Image: new image
"""
imageOffset = Image.new("RGBA", size)
imageOffset.paste(image.convert("RGBA"), offsets, image.convert("RGBA"))
return Image.blend(Image.new("RGBA", size), imageOffset, alpha)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment