mike watkins dot ca : Python Object Persistence

Python Object Persistence

Noted via #python on Twitter, a link to this discussion on creating a simple Movie and Actor database. Some suggestions for the original poster included using Shelve; myself I'd prefer to move up a rung or two and use a Python object database like ZODB or Durus, either of which can be used for anything from simple to complex solutions.

Attached is a dirt-simple module implementing a movie and actor database. Look ma, no SQL. The core of it is here, with the objects of interest subclassing Durus's Persistent class:

# Our "Models" -- it's just Python.

class Actor(Persistent):

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return '<Actor: %s>' % self.name


class Movie(Persistent):

    def __init__(self, title):
        self.title = title
        self.actors = PersistentSet()

    def add_actor(self, actor):
        self.actors.add(actor)

    def __str__(self):
        return '<Movie: %s>' % self.title


class Database(Persistent):

    def __init__(self):
        self.movies = PersistentDict()
        self.actors = PersistentDict()

    def add_movie(self, movie):
        assert movie.title not in self.movies, 'Already exists!'
        self.movies[movie.title] = movie
        return movie

    def get_movie(self, title):
        return self.movies.get(title, None)

    def add_actor(self, actor):
        assert actor.name not in self.actors, 'Already exists!'
        self.actors[actor.name] = actor

    def get_actor(self, name):
        return self.actors.get(name, None)

    def appears_in(self, actor):
        """(actor : Actor) -> [Movie,]

        Returns a sequence of Movies, if any, the Actor played a role in.
        """
        return [m for m in self.movies.values() if actor in m.actors]

Objects which subclass the Persistent class become persistence-aware. Aside from having some special container classes for persistent dicts, lists, sets and BTrees for performance with large mappings, there isn't much of a mental burden to design objects that participate in a Durus or ZODB object database.

An example of retrieving some data from our film database:

if __name__ == '__main__':
    from examples.durus.flicks.movies import load_it, get_db
    from durus.file_storage import FileStorage
    from durus.connection import Connection

    connection = Connection(FileStorage("movies.durus"))
    # load some sample data
    if not connection.get_root().get('films', False):
        load_it(connection)
    db = get_db(connection)
    hopkins = db.get_actor('Anthony Hopkins')
    mel = db.get_actor('Mel Gibson')

    # Which films has an actor appeared in?
    for actor in (hopkins, mel):
        print()
        print('%s is in:' % actor.name)
        print([str(m) for m in db.appears_in(actor)])

    # What films are in our movie database and name known actors in each
    print("\nComplete Movie Listing")
    for m in db.movies.values():
        print(m.title, ':', ','.join([str(a) for a in m.actors]))

Running this script the first time you'll note some log activity to stdout as the object instances are committed to the database. The listing output will look like this:

Anthony Hopkins is in:
['<Movie: Mutiny on the Bounty>']

Mel Gibson is in:
['<Movie: Braveheart>', '<Movie: Lethal Weapon II>',
'<Movie: Mutiny on the Bounty>', '<Movie: Lethal Weapon III>',
'<Movie: Lethal Weapon>']

Complete Movie Listing
Braveheart : <Actor: Mel Gibson>
Lethal Weapon II : <Actor: Mel Gibson>,<Actor: Danny Glover>
Mutiny on the Bounty : <Actor: Mel Gibson>,<Actor: Anthony Hopkins>
Lethal Weapon III : <Actor: Mel Gibson>,<Actor: Danny Glover>
Lethal Weapon : <Actor: Mel Gibson>,<Actor: Danny Glover>

(No commentary on my taste for films, please)

In addition to the FileStorage class, Durus offers a client-server solution ClientStorage suitable for use by multi-process applications such as web apps. ZODB provides the same through its ZEO feature.

movies.py (3614 bytes, text/plain)
A cheap and cheerful movie and actor tracking database using the Python Durus object database for persistence.