I see now, thanks! It's fairly difficult to plug in the events as you 
suggested in my code, but I subclassed MappedCollection to discard setitem 
calls with a None key, and used this as the collection class. This seems to 
work, and if there's no other events I need to worry about during this 
append process, should be a complete solution. 

On Monday, July 8, 2013 9:14:02 PM UTC-5, Michael Bayer wrote:
>
> here's how you debug that:
>
> @event.listens_for(A.ab, "append")
> def append(target, value, initiator):
>     import pdb
>     pdb.set_trace()
>
> @event.listens_for(B.ab, "append")
> def append(target, value, initiator):
>     import pdb
>     pdb.set_trace()
>
> and if you're concerned about attribute-access side effects, in your pdb 
> you look at an object like:
>
> >> ab.__dict__
>
> no side effects that way (though there aren't any getter side effects in 
> this test).
>
> the sequence is:
>
> 1. create AB(a=a, b=b)
>
> 2. the AB has "a" set first, which then fires off the backref A.ab
>
> 3. AB is assigned to the A.ab dictionary, with key of None because AB.b is 
> None
>
> 4. AB.b is then assigned to "b"
>
> 5. AB.b fires off backref B.ab
>
> 6. the association proxy now gets involved, and appends your AB to the 
> A.ab collection again, this time with the correct key of "b"
>
> So if you just did the assignment without the association proxy (which is 
> a good idea when understanding this), you just get the key of None in aa.ab 
> and nothing else.   If you were to assign .b or .a to the AB first, you get 
> the same problem here in one direction or the other, because both AB.a and 
> AB.b will both try to assign it to a dictionary that requires the other 
> reference be present, it's a mutual referencing issue.
>
> It's an awkward mapping, one way to make it work is to just not use 
> backrefs and make your own event, though to make it work in both directions 
> without going into an endless loop would require a more intricate approach 
> (using internal appenders that pass along the "initiator" so you can stop 
> an endless setter loop).  Below is just the one direction:
>
> class A(Base):
>     __tablename__ = 'table_a'
>     id = Column(Integer, primary_key=True)
>     ab = relationship('AB',
>                       collection_class=attribute_mapped_collection('b'))
>     abnum = correlated_proxy('ab', 'num', correlator=corr)
>
> class AB(Base):
>     __tablename__ = 'table_ab'
>     num = Column(Integer)
>     a_id = Column(Integer, ForeignKey('table_a.id'), primary_key=True)
>     b_id = Column(Integer, ForeignKey('table_b.id'), primary_key=True)
>     a = relationship("A")
>     b = relationship("B")
>
> class B(Base):
>     __tablename__ = 'table_b'
>     id = Column(Integer, primary_key=True)
>     ab = relationship('AB',
>                       collection_class=attribute_mapped_collection('a')
>                       )
>
> @event.listens_for(A.ab, "append")
> def append(target, value, initiator):
>     value.b.ab[value.a] = value
>
>
>
>
>
>
>
>
> On Jul 8, 2013, at 9:07 PM, Greg Yang <sorcer...@gmail.com <javascript:>> 
> wrote:
>
> I created a class CorrelatedProxy inheriting from AssociationProxy that 
> allows the creator function to depend on the owner instance of the 
> association proxy. Essentially it gets a attribute 'correlator' of the 
> something like lambda x: lambda y, z: Constructor(x, y, z), and then 
> intercepts the __get__ of AssociationProxy to create self.creator on the 
> fly by applying the owner instance to the correlator. Now consider the code 
> below.
>
> from sqlalchemy.engine import create_engine
> from sqlalchemy.ext.declarative.api import declarative_base
> from sqlalchemy.orm import relationship
> from sqlalchemy.orm.collections import attribute_mapped_collection
> from sqlalchemy.orm.session import sessionmaker
> from sqlalchemy.schema import Column, ForeignKey
> from sqlalchemy.types import Integer
> from sqlalchemy.ext.associationproxy import AssociationProxy
>
> class CorrelatedProxy(AssociationProxy):
>     def __init__(self, *args, **kw):
>         self.correlator = kw.pop('correlator', None)
>         AssociationProxy.__init__(self, *args, **kw)
>     def __get__(self, obj, class_):
>         if obj:
>             self.creator = self.correlator(obj)
>         return AssociationProxy.__get__(self, obj, class_)
>         
> def correlated_proxy(*args, **kw):
>     return CorrelatedProxy(*args, **kw)
>
>
> Base = declarative_base()
>
> class A(Base):
>     __tablename__ = 'table_a'
>     id = Column(Integer, primary_key=True)
>     ab = relationship('AB', backref = 'a', 
>                       collection_class=attribute_mapped_collection('b'))
>     abnum = correlated_proxy('ab', 'num', correlator=\
>                              lambda a: lambda b, n: AB(a=a, b=b, num=n))
> class AB(Base):
>     __tablename__ = 'table_ab'
>     num = Column(Integer)
>     a_id = Column(Integer, ForeignKey('table_a.id'), primary_key=True)
>     b_id = Column(Integer, ForeignKey('table_b.id'), primary_key=True)
>     
> class B(Base):
>     __tablename__ = 'table_b'
>     id = Column(Integer, primary_key=True)
>     ab = relationship('AB', backref = 'b', 
>                       collection_class=attribute_mapped_collection('a'))
>     
>     
> if __name__ == '__main__':
>     engine = create_engine('sqlite:///:memory:')
>     Session = sessionmaker(engine)
>     session = Session()
>     Base.metadata.create_all(engine)
>     
>     aa = A()
>     bb = B()
>     aa.abnum[bb] = 1
>     assert aa.abnum[bb] == aa.abnum[None] == 1
>
> Basically, no matter, what I do, any time I assign something to the 
> CorrelatedProxy, everything goes normally except that 'None' always becomes 
> a key, assigned to the last value I assigned to the proxy. I tried 
> debugging and tracing, but there's some quantum effect going on where if I 
> inspect some value, some other value changes. I for the life of me can't 
> figure out why it's doing this. I'm guessing it's some Instrumentation 
> effect of SA, but I don't understand the in and outs of that very much. I 
> currently can work around this by filtering out the None, but it'd be nice 
> to know why this occurs and whether it will affect any other elements of my 
> program with whatever is going on underneath.
>     
>
> -- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to sqlalchemy+...@googlegroups.com <javascript:>.
> To post to this group, send email to sqlal...@googlegroups.com<javascript:>
> .
> Visit this group at http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/groups/opt_out.
>  
>  
>
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to