Contents
DivmodAxiom : Reference
Item
item.Item(store=store.Store, **kw)
A class describing an item in an Axiom Store. Your subclass of axiom.item.Item may define the following special attributes:
- typeName: A string uniquely identifying this class schema in the store. If omitted, a normalised typeName will automatically be generated. If such an item class is refactored to a new module you will need to set typeName to the previous normalised value by passing the previous fully qualified class name, and passing it to axiom.item.normalize(). (See #399)
- schemaVersion: An integer denoting the schema version. If omitted, this defaults to 1. In subsequent schema versions the schemaVersion must be explicitly defined. See DivmodAxiom/Reference#Upgraders. (See #398)
You will also want to define other attributes and these must be special Axiom class attributes. A selection of standard types are already defined in the axiom.attributes module. A simple example:
from axiom import item, attributes class ShopProduct(item.Item): typeName = "ShopProduct" name = attributes.text(allowNone=False) price = attributes.integer(allowNone=False) stock = attributes.integer(default=0) def __repr__(self): return '<ShopProduct name="%s" price="%d" stock="%d">' % (self.name, self.price, self.stock)
Limitations
- Axiom Items only support one level of inheritance. You could not for example write a subclass of the ShopProduct class above. This is by design, and you are encouraged to explore object composition and adaption instead.
Store
store.Store([dbdir=None[, debug=False[, parent=None[, idInParent=None]]]])
A database in which Axiom Items can be stored. An Axiom Store can be instantiated with a dbdir parameter, in which case it will be persisted to the filesystem at the given path. Alternatively, if instantiated without a dbdir parameter, the store will exist inmemory only for the lifetime of the python process.
from axiom import store s = store.Store('/tmp/example.axiom') s = store.Store() # An inmemory store
If debug=True, the store will print out all sql commands as they are issued to the underlying Sqlite database.
Add Items to the Store
p = ShopProduct(store=s, name=u'Tea Bags', price=2)
That's all there is to it. The returned item can be treated just like any other python object. Changes made to it are automatically persisted to the store.
>>> p.name
u'Tea Bags'
>>> p.stock
0
>>> p.stock += 20
>>> p.stock
20
If you want to avoid duplicate items you can instead use the findOrCreate method (see below)
Retrieve Items from the Store
- getItemByID(storeID[, default=_noItem])
- Returns the item with the given "storeID". If no matching item raises KeyError or "default" if given. Every item in the store has a unique "storeID" attribute.
- findFirst(tableClass [, query arguments except 'limit'])
- Returns the first item of class "tableClass" matching the query "comparison" or None, eg.
>>> s.findFirst(ShopProduct, ShopProduct.name==u'Tea Bags') <ShopProduct name="Tea Bags" price="2" stock="20">
- findOrCreate(userItemClass[, **attrs])
- Returns the first item of class "userItemClass" or creates it if it doesn't already exist. eg.
>>> s.findOrCreate(ShopProduct, name=u'Pot Noodle') TypeError: attribute [ShopProduct.price = integer()] must not be None
but we must give all attributes required to create the new item
>>> s.findOrCreate(ShopProduct, name=u'Pot Noodle', price=3) <ShopProduct name="Pot Noodle" price="3" stock="0">
- query(tableClass[, comparison=None[, limit=None[, offset=None[, sort=None]]]])
- Return generator of items matching class "tableClass" and "comparison". Limited to length "limit" beyond "offset". Sorted by attribute "sort". Examples:
>>> "All products" >>> list(s.query(ShopProduct)) [<ShopProduct name="Tea Bags" price="2" stock="20">, <ShopProduct name="Pot Noodle" price="3" stock="0">] >>> "Products in stock" >>> list(s.query(ShopProduct, ShopProduct.stock > 0)) [<ShopProduct name="Tea Bags" price="2" stock="20">] >>> "Products in stock AND which cost less than 5" >>> from axiom.attributes import AND, OR >>> list(s.query(ShopProduct, AND(ShopProduct.stock > 0, ShopProduct.price < 5))) [<ShopProduct name="Tea Bags" price="2" stock="20">]
If you only want a single attribute from a query, you can use a ColumnQuery.
>>> list(s.query(ShopProduct, sort=ShopProduct.stock.ascending).getColumn('name')) [u'Pot Noodle', u'Tea Bags']
This also demonstrates how to sort the results.
Axiom is also capable of constructing more complex queries involving table joins behind the scenes. For more complete examples see [DivmodAxiom/Examples]
SubStore
substore.SubStore(store, path, *a, **kw)
XXX most of the stuff about SubStore instantiation is wrong now -- createNew?
A Store that also exists as an Item in a parent Store.
- store: a reference to a parent store;
- path: a string or an array representing the relative path to the substore from the files subdirectory of the parent store.
Example:
>>> from axiom import store, substore >>> s = store.Store('/tmp/substoretest.axiom') >>> ssItem = substore.SubStore(s, ['path', 'to', 'substore']) >>> s <Store '/tmp/substoretest.axiom'@0xb7d36f6c> >>> ssItem <axiom.substore.SubStore object at 0xb79a16a4>
The SubStore is just another Item. It's the store within that you will probably want.
>>> ss = ssItem.open()
<Store '/tmp/substoretest.axiom/files/path/to/substore'@0xb780df8c>
Calling SubStore.open() is not recommended however. Instead register an adaptor for substore.SubStore. When you adapt the SubStore, your adaptor will actually be passed the Store. eg.
from zope.interface import Interface, implements from twisted.python.components import registerAdapter class IMyApp(Interface): pass class MyApp(object): implements(IMyApp) def __init__(self, original): self.original = original registerAdapter(MyApp, store.Store, IMyApp)
>>> IMyApp(ssItem).original
<Store '/tmp/substoretest.axiom/files/path/to/substore'@0xb77e158c>
Now given a simple Item...
from axiom import item, attributes class MenuItem(item.Item): typeName = "MenuItem" name = attributes.text() parent = attributes.reference()
...we can see that items in two different stores cannot have references to one other...
>>> foo = MenuItem(store=s, name=u"foo") >>> bar = MenuItem(store=ss, name=u"bar") >>> foo.parent = bar Traceback (most recent call last): File "/home/richard/workspace/Divmod/Axiom/axiom/attributes.py", line 566, in infilter raise NoCrossStoreReferences( axiom.errors.NoCrossStoreReferences: You can't establish references to items in other stores.
...they can however find each other...
>>> [x.name for x in s.findFirst(substore.SubStore).open().query(MenuItem)] [u'bar'] >>> ss.parent <Store '/tmp/substoretest.axiom'@0xb784e2ec> >>> [x.name for x in ss.parent.query(MenuItem)] [u'foo']
Powerups
Powerups serve two main purposes:
- separation of concern - they let an Item be used for things the original author never planned for;
- they allow Items to be retrieved from the Store by reference to their Interface.
In the first instance the powerup can be thought of as a type of Axiom plugin.
One or more Powerups (Axiom Items) are registered to another Axiom Item in the same store or to the Store itself using the powerUp(pup, interface) method.
The Powerup(s) can be retrieved later by adaption (eg IMyInterface({Item,Store})) or by calling {Store,Item}.powerupsFor(IMyInterface).
In the former case the highest priority powerup is returned, in the latter, a generator of powerUps in order of priority.
- See the Axiom Shop example for an example of "Separation of Concern"
- See Mantissa webapp.py for an example of powerupsFor
- See the axiom.item.Empowered class
Upgraders
Why
To illustrate the need for upgraders, we'll first create an IShopProduct interface and have ShopProduct power itself up accordingly so we can locate it via this interface later.
from axiom import item, attributes from zope.interface import Interface, implements class IShopProduct(Interface): """Marker interface for shop products""" class ShopProduct(item.Item): implements(IShopProduct) typeName = "ShopProduct" name = attributes.text(allowNone=False) price = attributes.integer(allowNone=False) stock = attributes.integer(default=0) def installOn(self, other): other.powerUp(self, IShopProduct) def __repr__(self): return '<ShopProduct name="%s" price="%d" stock="%d">' % (self.name, self.price, self.stock)
So, we'll install some of these in a store:
s = Store('shop.axiom') ShopProduct(name=u'streaky bacon', price=10, store=s).installOn(s) ShopProduct(name=u'shoes', price=100000, store=s).installOn(s)
At some point in the future we think it would be a good idea to add a 'description' attribute to ShopProduct. Being reckless and indiscriminate, we just go ahead and stuff this on the class, at the same time adding 'schemaVersion' with a value of 2 (it defaulted to 1 before) because we are indeed updating our schema.
class ShopProduct(item.Item): implements(IShopProduct) typeName = "ShopProduct" schemaVersion = 2 name = attributes.text(allowNone=False) price = attributes.integer(allowNone=False) stock = attributes.integer(default=0) description = attributes.text() def installOn(self, other): other.powerUp(self, IShopProduct) def __repr__(self): return '<ShopProduct name="%s" price="%d" stock="%d" description="%s">' % (self.name, self.price, self.stock, self.description)
We then try to load some ShopProducts:
from axiom.store import Store from shopthings import IShopProduct store = Store('yourstore.axiom') for product in store.powerupsFor(IShopProduct): print product.description
but we get an error! What about all the version 1 ShopProducts?! There needs to be some kind of code telling those objects what to do with themselves in order to comform to the new schema. None of them have 'description' attributes, but from now on we expect all ShopProducts to have at least some description. The code that informs Axiom about what course of action to take is called an upgrader. So, we will write one.
How
Here is an example to upgrade ShopProducts.
def upgradeShopProduct1to2(oldShopProduct): if 'bacon' in oldShopProduct.name: description = 'salty' else: description = 'who cares' # this method is going to return another ShopProduct item, # with a schema version of two. we pass in a new value for # the newly added 'description' attribute, and we copy the # others over from the old item return oldShopProduct.upgradeVersion('ShopProduct', 1, 2, description=description, name=oldShopProduct.name, price=oldShopProduct.price, stock=oldStockProduct.stock) from axiom.upgrade import registerUpgrader # we are telling axiom: "the upgradeShopProduct1to2 function should be used to # upgrade items with a type name of 'ShopProduct' from version one to version two" registerUpgrader(upgradeShopProduct1to2, 'ShopProduct', 1, 2)
If upgrading to higher version number (version>2) use the axiom.item.declareLegacyItem to provide axiom with the needed schema for that version of the item.
todo: write more stuff
