An item is a persisted object with one or more attributes that make up a schema.
Axiom comes with the usual suspects of attributes:
text
for (Unicode) textbytes
for bytesinteger
for integer valuespointXdecimal
(X varying from 1 to 10) for decimals of varying precisionieee754_double
for floating point valuestimestamp
for absolute points in time
It also comes with a few slightly more advanced ones:
reference
which allows you to store a reference to any other Axiom item (not just those of a particular type)textlist
which allows you to store lists of (Unicode) text in one fieldpath
which allows you to store both relative and absolute pathsinmemory
which allows you to store non-persisted/non-persistable state on the item instance
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:
.. literalinclude:: person.py :language: python
.. testsetup:: from person import Person
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:
.. literalinclude:: coin.py :language: python
.. testsetup:: from coin import Coin import decimal
>>> 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:
.. literalinclude:: bunny.py :language: python
.. testsetup:: from bunny import Bunny
>>> 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:
.. literalinclude:: alphabet.py :language: python
.. testsetup:: from alphabet import Letter
>>> 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:
.. testsetup:: from axiom import attributes, item
>>> 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.