def associable(assoc_entity, plural_name=None, lazy=True):
    '''
    Generate an associable Elixir Statement
    '''
    interface_name = assoc_entity._descriptor.tablename
    able_name = interface_name + 'able'

    if plural_name:
        attr_name = "%s_rel" % plural_name
    else:
        plural_name = interface_name
        attr_name = "%s_rel" % interface_name

    class GenericAssoc(object):

        def __init__(self, tablename):
            self.type = tablename

    #TODO: inherit from entity builder
    class Associable(object):
        """An associable Elixir Statement object"""

        def __init__(self, entity, name=None, uselist=True, lazy=True):
            self.entity = entity
            self.lazy = lazy
            self.uselist = uselist

            if name is None:
                self.name = plural_name
            else:
                self.name = name

        def after_table(self):
            col = sa.Column('%s_assoc_id' % interface_name, sa.Integer,
                            sa.ForeignKey('%s.id' % able_name))
            self.entity._descriptor.add_column(col)

            if not hasattr(assoc_entity, '_assoc_table'):
                metadata = assoc_entity._descriptor.metadata
                association_table = sa.Table("%s" % able_name, metadata,
                    sa.Column('id', sa.Integer, primary_key=True),
                    sa.Column('type', sa.String(40), nullable=False),
                )
                tablename =  "%s_to_%s" % (able_name, interface_name)
                association_to_table = sa.Table(tablename, metadata,
                    sa.Column('assoc_id', sa.Integer,
                              sa.ForeignKey(association_table.c.id,
                                            ondelete="CASCADE"),
                              primary_key=True),
                    #FIXME: this assumes a single id col
                    sa.Column('%s_id' % interface_name, sa.Integer,
                              sa.ForeignKey(assoc_entity.table.c.id,
                                            ondelete="RESTRICT"),
                              primary_key=True),
                )

                assoc_entity._assoc_table = association_table
                assoc_entity._assoc_to_table = association_to_table

        def after_mapper(self):
            if not hasattr(assoc_entity, '_assoc_mapper'):
                assoc_entity._assoc_mapper = sa.orm.mapper(
                    GenericAssoc, assoc_entity._assoc_table, properties={
                        'targets': sa.orm.relation(
                                       assoc_entity,
                                       secondary=assoc_entity._assoc_to_table,
                                       lazy=lazy, backref='associations',
                                       order_by=assoc_entity.mapper.order_by)
                })

            entity = self.entity
            entity.mapper.add_property(
                attr_name,
                sa.orm.relation(GenericAssoc, lazy=self.lazy,
                                backref='_backref_%s' % entity.table.name)
            )

            if self.uselist:
                def get(self):
                    if getattr(self, attr_name) is None:
                        setattr(self, attr_name,
                                GenericAssoc(entity.table.name))
                    return getattr(self, attr_name).targets
                setattr(entity, self.name, property(get))
            else:
                # scalar based property decorator
                def get(self):
                    attr = getattr(self, attr_name)
                    if attr is not None:
                        return attr.targets[0]
                    else:
                        return None
                def set(self, value):
                    if getattr(self, attr_name) is None:
                        setattr(self, attr_name,
                                GenericAssoc(entity.table.name))
                    getattr(self, attr_name).targets = [value]
                setattr(entity, self.name, property(get, set))

            # self.name is both set via mapper synonym and the python
            # property, but that's how synonym properties work.
            # adding synonym property after "real" property otherwise it
            # breaks when using SQLAlchemy > 0.4.1
            entity.mapper.add_property(self.name, sa.orm.synonym(attr_name))

            # add helper methods
            def select_by(cls, **kwargs):
                return cls.query.join(attr_name, 'targets') \
                                .filter_by(**kwargs).all()
            setattr(entity, 'select_by_%s' % self.name, classmethod(select_by))

            def select(cls, *args, **kwargs):
                return cls.query.join(attr_name, 'targets') \
                                .filter(*args, **kwargs).all()
            setattr(entity, 'select_%s' % self.name, classmethod(select))

    return Statement(Associable)
dataset/ETHPy150Open jmg/elixir/elixir/ext/associable.py/associable/original