Jonathan Ong
Jonathan Ong

Reputation: 20315

Mixin being added to every child class table when using joined table inheritance

I have the following setup:

class Attribute(object):
    a = Column(Integer)

class Thing(Base, Attribute):
    b = Column(Integer)

class Subthing(Thing):
    c = COlumn(Integer)

However, both Thing and Subthing will have the Attribute mixin, meaning they'll both have the columns specified in Attribute:

Thing: a | b
Subthing: a | c

I only want these mixin columns present in Thing and not Subthing:

Thing: a | b
Subthing: c

Is this possible or will I have to resort to making columns and methods manually instead of using a mixin for every Thing?

Upvotes: 0

Views: 355

Answers (3)

zzzeek
zzzeek

Reputation: 75117

This is a behavioral inconsistency that's developed as the result from the behavior of @declared_attr slowly deviating from a behavioral contract that was only tested against the "column copy" use case. The original use case of the "column on mixin" was to apply it to all inheriting classes equally, however @declared_attr, which was developed a bit later, didn't adopt this behavior, hence inconsistent.

Changing "column copy" to only take effect for the non-subclass is a backwards-incompatible behavioral change, so is in the upcoming 0.8 only. This is ticket #2565 (http://www.sqlalchemy.org/trac/ticket/2565) and is resolved in r9baa197f2c67.

test:

from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base, declared_attr

Base = declarative_base()

class Mixin(object):
    a = Column(Integer)

    @declared_attr
    def b(cls):
        return Column(Integer)

class A(Mixin, Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)

class B(A):
    __tablename__ = 'b'
    id = Column(Integer, ForeignKey('a.id'), primary_key=True)

assert 'a' in A.__table__.c
assert 'b' in A.__table__.c
assert 'a' not in B.__table__.c
assert 'b' not in B.__table__.c

Upvotes: 1

Kiran Jonnalagadda
Kiran Jonnalagadda

Reputation: 2826

I just ran into the same issue. It turns out if you define columns in your mixin class using @declared_attr, SQLAlchemy behaves properly. Columns declared directly in the mixin leak into sub-classes when using joined-table inheritance.

Upvotes: 0

Michael Merickel
Michael Merickel

Reputation: 23331

Subthing gets a from the parent by definition of inheritance. If you don't want that behavior, then Subthing cannot inherit from that object. A possible solution is to introduce yet another base class.

class Attribute(object):
    @declared_attr # the actual syntax on SQLAlchemy for doing mixins
    def a(cls):
       return Column(Integer)

class BaseThing(Base):
    pass

class Thing(BaseThing, Attribute):
    b = Column(Integer)

class Subthing(BaseThing):
    c = Column(Integer)

Then Subthing only has column c, and Thing has a, and b.

Upvotes: 4

Related Questions