Examples¶
Enumerations in Python can provide rich class based interfaces, well suited
to many scenarios. A real world example is presented here that leverages
IntegerChoices
integration with enum-properties to encapsulate more
information and get our enum to do more work.
Map Box Style¶
Mapbox is a leading web mapping platform. It comes with a handful of default map styles. An enumeration is a natural choice to represent these styles but the styles are complicated by the fact that they are versioned and that when used as a parameter in the mapbox API they are in a URI format that is overly verbose for a human friendly user interface.
Each mapbox style enumeration is therefore composed of 4 primary properties. A a human friendly label for the style, a name slug used in the URI, a version number for the style and the full URI specification of the style. We might implement our style enumeration like so:
import typing as t
from django.db import models
from django_enum import EnumField
from enum_properties import IntEnumProperties, Symmetric
class Map(models.Model):
class MapBoxStyle(IntEnumProperties):
"""
https://docs.mapbox.com/api/maps/styles/
"""
_symmetric_builtins_ = ['name', 'uri']
label: t.Annotated[str, Symmetric()]
slug: t.Annotated[str, Symmetric(case_fold=True)]
version: int
# name value label slug version
STREETS = 1, 'Streets', 'streets', 12
OUTDOORS = 2, 'Outdoors', 'outdoors', 12
LIGHT = 3, 'Light', 'light', 11
DARK = 4, 'Dark', 'dark', 11
SATELLITE = 5, 'Satellite', 'satellite', 9
SATELLITE_STREETS = 6, 'Satellite Streets', 'satellite-streets', 12
NAVIGATION_DAY = 7, 'Navigation Day', 'navigation-day', 1
NAVIGATION_NIGHT = 8, 'Navigation Night', 'navigation-night', 1
@property
def uri(self):
return f'mapbox://styles/mapbox/{self.slug}-v{self.version}'
def __str__(self):
return self.uri
style = EnumField(MapBoxStyle, default=MapBoxStyle.STREETS)
We’ve used a small integer as the value of the enumeration to save storage
space. We’ve also added a symmetric case insensitive slug and a non-symmetric
version property. We do not need to specify the label property because we’re
inheriting from Django’s Choices type which provides a label
property as
the second element in the value tuple.
The version numbers will increment over time, but we’re only concerned with the
most recent versions, so we’ll increment their values in this enumeration as
they change. Any version number updates exist only in code and will be picked
up as those persisted values are re-instantiated as MapBoxStyle
enumerations.
The last property we’ve added is the uri
property. We’ve added it as
concrete property on the class because it can be created from the slug and
version. We could have specified it in the value tuple but that would be very
verbose and less
DRY. To make this
property symmetric we added it to the _symmetric_builtins_
list.
We can use our enumeration like so:
map = Map.objects.create()
assert map.style.uri == 'mapbox://styles/mapbox/streets-v11'
# uri's are symmetric
map.style = 'mapbox://styles/mapbox/light-v10'
map.full_clean()
assert map.style is Map.MapBoxStyle.LIGHT
assert map.style == 3
assert map.style == 'light'
# so are labels (also case insensitive)
map.style = 'satellite streets'
map.full_clean()
assert map.style == Map.MapBoxStyle.SATELLITE_STREETS
# when used in API calls (coerced to strings) - they "do the right thing"
assert str(map.style) == 'mapbox://styles/mapbox/satellite-streets-v11'