So far, we've created items, but never actually put them in a database. Axiom "databases" are called stores.
To create a store, just call axiom.store.Store()
. By default, that
will create an in-memory store. Obviously, in-memory stores aren't
persisted.
.. testsetup:: from axiom.store import Store
>>> inMemoryStore = Store()
To create a persisted store, pass either a path as a string, or a
twisted.python.filepath.FilePath
to the store constructor:
>>> persistedStore = Store("mystore")
You can either set an item's store when you create it, or by setting its store attribute later:
.. testsetup:: from person import Person
>>> store = Store()
>>> alice = Person(store=store, name=u"Alice")
>>> bob = Person(name=u"Bob")
>>> bob.store = store
>>> assert alice.store is bob.store is store
Once we have some objects in a store, we can query them. Simple queries take the item class you want to get instances of, and the usual querying suspects:
- comparisons (filters)
- limits and offsets
- sorting
The result returned by a query is a generator, so we'll call list
on it to show its contents.
>>> people = list(store.query(Person, sort=Person.name.ascending))
>>> assert people == [alice, bob]
>>> person, = store.query(Person, sort=Person.name.ascending, limit=1)
>>> assert person is alice
>>> person, = store.query(Person, sort=Person.name.ascending, limit=1, offset=1)
>>> assert person is bob
Since finding exactly one object is such a common pattern, there's an
easier shorthand for it called findUnique
.
>>> person = store.findUnique(Person, Person.name == u"Alice")
>>> assert person is alice
Since you're expecting to find a single object, findUnique
will
raise an exception if there is more than one:
>>> secondAlice = Person(store=store, name=u"Alice")
>>> store.findUnique(Person, Person.name == u"Alice")
Traceback (most recent call last):
...
DuplicateUniqueItem: (person.Person.name = u'Alice', [...])
Similarly, it will raise an exception if there are none:
>>> store.findUnique(Person, Person.name == u"Nobody")
Traceback (most recent call last):
...
ItemNotFound: person.Person.name = u'Nobody'
Sometimes, you want to create an object if it does not exist, or
update an object if it does. Axiom calls this findOrCreate
.
>>> newAlice = store.findOrCreate(Person, name=u"Alice")
>>> assert newAlice is alice, "returns the existing object"
>>> charlie = store.findOrCreate(Person, name=u"Charlie")
Often, you only want part of the data instead of the entire stored
item using getColumn
. (This will once again produce a lazy
iterator, so we will use list
to consume it.)
.. testsetup:: from bunny import Bunny
>>> bugs = Bunny(store=store, timesPetted=1)
>>> fluffy = Bunny(store=store, timesPetted=2)
>>> thumper = Bunny(store=store, timesPetted=3)
>>> query = store.query(Bunny, sort=Bunny.timesPetted.ascending)
>>> assert list(query.getColumn("timesPetted")) == [1, 2, 3]
You can sum over all the attributes in a store:
>>> assert store.sum(Bunny.timesPetted) == 6
Usually, it's more useful to do it over a query than over all items:
>>> query = store.query(Bunny, Bunny.timesPetted > 1).getColumn("timesPetted")
>>> assert query.sum() == 5
While you could also do this by using Python's builtin sum
function to get the same result. The principal difference is that with
the query method, the summing is actually done inside the database.
You can also count how many items there are in a query.
>>> assert query.count() == 2
You can also average values:
>>> assert query.average() == 2.5
Notice how we're re-using the query object. Queries are lazy: they're only executed when you actually need an item. For example, if an item is created or modified so that it suddenly is affected by the query, you get the appropriate result:
>>> bugs.timesPetted += 1
>>> assert query.count() == 3