Enum types

dbzero.enum: Typed Enumerations

dbzero.enum provides a way to create strongly-typed, persistent enumerations. Think of them as special sets of constants that are more robust than using simple strings. They help prevent typos and make your code clearer.

You can create enums in two main ways: using the functional API or a class decorator.

Creating and Accessing Enums

Functional API

The most direct way is with the dbzero.enum() function. You provide a name for the enum type and a list of string values.

# Create an enum type named "Colors"
Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
 
# The enum type itself is an object
assert Colors is not None

Decorator API

For a more declarative style, you can use the @dbzero.enum decorator on a class.

@db0.enum(values = ["RED", "GREEN", "BLUE"])
class ColorsEnum:
  pass
 
# Now ColorsEnum works just like an enum type
assert ColorsEnum.RED is not None
💡

Once an enum type like "Colors" is defined, you cannot redefine it with a different set of values. This ensures type consistency throughout your application.

# This will raise an exception because "Colors" already exists
with pytest.raises(Exception):
  db0.enum("Colors", ["ONE", "TWO", "THREE"])

Accessing Values

You can retrieve individual enum values in several ways:

  • By attribute: Colors.RED
  • By string key: Colors['RED']
  • By integer index: Colors[0] (The order from creation is preserved)
Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
 
# Access by attribute
assert Colors.RED is not None
 
# Access by string key
assert Colors['GREEN'] is Colors.GREEN
 
# Access by index
assert Colors[2] is Colors.BLUE
 
# Trying to access a non-existent value will fail
with pytest.raises(Exception):
  Colors.PURPLE

Using Enum Values

dbzero.enum values are first-class citizens in dbzero and can be used in various ways, including as tags, object attributes, and dictionary keys.

As Tags

Enums are excellent for tagging and categorizing objects. You can easily add, remove, and find objects by their enum tags.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
obj_1 = MemoTestClass(1)
 
# Add an enum value as a tag
db0.tags(obj_1).add(Colors.RED)
 
# Find all objects tagged with Colors.RED
results = db0.find(Colors.RED)
assert len(list(results)) == 1
 
# Remove the tag
db0.tags(obj_1).remove(Colors.RED)
assert len(list(db0.find(Colors.RED))) == 0

Type Safety

A key feature of dbzero.enum is type safety. An enum value is distinct from a plain string from a value in another enum with the same name.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
Palette = db0.enum("Palette", ["RED", "GREEN", "BLUE"])
 
db0.tags(MemoTestClass(value=1)).add(Colors.RED)
db0.tags(MemoTestClass(value=2)).add(Palette.RED)
db0.tags(MemoTestClass(value=3)).add("RED")
 
# Each find query correctly distinguishes the type
assert set(x.value for x in db0.find(Colors.RED)) == {1}
assert set(x.value for x in db0.find(Palette.RED)) == {2}
assert set(x.value for x in db0.find("RED")) == {3}

As Object Members & Dictionary Keys

You can store enum values directly as members of your memo objects or use them as keys in both dbzero.dict and standard Python dictionaries.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
 
# As an object member
obj = MemoTestClass(value=Colors.RED)
assert obj.value == Colors.RED
 
# As a db0.dict key
dict_db0 = db0.dict({Colors.RED: "red value"})
assert dict_db0[Colors.RED] == "red value"
 
# As a standard Python dict key
dict_py = {Colors.RED: "red value"}
assert dict_py[Colors.RED] == "red value"

API and Methods

Listing All Values

The .values() method returns an ordered list of all values in the enum.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
all_colors = Colors.values()
 
assert len(all_colors) == 3
assert Colors.RED in all_colors

str and repr

Enum values have clear string and representation formats.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
 
# str() returns the simple name
assert str(Colors.RED) == "RED"
 
# repr() returns a detailed, unique representation
assert repr(Colors.RED) == "<EnumValue Colors.RED>"

Type Checking

You can check if an object is an enum type or an enum value.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
 
assert db0.is_enum(Colors)
assert not db0.is_enum(Colors.RED)
 
assert db0.is_enum_value(Colors.RED)
assert not db0.is_enum_value(Colors)

Hashing

Enum values are hashable, which allows them to be used in sets and as dictionary keys.

assert db0.hash(Colors.RED) != db0.hash(Colors.GREEN)

Persistence, Prefixes, and Default Arguments

dbzero.enum values are seamlessly integrated with dbzero's persistence and prefix system.

Enums and Prefixes

An enum value is materialized within the currently active dbzero prefix. However, dbzero is smart enough to treat enum values with the same type and name as equal, even if they originate from different prefixes.

# val_1 is created in the default prefix
val_1 = ColorsEnum.RED
 
# Switch to a new prefix
db0.open("some-other-prefix", "rw")
 
# val_2 is created in the "some-other-prefix"
val_2 = ColorsEnum.RED
 
# Their underlying prefixes are different...
assert db0.get_prefix_of(val_1) != db0.get_prefix_of(val_2)
 
# ...but they are considered equal in comparisons and lookups!
assert val_1 == val_2

Default Function Arguments

An important feature is the ability to use enum values as default arguments in functions. This works even before dbzero is initialized.

When you define a function with an enum value as a default argument, Python stores a lightweight representation of that value. When you later call the function, dbzero automatically resolves this representation into the actual, materialized enum value from the currently active prefix.

# This function is defined with a default argument.
# At definition time, `dbzero` is not be initialized yet.
def get_color(color=ColorsEnum.RED):
    return color
 
db0.init(dbzero_root="/tmp/data")
db0.open("app-data")
 
# When we call it, the representation is resolved to the real enum value.
assert get_color() == ColorsEnum.RED
assert get_color(ColorsEnum.GREEN) == ColorsEnum.GREEN

Serialization

dbzero can serialize and deserialize enum values and their representations, even across different prefixes. This mechanism allows to reference enum values externally, e.g. from a HTML select tag.

Colors = db0.enum("Colors", ["RED", "GREEN", "BLUE"])
 
# Serialize a materialized enum value
bytes_val = db0.serialize(Colors.RED)
assert db0.deserialize(bytes_val) == Colors.RED
 
# This function's default argument is an EnumValueRepr
def get_repr_func(color=ColorsEnum.RED):
  return color
 
# Serialize the representation
bytes_repr = db0.serialize(get_repr_func())
 
# It deserializes back to the materialized value
assert db0.deserialize(bytes_repr) == ColorsEnum.RED