Hi Michael, Thank you very much for your response!
I'll try to code some workaround, and post it here if it works. Thank you again! 2011/8/3 Michael Bayer <[email protected]> > > On Aug 3, 2011, at 12:32 PM, Pau Tallada wrote: > > Hi! > > I have a model with a many-to-many relation, similar to the > Broker/Holding/Stocks example in the docs. I've set up relationships between > both sides using the association object pattern. Finally, I've set up the > collection class of those relationships to an > attribute_mapped_collection. It is expected that when an instance of Holding > is created, it should appear on BOTH attribute_mapped_collections, but the > KEY is only present on one of them. > > Following there is a small adaptation of the Broker/Stocks/Holding model to > explain the problem. > > from sqlalchemy import Column, ForeignKey, MetaData, Table > from sqlalchemy import Integer, String, Numeric > from sqlalchemy.orm import mapper, relationship > from sqlalchemy.orm.collections import attribute_mapped_collection > meta = MetaData() > stocks_table = Table("stocks", meta, > Column('symbol', String(10), primary_key=True), > Column('last_price', Numeric) > ) > brokers_table = Table("brokers", meta, > Column('id', Integer,primary_key=True), > Column('name', String(100), nullable=False) > ) > holdings_table = Table("holdings", meta, > Column('broker_id', Integer, ForeignKey('brokers.id'), > primary_key=True), > Column('symbol', String(10), ForeignKey('stocks.symbol'), > primary_key=True), > Column('shares', Integer) > ) > class Broker(object): > def __init__(self, name): > self.name = name > class Stock(object): > def __init__(self, symbol): > self.symbol = symbol > self.last_price = 0 > class Holding(object): > def __init__(self, broker=None, stock=None, shares=0): > self.broker = broker > self.stock = stock > self.shares = shares > mapper(Stock, stocks_table, properties={ > 'by_broker': relationship(Holding, back_populates="stock", > collection_class=attribute_mapped_collection('broker')) > }) > mapper(Broker, brokers_table, properties={ > 'by_stock': relationship(Holding, back_populates="broker", > collection_class=attribute_mapped_collection('stock')) > }) > mapper(Holding, holdings_table, properties={ > 'stock': relationship(Stock, back_populates="by_broker"), > 'broker': relationship(Broker, back_populates="by_stock") > }) > broker = Broker('paj') > stock = Stock('ZZK') > holding = Holding(broker, stock, 10) > print stock.by_broker > print broker.by_stock > > The expected behaviour would be: > > {<__main__.Broker object at 0xefce10>: <__main__.Holding object at > 0xf00910>} > {<__main__.Stock object at 0x1c74190>: <__main__.Holding object at > 0xf00910>} > > > But I get: > > {<__main__.Broker object at 0xefce10>: <__main__.Holding object at > 0xf00910>} > {*None*: <__main__.Holding object at 0xf00910>} > > Debugging in the code of sqlalchemy I assume the problem is that the > addition of the Holding instance in the attribute_mapped_collections is done > in two steps. During Holding object creation, when the first attribute > (broker) is set, it fires an event and causes its addition to the > broker.by_stock collection, but as stock is still not set, the key is None. > Then, when the second attribute (stock) is set, it get correctly added to > the collection as broker value is already set. > > > that would appear to be correct. > > As far as a bug, mmm, how could such a thing be worked around in an > automated fashion ? Attribute set events are just that. Either the > backref events would not fire off at all, waiting for the database round > trip to populate (just don't use back_populates to achieve that), or the > backref events would be delayed until after constructor, but that is quite > awkward and arbitrary, as the same kinds of events can happen in any other > number of ways besides being set within the constructor. > > There's another behavior, which is that a backref populates the incoming > record into the so-called "pending" collection for an unloaded collection, > which populates upon access of the collection after the database population > has occurred. This behavior is to save on SQL calls for backref events, and > it does not kick in on a transient or pending object since there's no > collection to load. Some awkward mechanics could allow this behavior to > occur on a pending/transient, but then the moment you access the actual > dictionary for a read, its loaded, and then you'd have this undesirable > behavior again for subsequent populations of Holding. As long as the > dictionary we're dealing with is just a plain dict and not some dynamic view > type of thing (which is in fact a feature here), I'm not seeing how this > kind of issue is avoidable (don't take that to mean there's no solution > though...I can't say for sure). > > It would appear the simplest approach is to just populate the dictionaries > directly: > > class Holding(object): > def __init__(self, broker, stock): > stock.by_broker[broker] = self > broker.by_stock[stock] = self > > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To post to this group, send email to [email protected]. > To unsubscribe from this group, send email to > [email protected]. > For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. > -- ---------------------------------- Pau Tallada Crespí Dep. d'Astrofísica i Cosmologia Port d'Informació Científica (PIC) Tel: +34 93 586 8233 ---------------------------------- -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.
