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 <sorcerero...@gmail.com> 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+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.
>  
>  

-- 
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