Wiki

Clone wiki

sqlalchemy / UsageRecipes / QuickHybrid

QuickHybrid

Declare a @hybrid_property using a decorator that generates a corresponding Column for you. The technique here is to combine the declared_attr decorator with a hybrid so that you still get the full configurability of hybrid while piggybacking on the usual declarative column assignment.

from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column

def mapped_column(*args, **kw):
    colargs, colkw = args, kw
    class decorate(declared_attr):
        def __init__(self, fget, *arg, **kw):
            super(decorate, self).__init__(fget, *arg, **kw)

            assert fget.__name__.startswith("_")
            self.attrname = fget.__name__[1:]
            self.colname = colname = fget.__name__

            @hybrid_property
            def attrib(self):
                return fget(self, getattr(self, colname))

            @attrib.setter
            def attrib(self, value):
                setattr(self, colname, value)

            @attrib.expression
            def attrib(cls):
                return getattr(cls, colname)

            self.attrib = attrib
            self.col = Column(self.attrname, *colargs, **colkw)

        def __get__(desc, self, cls):
            setattr(cls, desc.attrname, desc.attrib)
            return desc.col

        def setter(self, fset):
            self.attrib.setter(fset)
            return self

        def deleter(self, fdel):
            self.attrib.deleter(fset)
            return self

        def expression(self, expr):
            self.attrib.expression(fset)
            return self

    return decorate

Basic usage:

from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MyClass(Base):
    __tablename__ = 'mytable'
    id = Column(Integer, primary_key=True)

    @mapped_column(String)
    def _name(self, value):
        return "The name is: " + value

e = create_engine('sqlite://',echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add(MyClass(name="myname"))
s.commit()

print s.query(MyClass).first().name

The full capability of Hybrid is still here, as below where we add a custom setter in:

class MyClass(Base):
    __tablename__ = 'mytable'
    id = Column(Integer, primary_key=True)

    @mapped_column(String)
    def _name(self, value):
        return "The name is: " + value

    @_name.setter
    def _name(self, value):
        return value + "is my name"

the test program's output with the above would be The name is: myname is my name.

Updated