pyramid_alchemy

Pyramid framework integration to extend SQLAlchemy ORM classes with separate, easily testable, code.


Licenses
AFL-3.0/libselinux-1.0
Install
pip install pyramid_alchemy==0.0.1

Documentation

Good frameworks, like Pyramid and Angular, help developers write testable code. SQLAlchemy is a great framework. However, the sheer convenience of having model instances returned from a database query encourages developers to write large ORM classes which can be hard to test.

One way to avoid this is to keep model classes thin and write separate code to instantiate and manipulate them. The downside of this approach is the loss in convenience: the separated, easily testable code isn't provided by default as an attribute of the model instances returned from a database query.

pyramid_alchemy provides an add_model_method Pyramid configuration directive that extends SQLAlchemy ORM classes in the same way that add_request_method extends the Pyramid Request. Using this directive allows developers to write easily testable code that is conveniently available as a model instance attribute. For example, if you include this in your Pyramid application:

from .model import Spam

def get_eggs(instance, source='hens', limit=9, offset=0):
    """Example ORM instance method, implemented as a standalone function."""
    
    query = instance.query.filter_by(source=source)
    return query.offset(offset).limit(limit)

def includeme(config):
    config.add_model_method(Spam, get_eggs, 'get_eggs')

You can then use the get_eggs method from Spam instances:

spam = Session.query(Spam).get(1)
eggs = spam.get_eggs()

Interfaces

Just as you can hang a Pyramid view off any context object implementing a specific interface, you can extend any model instance implementing an interface. For example, if your model looked something like this:

from sqlalchemy.ext.declarative import declarative_base
from zope.interfaces import implementer

from .interfaces import IFilling

Base = declarative_base()

@implementer(IFilling)
class Ham(Base):
    # ...

@implementer(IFilling)
class Spam(Base):
    # ...

Then you could extend all fillings -- current and future -- with:

config.add_model_method(IFilling, get_eggs, 'get_eggs')

Limitations

Note that it's highly unlikely to be a good idea to use add_model_method to add dynamic or hybrid methods that affect the underlying sql table or mapping.