SQLObjectInherit: Alternative SQLObject inheritance

Motivation

I think that SQLObject is a fantastic ORM. However, one aspect of its behaviour that fails to excite me is its way of managing inheritance. You may refer to the documentation for details; in brief, SQLObject’s inheritance scheme assumes that information distinguishing subclasses is stored in separate tables, linked via foreign keys from the parent class table.

For various reasons, my own code contains data that represents a few types in a hierarchy within the same table, with subtypes denoted by flags: For instance, a Spell is a (is_a) Skill, but has a few additional properties; the skill table contains a column is_spell, defaulting to false.

What I wanted out of SQLObject was an inheritance scheme where I could

Features

SQLObjectInherits provides an eponymous decorator that provides inheritance features for SQLObject classes. The decorator is parameterised on an attribute (and optionally, a value—normally and by default True) to distinguish between subclasses.

The attribute will be used intelligently to return the correct type from SQLObject select() and get() calls, so that iteration over a result set from a select() on the parent may contain a mixture of parent and child class instances.

Additionally, child classes are augmented to force and assume the attribute value; e.g. when child instances are initialised, the attribute is set automatically in __init__(), and select() queries to the child class automatically have the appropriate WHERE clause injected.

Usage

# Sample usage of SQLObjectInherits
# (sample modified from development unit tests)


class Parent(SQLObject):
    name = StringCol(length=32, alternateID=True, unique=True)
    is_child = BoolCol(default=False)
Parent.createTable()


@SQLObjectInherits('is_child')
class Child(Parent):
    class sqlmeta:
        table = Parent.sqlmeta.table

    def some_child_specific_stuff(self):
        # ...
        pass


some_bloke = Parent(name='Eddie')
some_lad = Child(name='Badcrumble')
# ...

for child in Child.select():
    # All instances will be children
    # ...

for person in Parent.select():
    # Instances may be either Parent or Child instances
    # ...

Restrictions

You must define sqlmeta in all child classes. Because SQLObject works miscellaneous magic during class construction time, the class is tied to a table before the SQLObjectInherits decorator does its work.

Each inheriting child class must have exactly one parent SQLObject class. The behaviour of child classes with multiple inheritance from classes extending SQLObject is undefined.

Database requirements

You are perfectly free to define columns in child classes, but of course if your schema contains columns that are specific to some child class, and some rows will represent instances of other classes, then these columns must allow NULL values or contain sensible defaults.

Dependencies

Source highlighting thanks to Will Larson.
Now in my blog:
RSS feed LiveJournal blog Show me more!