base.enum

Better Named Constants

Yes, Python has an enum library, but this one is better.


At a Glance

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.


Deeper

Every part of this definition can be expanded.

Singular/Plural

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.

Option Details

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:

  • On a 1-tuple, you’re giving just the option’s human-readable name, and the value of that option is set to the first character of the name
  • On a 2-tuple, you’re giving the name and the value of that option, which we call the option’s “tag”
  • On a 3-tuple you’re giving the name, the tag, and the python-code name as well. Note the code-name will be fully uppercased, always.
  • On a 4-tuple you get to also specify an “icon” property

The icon property is a great lead-in to talking about the MyEnums object that’s created.


The Enum Object

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 enum

Those dealing with the enum as a whole are:

  • .Choices() – returns a list of [(name, tag), …]
    • this is exactly what Django expects as a choices= attribute on a field
  • .MaxTagLength() – returns an integer for how many characters are needed for the longest tag
    • works only if your tags are strings, or None
  • .Names() – returns a list of all option names
  • .Tags() – returns a list of all option tags
  • .Definitions() – returns a list of all option definitions

As well, several of the pythonic shortcuts are enabled and work like you would expect:

  • if tag in MyEnums
  • for tag in MyEnums
  • len(MyEnums)

Deepest Depths Yet

Clarifications

A few clarifications on the above:

Tags Need Not Be Strings

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 ...
))

Icons

There is nothing specifically iconic about an option’s icon attribute, it’s just another attribute that holds an arbitrary value.

Advanced Features

Some super-advanced features are also possible.

Adding to Existing Enums

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)

More Attributes On Each Option

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)

Django Template Contexts

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

Nested Enums

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.


Back to “OctoBase”