One problem that we had in the first version of our inheritance by composition pattern was that we could not make storm queries using the subclasses. In other words, the following would return None::

secret_agent = store.find(SecretAgent, SecretAgent.name==u'James Bond').one()

The reason is that the expresion SecretAgent.name would resolve to a Passthrough lazr.delegates object that Storm does not know how to handle.

This time we will try to fix this problem using a manually generated version of our classes using storm's Proxy objects.

The interface definitions do not change:

>>> from zope.interface import Interface, Attribute
>>> class IPerson(Interface):
...     name = Attribute("Name")
...
>>> class ISecretAgent(IPerson):
...     passcode = Attribute("Passcode")
...
>>> class ITeacher(IPerson):
...     school = Attribute("School")

Neither the Person class:

>>> from zope.interface import implements
>>> from zope.interface.verify import verifyClass
>>> from lazr.delegates import delegates
>>> from storm.locals import Store, Storm, Unicode, Int, Proxy, Reference
>>> class Person(Storm):
...     implements(IPerson)
...
...     __storm_table__ = "person"
...
...     id = Int(allow_none=False, primary=True)
...     name = Unicode()
...     person_type = Int(allow_none=False)
...     _person = None
...
...     def __init__(self, store, name, person_class, **kwargs):
...         self.name = name
...         self.person_type = person_class.person_type
...         store.add(self)
...         self._person = person_class(self, **kwargs)
...
...     @property
...     def person(self):
...         if self._person is None:
...             assert self.id is not None
...             person_class = BasePerson.get_class(self.person_type)
...             self._person = Store.of(self).get(person_class, self.id)
...         return self._person
>>> verifyClass(IPerson, Person)
True

Neither our custom metaclass:

>>> from storm.properties import PropertyPublisherMeta
>>> class PersonType(PropertyPublisherMeta):
...     def __init__(self, name, bases, dict):
...         super(PersonType, self).__init__(name, bases, dict)
...         if not hasattr(self, '_person_types_registry'):
...             self._person_types_registry = {}
...         elif hasattr(self, '__storm_table__'):
...             key = len(self._person_types_registry)
...             self._person_types_registry[key] = self
...             self.person_type = key
...
...     def get_class(self, person_type):
...         return self._person_types_registry[person_type]

Here start the changes. Our BasePerson class does not have the delegates call and does not define the person_id attribute and person reference.

>>> class BasePerson(Storm):
...     __metaclass__ = PersonType
...
...     def __init__(self, person):
...         self.person = person

Instead we define the person_id and person reference in each subclass and also we define each attribute of the Person base class as a proxy to the related attribute of the person reference.

>>> class SecretAgent(BasePerson):
...     implements(ISecretAgent)
...
...     __storm_table__ = "secret_agent"
...     passcode = Unicode()
...     person_id = Int(allow_none=False, primary=True)
...     person = Reference(person_id, Person.id)
...     name = Proxy(person, Person.name)
...
...     def __init__(self, person, passcode=None):
...         super(SecretAgent, self).__init__(person)
...         self.passcode = passcode
>>> verifyClass(ISecretAgent, SecretAgent)
True

We do it again for the Teacher class:

>>> class Teacher(BasePerson):
...     implements(ITeacher)
...
...     __storm_table__ = "teacher"
...     school = Unicode()
...     person_id = Int(allow_none=False, primary=True)
...     person = Reference(person_id, Person.id)
...     name = Proxy(person, Person.name)
...
...     def __init__(self, person, school=None):
...         super(Teacher, self).__init__(person)
...         self.school = school
>>> verifyClass(ITeacher, Teacher)
True

Time to test the database storage:

>>> from storm.locals import create_database
>>> database = create_database("sqlite:")
>>> store = Store(database)
>>> result = store.execute("""
...     CREATE TABLE person (
...         id INTEGER PRIMARY KEY,
...         person_type INTEGER NOT NULL,
...         name TEXT NOT NULL)
... """)
>>> result = store.execute("""
...     CREATE TABLE secret_agent (
...         person_id INTEGER PRIMARY KEY,
...         passcode TEXT)
... """)
>>> result = store.execute("""
...     CREATE TABLE teacher (
...         person_id INTEGER PRIMARY KEY,
...         school TEXT)
... """)
>>> secret_agent = Person(store, u"James Bond",
...                        SecretAgent, passcode=u"007")
>>> ISecretAgent.providedBy(secret_agent.person)
True
>>> teacher = Person(store, u"Albus Dumbledore",
...                  Teacher, school=u"Hogwarts")
>>> ITeacher.providedBy(teacher.person)
True
>>> store.commit()

And what's more important, all this changes should make possible to do the query with the subclass:

>>> del secret_agent
>>> del teacher
>>> store.rollback()
>>> secret_agent = store.find(SecretAgent).one()
>>> secret_agent.name, secret_agent.passcode
(u'James Bond', u'007')
>>> teacher = store.find(Teacher).one()
>>> teacher.name, teacher.school
(u'Albus Dumbledore', u'Hogwarts')
>> secret_agent = store.find(SecretAgent, SecretAgent.name==u'James Bond').one()
>>> secret_agent.passcode
u'007'

We have improved the power of the pattern but now it is much more verbose to write each subclass since we need to repeat a lot of things. Note that we can not move the definition of the person_id and person attributes to the BasePerson aux class because Storm will tell us that it lacks the __storm_table__ attribute. In other words: storm does not allow attributes in abstract classes.