An item is a persisted object with one or more attributes that make up a schema.
Axiom comes with the usual suspects of attributes:
It also comes with a few slightly more advanced ones:
All of these will be covered throughout the book.
Creating an item class is done by subclassing axiom.item.Item, and assigning at least one attribute from axiom.attributes to a name in the class body:
from axiom import attributes, item
class Person(item.Item):
"""
A person.
"""
name = attributes.text()
You can then create instances of that item by calling the Item class and specifying the attributes as keyword arguments. You can access the attributes on the item just like regular attributes:
>>> alice = Person(name=u"Alice")
>>> assert alice.name == u"Alice"
Axiom item attributes are not just statically typed but also strongly typed. For example, you can’t assign a bytestring to a text attribute:
>>> Person(name="a bytestring")
Traceback (most recent call last):
...
ConstraintError: attribute [Person.name = text()] must be (unicode string without NULL bytes); not 'str'
>>> alice = Person(name=u"Alice")
>>> alice.name = "another bytestring"
Traceback (most recent call last):
...
ConstraintError: attribute [Person.name = text()] must be (unicode string without NULL bytes); not 'str'
You’re allowed to not specify an attribute, which sets it to None:
>>> anonymous = Person()
>>> assert anonymous.name is None
If you don’t want to allow None as a value, set allowNone=False on the attribute:
from axiom import attributes, item
class Coin(item.Item):
"""
A coin.
"""
# attributes.money is the same thing as point4decimal
value = attributes.money(allowNone=False)
>>> Coin()
Traceback (most recent call last):
...
TypeError: attribute [Coin.value = money()] must not be None
>>> quarter = Coin(value=decimal.Decimal("0.25"))
You can also have default values. For example, we could have a petting zoo with bunnies, and bunnies start out having been petted zero times:
from axiom import attributes, item
class Bunny(item.Item):
"""
A bunny in a petting zoo.
"""
timesPetted = attributes.integer(default=0)
>>> thumper = Bunny()
>>> assert thumper.timesPetted == 0 # Aww :-(
>>> thumper.timesPetted += 1
>>> assert thumper.timesPetted == 1 # Yay :-)
Sometimes a default value isn’t enough, and you need a default value factory that gets called when the item gets created. Let’s recite the alphabet:
import string
from axiom import attributes, item
letters = string.ascii_lowercase.decode("ascii")
class Letter(item.Item):
"""
A letter in the alphabet being recited.
"""
value = attributes.text(defaultFactory=iter(letters).next)
# This creates an iterator over the list, and takes its ``next`` method.
# Calling this method will produce the letters in sequence.
>>> a, b, c = Letter(), Letter(), Letter()
>>> assert a.value == "a"
>>> assert b.value == "b"
>>> assert c.value == "c"
Axiom item classes have a “type name”, which is the unique name used to identify instances of it in an Axiom store, and distinguish them from other item classes. If you don’t explicitly specify a type name, one is automatically generated based on the name of the class and the module it’s in. These are put into lower case and concatenated with underscores. This is done because the type name will be a SQLite table name, and therefore has to be a valid SQL identifier.
>>> Bunny.typeName
'bunny_bunny'
The module is called bunny. In this documentation, there are no modules above it; if your module is in a package, it could be something like 'top_mid_leaf_class'.
There are some benefits to specifying an explicit type name. For example, you will be able to change the structure of your package, or move items to different, independent packages.
You can specify the type name using the typeName class attribute:
>>> class MobileBunny(item.Item):
... typeName = "mobile_bunny"
... timesPetted = attributes.integer()
>>> MobileBunny.typeName
'mobile_bunny'
Note
There’s no ubiquitous standard for type names. These will map to SQL tables, so whatever your preference is for those might win out.