On Thursday, July 21, 2011 09:27:21 PM Michael Bayer wrote:
> On Jul 21, 2011, at 10:48 AM, Michael Bayer wrote:
> > On Jul 21, 2011, at 2:11 AM, Fayaz Yusuf Khan wrote:
> >> Hi,
> >> I have this declarative table model:
> >> http://paste.pound-python.org/show/9857/
> > 
> > The short answer is you can't do it that way right now.   The "user" and
> > "timestamp" columns of C can't get assigned to the Table by declarative
> > because they've already been instrumented by the mapping of the Timeline
> > class - the columns generated on the TMixin class are replaced by the
> > instrumented versions of Timeline.
Well, I'd really like to see this feature implemented.
> > 
> > You'd have to forego the usage of mixins here or have C() use a mixin
> > that isn't in the hierarchy of Timeline.
> > 
> > Ticket #2226 is added to see if this can be fixed.
> 
> OK it's an invalid, though I made the error message with the
> ForeignKeyConstraint nicer in the latest tip.
> 
> When C inherits from CMixin and Timeline, Python's method resolution is
> going to put TMixin after Timeline, that is:
> 
> (<class '__main__.C'>, <class '__main__.CMixin'>, <class
> '__main__.Timeline'>, <class '__main__.TMixin'>, <class
> 'sqlalchemy.ext.declarative.Base'>, <class '__main__.TableName'>, <type
> 'object'>)
Maybe, joint-table inheritance and mixin syntaxes should be made more distinct 
and unambiguous?
It may well be impossible (and backward incompatible) to implement in the 
current code-base, but a usage recipe which implements this behaviour might be 
possible.
> 
> The "user" and "timestamp" columns you're looking to be copied separately
> to both C and Timeline only get applied to Timeline - since Timeline is
> mapped and it ends the scope of TMixin propagating its attributes outward.
> 
> A similar example shows how this behavior produces what's expected.  Below,
> Child inherits from Parent, but we would expect Child to get the behavior
> of y() from Parent, not that of SomeMixin which is "behind" Parent:
> 
> from sqlalchemy import Column, String, ForeignKey, Integer
> from sqlalchemy.ext.declarative import declarative_base, declared_attr
> 
> class TableName(object):
>    @declared_attr
>    def __tablename__(cls):
>        return cls.__name__
> 
> Base=declarative_base(cls=TableName)
> 
> class SomeMixin(object):
>     @declared_attr
>     def x(cls):
>         return Column(String)
> 
>     @declared_attr
>     def y(cls):
>         return Column(String)
> 
> class Parent(SomeMixin, Base):
>    id = Column(Integer, primary_key=True)
> 
>    def y(self):
>        return "hi"
> 
> class SomeOtherMixin(SomeMixin):
>     pass
> 
> # this:
> class Child(SomeOtherMixin, Parent):
>    id = Column(Integer, ForeignKey('Parent.id'), primary_key=True)
> 
> # is essentially the same here as:
> #class Child(Parent):
> #   id = Column(Integer, ForeignKey('Parent.id'), primary_key=True)
> 
> # Parent.y overrides the @declared_attr in SomeMixin
> assert Child().y() == 'hi'
> 
> # so similarly, SomeMixin.x only applies to Parent
> assert 'x' not in Child.__table__.c
> 
> # Child.x and Parent.x are the same mapped property,
> # ultimately.
> assert Child.x.property is Parent.x.property
> 
> In your example, the "user" and "timeline" columns as associated with C are
> not really "the same" as the columns on Timeline - the Timeline columns
> are the ultimate autogenerating PK columns, whereas those of "C" are
> referencing - they have a different role.   So it makes sense they be
> declared distinctly on CMixin.  CMixin extends TMixin below but it might
> as well not, as Timeline in the middle takes the place of TMixin.
> 
> 
> from sqlalchemy import Column, String, ForeignKeyConstraint, Integer
> from sqlalchemy.ext.declarative import declarative_base, declared_attr
> 
> class TableName(object):
>    @declared_attr
>    def __tablename__(cls):
>        return cls.__name__
> 
> Base=declarative_base(cls=TableName)
> 
> class TMixin(object):
> 
>    @declared_attr
>    def user(cls):
>        return Column(String, primary_key=True)
> 
>    @declared_attr
>    def timestamp(cls):
>        return Column(Integer, autoincrement=False, primary_key=True)
> 
> 
> class Timeline(TMixin, Base):
> 
>    snaptype = Column(Integer, primary_key=True, autoincrement=False)
> 
>    __mapper_args__ = {'polymorphic_on': snaptype}
> 
> 
> class CMixin(TMixin):
>    @declared_attr
>    def user(cls):
>        return Column(String, primary_key=True)
> 
>    @declared_attr
>    def timestamp(cls):
>        return Column(Integer, autoincrement=False, primary_key=True)
> 
>    @declared_attr
>    def __table_args__(cls):
>        return (ForeignKeyConstraint(
>                    ['user', 'timestamp'],
>                    ['Timeline.user', 'Timeline.timestamp']),{})
> 
>    @declared_attr
>    def __mapper_args__(cls):
>        return {'polymorphic_identity': CMixin.__subclasses__().index(cls)}
> 
> class C(CMixin, Timeline):
>    pass
> 
> >> ==========================================================
> >> from sqlalchemy import Column, String, ForeignKeyConstraint, Integer
> >> from sqlalchemy.ext.declarative import declarative_base, declared_attr
> >> 
> >> class TableName(object):
> >>   @declared_attr
> >>   
> >>   def __tablename__(cls):
> >>       return cls.__name__
> >> 
> >> Base=declarative_base(cls=TableName)
> >> 
> >> class TMixin(object):
> >>   @declared_attr
> >>   
> >>   def user(cls):
> >>       return Column(String, primary_key=True)
> >>   
> >>   @declared_attr
> >>   
> >>   def timestamp(cls):
> >>       return Column(Integer, autoincrement=False, primary_key=True)
> >> 
> >> class Timeline(TMixin, Base):
> >>   snaptype = Column(Integer, primary_key=True, autoincrement=False)
> >>   
> >>   __mapper_args__ = {'polymorphic_on': snaptype}
> >> 
> >> class CMixin(TMixin):
> >>   @declared_attr
> >>   
> >>   def __table_args__(cls):
> >>       return (ForeignKeyConstraint(
> >>       
> >>                   ['user', 'timestamp'],
> >>                   ['Timeline.user', 'Timeline.timestamp']),)
> >>   
> >>   @declared_attr
> >>   
> >>   def __mapper_args__(cls):
> >>       return {'polymorphic_identity':
> >>       CMixin.__subclasses__().index(cls)}
> >> 
> >> class C(CMixin, Timeline):
> >>   pass
> >> 
> >> ==========================================================
> >> 
> >> But on running this code, I get:
> >> ==========================================================
> >> Traceback (most recent call last):
> >> File "schema.py", line 45, in <module>
> >> 
> >>   class C(CMixin, Timeline):
> >> File
> >> "/usr/local/lib/python2.7/dist-packages/sqlalchemy/ext/declarative.py",
> >> line 1129, in __init__
> >> 
> >>   _as_declarative(cls, classname, cls.__dict__)
> >> 
> >> File
> >> "/usr/local/lib/python2.7/dist-packages/sqlalchemy/ext/declarative.py",
> >> line 1027, in _as_declarative
> >> 
> >>   **table_kw)
> >> 
> >> File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/schema.py", line
> >> 265, in __new__
> >> 
> >>   table._init(name, metadata, *args, **kw)
> >> 
> >> File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/schema.py", line
> >> 340, in _init
> >> 
> >>   self._init_items(*args)
> >> 
> >> File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/schema.py", line
> >> 64, in _init_items
> >> 
> >>   item._set_parent_with_dispatch(self)
> >> 
> >> File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/events.py", line
> >> 227, in _set_parent_with_dispatch
> >> 
> >>   self._set_parent(parent)
> >> 
> >> File "/usr/local/lib/python2.7/dist-packages/sqlalchemy/schema.py", line
> >> 2010, in _set_parent
> >> 
> >>   col = table.c[col]
> >> 
> >> File "/usr/local/lib/python2.7/dist-
> >> packages/sqlalchemy/util/_collections.py", line 88, in __getitem__
> >> 
> >>   return self._data[key]
> >> 
> >> KeyError: 'user'
> >> ==========================================================
> >> 
> >> I don't get any error if C isn't inheriting from Timeline (ie, no
> >> joint-table inheritance). What could be wrong here?
> >> 
> >> sqlalchemy.__version__ = '0.7.1'
> >> 
> >> And thanks in advance.

Attachment: signature.asc
Description: This is a digitally signed message part.



Reply via email to