Skip to content

Instantly share code, notes, and snippets.

@groner
Created September 3, 2010 05:42
Show Gist options
  • Save groner/563464 to your computer and use it in GitHub Desktop.
Save groner/563464 to your computer and use it in GitHub Desktop.
sa.ext.declarative extension to generate relations from foreign keys and __singular__/__plural__ declarations
from sqlalchemy import Column
from sqlalchemy.orm import RelationProperty, relation, backref, class_mapper
from sqlalchemy.ext.declarative import DeclarativeMeta, declarative_base
class AutoRelationingDeclarativeMeta(DeclarativeMeta):
r'''
>>> from sqlalchemy import Integer, ForeignKey, String
>>> Base = declarative_base(metaclass=AutoRelationingDeclarativeMeta)
Define a couple of classes with some relation
>>> class Foo(Base):
... __tablename__ = 'foo'
... __singular__ = 'foo'
... id = Column(Integer, primary_key=True)
... name = Column(String, nullable=False)
>>> class Bar(Base):
... __tablename__ = 'bar'
... __plural__ = 'bars'
... id = Column(Integer, primary_key=True)
... name = Column(String, nullable=False)
... foo_id = Column(Integer, ForeignKey(Foo.id))
Force mapper compilation
>>> _ = class_mapper(Bar)
>>> str(~Foo.bars.any())
'NOT (EXISTS (SELECT 1 \nFROM bar \nWHERE foo.id = bar.foo_id))'
>>> str(Bar.foo==None)
'bar.foo_id IS NULL'
'''
def __init__(cls, name, bases, d):
# Let the base metaclass do it's thing first, since we want to
# introspect on the table it will possibly be generating
super(AutoRelationingDeclarativeMeta, cls).__init__(name, bases, d)
# We need to be able to look up a class for a given table
if hasattr(cls, '__table__'):
cls._decl_table_class_map[cls.__table__] = cls
else:
cls._decl_table_class_map = {}
if '__table__' in d:
foreign_key_columns = [ fk.parent for fk in cls.__table__.foreign_keys ]
else:
foreign_key_columns = [ v for v in d.values() if isinstance(v, Column) and v.foreign_keys ]
attrs = ( getattr(cls, k) for k in dir(cls) )
relations = [ v for v in attrs if isinstance(v, RelationProperty) ]
for c in foreign_key_columns:
'''Find all foreign keys in the table being mapped'''
if any( c in prop.local_side for prop in relations ):
continue
'''For which no relation has been defined'''
tbl = c.foreign_keys[0].column.table
fcls = cls._decl_table_class_map[tbl]
if hasattr(fcls, '__singular__'):
if hasattr(cls, fcls.__singular__):
'''If something else is already defined here, don't do anything'''
continue
backref_ = None
if '__plural__' in d:
backref_ = backref(cls.__plural__)
elif '__singular__' in d:
'''Support ONE-to-ONE behavior'''
backref_ = backref(cls.__plural__, uselist=False)
# TODO: support secondary joins
cls.__mapper__.add_property(fcls.__singular__, relation(fcls, backref=backref_))
Base = declarative_base(metaclass=DeclarativeMeta)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment