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 <[email protected] <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 [email protected] <javascript:>.
> To post to this group, send email to [email protected]<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 [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.