Declarative Mapping with SQLAlchemy · Monday, February 13, 2006

UPDATE: My work on ActiveMapper has been discontinued in favor of the elixir delcarative mapper.

For a long time now, I have been a huge fan of a Python object-relational mapper called sqlalchemy, which is much more flexible and powerful than the other popular ORM(object relational mapping) toolkits currently available. As much as I liked SQLAlchemy, I found its separation of database mapping and class design to be a little cumbersome, and hibernate-like.

As a result, I began to tinker around a bit with SQLAlchemy in order to create a way to define database mapping and class design in a single declarative way. A few days ago, Mike Bayer was kind enough to put my first pass at this into the SQLAlchemy subversion repository as the sqlalchemy.ext.activemapper module, along with unit tests that show it off. The module allows you to define your classes and your database mapping in one go, while not forgoing most of the amazing flexibility of SQLAlchemy's table and mapper objects.

ActiveMapper allows you to easily declare tables and classes like the following:

class Person(ActiveMapper):
    class mapping:
        id          = column(Integer, primary_key=True)
        full_name   = column(String)
        first_name  = column(String)
        middle_name = column(String)
        last_name   = column(String)
        birth_date  = column(DateTime)
        ssn         = column(String)
        gender      = column(String)
        home_phone  = column(String)
        cell_phone  = column(String)
        work_phone  = column(String)
        prefs_id    = column(Integer, foreign_key=ForeignKey('preferences.id'))
        addresses   = one_to_many('Address', colname='person_id', backref='person')
        preferences = one_to_one('Preferences', colname='pref_id', backref='person')
    
    def __str__(self):
        s =  '%s\n' % self.full_name
        s += '  * birthdate: %s\n' % (self.birth_date or 'not provided')
        s += '  * fave color: %s\n' % (self.preferences.favorite_color or 'Unknown')
        s += '  * personality: %s\n' % (self.preferences.personality_type or 'Unknown')
        
        for address in self.addresses:
            s += '  * address: %s\n' % address.address_1
            s += '             %s, %s %s\n' % (address.city, address.state, address.postal_code)
        
        return s


class Preferences(ActiveMapper):
    class mapping:
        __table__        = 'preferences'
        id               = column(Integer, primary_key=True)
        favorite_color   = column(String)
        personality_type = column(String)


class Address(ActiveMapper):
    class mapping:
        id          = column(Integer, primary_key=True)
        type        = column(String)
        address_1   = column(String)
        city        = column(String)
        state       = column(String)
        postal_code = column(String)
        person_id   = column(Integer, foreign_key=ForeignKey('person.id'))

ActiveMapper can infer the table name from the class name, and handle all the relationships between dependent objects. There is still plenty of work to do, and a few unit tests are failing. I have already received some feedback about the repetitiveness of typing `column` repeatedly, and a few other minor nits, but I really feel like we are on to something with this.

I am currently looking for a partner or two in crime to help me take this to a new level. Right now, its more of a proof-of-concept than a full layer on top of SQLAlchemy. Mike Bayer seems to be in full support of the effort, and has given me committer status on the SQLAlchemy repository, so lets get this thing rolling. If you want to join me, please leave a comment here, or shoot me an email so that we can make declarative SQLAlchemy a reality.

Comment

  1. Consider me interested. I’ve been looking for an open source project to tinker with and try to make some contributions to, preferably in python. I’ve been working with several ORM’s lately, Hibernate for Java at work, SQLObject, Ruby on Rails’ ActiveRecord, and finally Django’s.

    I’m just trying out SQLAlchemy now, if I can get up to speed quick enough I’d love to lend a hand with what you’re looking to accomplish.

    Cheers.
    herk    2284 days ago    #
  2. I’m curious why you’ve chosen to use an inner class called mapping for this rather than using SQLObject/Django style classes. Also, if I can muster the time, I’d be willing to help out. I thought about doing something like this after I scanned the SQLAlchemy docs. I’m glad to see someone is taking it on.
    joseph    2282 days ago    #
  3. Good job! I just wonder how to do many-to-many-relationships. Is this possible yet? How would I most easily do this?
    Daniel    2269 days ago    #
  4. Many-to-many relationships are possible in SQLAlchemy, but I have no shortcut in ActiveMapper yet. Currently, you would have to manually manipulate the mappers on your objects, which is pretty easy.

    I plan on implementing many-to-many, inheritence, and a few other things as soon as I get the chance, although I am pretty busy right now. I would love some help though :)
    Jonathan LaCour    2269 days ago    #
  5. I’m interested in pitching in, and actually I wonder if this isn’t a great opportunity to start pulling Zope / Plone dependencies out of the Archetypes stack.

    Archetypes, for those not familiar, is a handy schema monster which is used primarily within ZODB, sql-free. we have a sqlStorage option which is less than optimal and is due to be improved with sqlAlchemy.

    The current focus of Archetypes is auto-generated web forms, but hopefully are M-V-C enough to address other cases for ORM with editing context. We have also looked at sqlos integration, maybe sqlos and sqlAlchemy should meet up.
    justizin    2202 days ago    #
  6. Is it fast?

    I’m really keen on this extension and I’m tempted to skip learning SA and jump straight at this more or less so I don’t even know how fast SA is, but what do you know of the speed/performance of AM?
    Peter Bengtsson    2180 days ago    #
  7. Peter—ActiveMapper should be just as “fast” as plain-old SQLAlchemy, since its a very thin layer on top of SQLAlchemy proper. As far as how fast SQLAlchemy is, in my experience it does a fairly good job of generating efficient SQL, but YMMV. I would recommend playing around with it a bit yourself.

    I would advise that you learn all of SQLAlchemy, and not just ActiveMapper. ActiveMapper is mostly syntactic sugar on top of SQLAlchemy, and it still needs some work. To truly see all of the power of SQLAlchemy, you will likely need to read the tutorial (which doesn’t take long).

    Best of luck!
    Jonathan LaCour    2180 days ago    #
  8. I don’t know if you are interested but I’m currently modifying ActiveMapper so that it has a more SQLObject-like syntax. Also, I’m fixing all the little squirks I can find (many to many relationships, circular references, etc…). I hope you are not against some heavy modifications to your code. I’d really like to discuss them with you sometime. I’d be grateful if you could contact me (supposing you are interested, of course).
    GaĆ«tan de Menten    2035 days ago    #
  9. For those interested: TurboEntity looks also quite nice: http://turboentity.ematia.de/
    Markus Gritsch    2023 days ago    #

commenting closed for this article