[plum.int.flag] Tutorial: Using Integer Flag Enumeration Types

The plum.int.flag subpackage provides a plum type class for packing and unpacking integer flag enumeration members. Compared to normal integers, enumerations offer better representations within the bytes summary dumps. Flag enumerations also support bitwise combinations that retain enumeration membership. This tutorial teaches how to create and use integer flag enumeration plum types.

Creating an Integer Enumeration Type

To create a plum integer flag enumeration type, subclass the Flag class (similar to subclassing IntFlag from the enum standard library module). Use arguments to control the number of bytes and byte order. For example:

>>> from plum.int.flag import Flag
>>>
>>> class Color(Flag, nbytes=2, byteorder='big'):
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     WHITE = RED | GREEN | BLUE
...

Besides having plum behaviors (discussed in next sections), the enumeration behaves as subclasses of enum.IntFlag do. For example:

>>> Color.RED == 1
True
>>> Color(1)
<Color.RED: 1>
>>> Color['RED']
<Color.RED: 1>
>>> Color.RED | Color.GREEN | Color.BLUE
<Color.WHITE: 7>

Unpacking Bytes

plum integer flag enumeration types convert bytes into an enumeration member instance when used with the various plum unpacking mechanisms. For example (using the enumeration from the previous section):

>>> from plum import unpack, Buffer
>>>
>>> # utility function
>>> unpack(Color, b'\x00\x01')
<Color.RED: 1>
>>>
>>> # class method
>>> Color.unpack(b'\x00\x01')
<Color.RED: 1>
>>>
>>> # bytes buffer
>>> with Buffer(b'\x00\x01\x00\x02') as buffer:
...     a = buffer.unpack(Color)
...     b = buffer.unpack(Color)
...
>>> a, b
(<Color.RED: 1>, <Color.GREEN: 2>)

Packing Bytes

plum integer flag enumeration types convert integers (or anything int-like) into bytes when used with the various plum packing mechanisms. For example (using the enumeration from the previous sections):

>>> from plum import pack
>>>
>>> # utility function
>>> pack(Color, Color.RED)
bytearray(b'\x00\x01')
>>>
>>> # class method
>>> Color.pack(1)
bytearray(b'\x00\x01')
>>>
>>> # instance method
>>> Color.RED.pack()
bytearray(b'\x00\x01')

Strict vs. Tolerant

Similar to enum.IntFlag standard library enumerations, plum integer flag enumerations tolerate undefined values:

>>> Color(8)
<Color.8: 8>
>>> Color.unpack(b'\x00\x08')
<Color.8: 8>
>>> Color.pack(8)
bytearray(b'\x00\x08')

No mechanism exists to force plum integer flag enumerations to limit values to the predefined enumeration members.

Inheriting Enumeration Members

To create a plum integer flag enumeration type with members inherited from another flag enumeration, subclass the Flag class and pass the enumeration with the members to copy using the source argument. For example:

>>> from enum import Flag as EnumFlag
>>> from plum.int.flag import Flag
>>>
>>> class Bit(EnumFlag):
...     """Normal flag enumeration without pack/unpack properties."""
...     ZERO = 1
...     ONE = 2
...
>>>
>>> class Bit8(Flag, nbytes=1, source=Bit):
...     """Plum enumeration with pack/unpack properties"""
...     TWO = 4  # optional additional member
...

The resulting plum flag enumeration contains members from the source plus members defined in the class body:

>>> for member in Bit8:
...     print(repr(member))
...
<Bit8.ZERO: 1>
<Bit8.ONE: 2>
<Bit8.TWO: 4>
>>>
>>> Bit8.ONE.dump()
+--------+--------+-------+-------+------+
| Offset | Access | Value | Bytes | Type |
+--------+--------+-------+-------+------+
| 0      |        | 2     | 02    | Bit8 |
|  [0]   | .zero  | False |       | bool |
|  [1]   | .one   | True  |       | bool |
|  [2]   | .two   | False |       | bool |
+--------+--------+-------+-------+------+