Created
September 3, 2010 05:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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