base.enum
Yes, Python has an enum
library, but this one is better.
An Enum is a list of named constants.
In the simplest form, you define an Enum like this:
base.Enum.Define('MyEnum', (
'First',
'Second',
'Third',
))
This defines the following for you:
MYENUM_FIRST = 'F'
MYENUM_SECOND = 'S'
MYENUM_THIRD = 'T'
These constants are introduced into the globals of the same module where you called Enum.Define()
. Additionally, a MyEnum
object derived from Enum
is also created and has many useful methods; more on that below.
Every part of this definition can be expanded.
Firstly, the name of the enum itself can be changed from MyEnum
into a tuple of singular/plural forms:
base.Enum.Define(('MYENUM', 'MyEnums'), (
...
The singular form is used as the prefix for each option’s definition. We’ve capitalized it here because it’s always capitalized when the options are added to your module’s globals. The plural form is used for the Enum subclass that we haven’t described yet.
More interestingly, each option can be a tuple of 1, 2, 3, or 4 parts:
base.Enum.Define(('MYENUM', 'MyEnums'), (
'Bare String', # name
('1-Tuple', ), # (name, )
('2-Tuple', 'two'), # (name, tag)
('3-Tuple', 'three', 'THREE'), # (name, tag, code_name)
('cat', '4-Tuple', 'four', 'FOUR') # (icon, name, tag, code_name)
))
Here, the options that are introduced look like this:
MYENUM_BARE_STRING = 'B'
MYENUM_1_TUPLE = '1'
MYENUM_2_TUPLE = 'two'
MYENUM_THREE = 'three'
MYENUM_FOUR = 'four'
Specifically:
The icon property is a great lead-in to talking about the MyEnums
object that’s created.
Each defined Enum creates an object that can map back and forth between names and tags, among other useful things. Continuing our example, the object that’s created here is called MyEnums
and it has all these methods:
MyEnums.Tag(value)
MyEnums.Name(value)
MyEnums.Icon(value)
Each of these methods takes either the human-readable name of an option or the tag of the option and yields back what you would expect:
MyEnums.Tag('two') == 'two'
MyEnums.Name('two') == '2-tuple'
MyEnums.Tag('2-tuple') == 'two'
MyEnums.Name('2-tuple') == '2-tuple'
There are several other useful functions on MyEnums
as well.
Those dealing with specific options are:
.Definition(value)
– returns a class instance with .name
, .tag
, etc. properties on it.Index(value)
– returns the numerical position of the option in the enum.Rank(value)
– returns a float [0, 1] for the relative position of the option in the enumThose dealing with the enum as a whole are:
.Choices()
– returns a list of [(name, tag), …]choices=
attribute on a field.MaxTagLength()
– returns an integer for how many characters are needed for the longest tag.Names()
– returns a list of all option names.Tags()
– returns a list of all option tags.Definitions()
– returns a list of all option definitionsAs well, several of the pythonic shortcuts are enabled and work like you would expect:
if tag in MyEnums
for tag in MyEnums
len(MyEnums)
A few clarifications on the above:
The tag of any enum option does not have to be a string.
For instance, it’s common to add a None
option to an Enum:
base.Enum.Define(('MYENUM', 'MyEnums'), (
('Undefined', None),
# ... more options ...
))
You also can define options with a totally different type for tags, such as integers:
base.Enum.Define(('STAGE', 'Stages'), (
('One', 1),
('Two', 2),
# ... more options ...
))
There is nothing specifically iconic about an option’s icon attribute, it’s just another attribute that holds an arbitrary value.
Some super-advanced features are also possible.
An Enum that has already been defined can be added to:
somemodule.MyEnum.Add('5th Option', 5, '5th')
This adds another option to an existing enum. Note that the new option is defined in the globals of the original module that the enum lives within:
assert(somemodule.MYENUM_5TH == 5)
If you want more attributes than name, tag, and icon, you can add your own by using a dictionary instead of a tuple as the option definition:
{'name': '6th Option', 'tag': 6, 'codename': '6th', 'my_extra_attr': some_instance}
This works either in the original Define(...)
of your enum as well as an Add(...)
call you do later.
You retrieve these attributes off the .Definition()
of your option:
assert(MyEnum.Definition(MYENUM_6TH).my_extra_attr == some_instance)
If you pass an Enum into your Django templates, we have some special-case logic to help out. Specifically, in template context:
MyEnums.name == tag
Definitions are also easy to retrieve:
MyEnums.Definitions.name
There is limited support for hierarchical enums. Instead of Enum.Define(...)
you would use:
MyEnum.DefineNested(
parent_option_definition,
(
sub_option_definition,
sub_option_definition,
# ... more ...
)
)
Nested option tags are simply concatenated with their parent option tags, which is probably not ideal in many cases but works perfectly fine in the one single case we wrote this to handle (a big table of filetypes). Enum “in
” testing works as expected though, where any option is considered “in” its parents.