Mixin of custom TypeEngine with ColumnProperty doesn't work

Issue #1796 resolved
Former user created an issue

I'm using GeoAlchemy which defines a custom type derived from TypeEngine and it never makes it into table columns, and though ignored during the creation phase. Below is a test case and I'm attaching a patch.

class LocalizedThingyMixin(object): title = Column('title', types.String(128), nullable=False) description = Column('description', types.Text, nullable=False) location = GeometryColumn('location', Point(2), comparator=PGComparator)

class Event(Base,LocalizedThingyMixin): """docstring for Event""" tablename = "event" id = Column(types.Integer, Sequence('object_sequence'), primary_key=True)

location is never created, because it is ignored during the mixing recursion phase.

Comments (13)

  1. Former user Account Deleted

    Sorry, didn't enclose test case in code block...

    class LocalizedThingyMixin(object):
        title = Column('title', types.String(128), nullable=False)
        description = Column('description', types.Text, nullable=False)
        location = GeometryColumn('location', Point(2), comparator=PGComparator)
    
    class Event(Base,LocalizedThingyMixin):
        """docstring for Event"""
        __tablename__ = "event"
        id = Column(types.Integer, Sequence('object_sequence'), primary_key=True)
    

    Ilya Sterin

  2. Mike Bayer repo owner

    Here's why the patch doesn't work. At the very least, the column_property would have to copy its contained columns. But that still doesn't prevent the scenario below and actually makes it worse:

            class MyMixin(object):
                prop = Column('prop', String(50))
                prop_hoho = column_property(prop + 'hoho')
    
            class MyModel(Base, MyMixin):
                __tablename__='test'
                id = Column(Integer, primary_key=True, test_needs_autoincrement=True)
    
            class MyOtherModel(Base, MyMixin):
                __tablename__='othertest'
                id = Column(Integer, primary_key=True, test_needs_autoincrement=True)
    
            compile_mappers()
    
            assert MyModel.prop.property.columns[0](0) is MyModel.__table__.c.prop
            # fails
            assert MyModel.prop_hoho.property.columns[0](0) is MyModel.__table__.c.prop
    
            assert MyOtherModel.prop.property.columns[0](0) is MyOtherModel.__table__.c.prop
    
            # fails
            assert MyOtherModel.prop_hoho.property.columns[0](0) is MyOtherModel.__table__.c.prop
    

    the above pattern "works" for simple cases but is sloppy and probably begins to have subtle issues since the prop_hoho is not referencing the correct Column object.

  3. Mike Bayer repo owner

    even worse:

            class MyMixin(object):
                prop_hoho = column_property(Column('prop', String(50)))
    
            class MyModel(Base, MyMixin):
                __tablename__='test'
                id = Column(Integer, primary_key=True, test_needs_autoincrement=True)
    
            class MyOtherModel(Base, MyMixin):
                __tablename__='othertest'
                id = Column(Integer, primary_key=True, test_needs_autoincrement=True)
    
            assert MyModel.__table__.c.prop is not None
            assert MyOtherModel.__table__.c.prop is not None
    

    "prop" is only getting placed on one of the tables. no errors are raised and the mapping is pretty severely broken.

  4. Former user Account Deleted

    Yeah, sorry, I'm not familiar with SQLAlchemy internals. I just started using it a week ago and ran into this issue. Fix seemed naively easy and rightfully so.

    I can dig in deeper, but it'll take a while to get familiar with internals, might be faster for someone with the knowledge to fix it.

    Let me know if I can do anything.

  5. Mike Bayer repo owner

    a new proposed patch is added. This isolates the point at which we'd have to copy the expression. still doesn't work though...

        def test_column_property_conflict_two(self):
            # We cannot copy "prop_hoho"
            # and still have it reference the correct 'prop' column.
            class MyMixin(object):
                prop = Column('prop', String(50))
                prop_hoho = column_property(prop)
    
            class MyModel(Base, MyMixin):
                __tablename__='test'
                id = Column(Integer, primary_key=True, test_needs_autoincrement=True)
    
            class MyOtherModel(Base, MyMixin):
                __tablename__='othertest'
                id = Column(Integer, primary_key=True, test_needs_autoincrement=True)
    
            compile_mappers()
            assert MyModel.prop_hoho.property.columns[0](0) is MyModel.__table__.c.prop
            # boom
            assert MyModel.prop.property.columns[0](0) is MyModel.__table__.c.prop
    
  6. Mike Bayer repo owner
    • changed milestone to 0.6.1

    ill keep it on 0.6.1 for now, so i can take another look before i put out 0.6.1 in a few weeks, but I really don't have time to play with it more today.

  7. Former user Account Deleted

    Thanks a lot for the fix.

    I don't see it in tip yet. Did you commit this to some other branch?

  8. Mike Bayer repo owner

    the fix does not work. there is no solution for this issue as of yet.

    I am considering something along the lines of:

    class MyMixin(object):
        prop = Column(Integer)
    
        @classproperty
        def prop_foo(cls):
            return sa.orm.column_property(cls.prop)
    

    the basic fact is that column_property() references other aspects of the table metadata by value. Copying it is not that simple, as is also the case with columns with foreign keys, etc. Expect to see many more mixin-configurations that don't work as expected without many more hurdles being overcome, as mixins were never part of declarative's original idea.

  9. Log in to comment