OK, i was oversimplifying.  if you really want a total bi-directional  
many-to-many relationship where the association object is essentially  
invisible, you have to set up proxies in both directions.  The  
attached script illustrates an almost generic way of doing this  
which, after a few more iterations, could possibly become an  
extension to SA so that folks could re-use this approach without the  
ugliness.  It basically sets up a "proxy" list that translates the  
association instances in both directions to the underlying target  
object.   in this example, both sides of the association are many-to- 
many.  further property tricks can be used to make one side of it  
scalar instead....



--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/sqlalchemy
-~----------~----~----~----~------~----~------~--~---
from sqlalchemy import *
m = BoundMetaData('sqlite://', echo=True)

objecttable = Table('objects', m,
    Column('id', Integer, primary_key=True),
    Column('data', String(30))
    )

associationtable = Table('association', m,
    Column('oid', Integer, ForeignKey('objects.id'), primary_key=True),
    Column('iid', Integer, ForeignKey('items.id'), primary_key=True))

itemtable = Table('items', m, 
    Column('id', Integer, primary_key=True),
    Column('data', String(30))
    )
    
m.create_all()

class Association(object):
    """association object"""
    pass

class AssociationList(object):
    """generic proxying list which proxies list operations to a different 
    list-holding attribute of the parent object, converting Association objects
    to and from a target attribute on each Association object."""
    def __init__(self, parent, collectionname, attr):
        """create a new AssociationList.
        
        parent - the parent object that contains this list
        collectionname - the attribute name which stores the collection of Associations
        attr - name of the attribute on the Association in which to get/set target values
        """
        self.parent = parent
        self.collectionname = collectionname
        self.attr = attr
    def append(self, item):
        a = Association()
        setattr(a, self.attr, item)
        getattr(self.parent, self.collectionname).append(a)
    def __iter__(self):
        return iter([getattr(x, self.attr) for x in getattr(self.parent, self.collectionname)])
    def __repr__(self):
        return repr([getattr(x, self.attr) for x in getattr(self.parent, self.collectionname)])
    def __len__(self):
        return len(getattr(self.parent, self.collectionname))
    def __getitem__(self, index):
        return getattr(getattr(self.parent, self.collectionname)[index], self.attr)
    def __setitem__(self, index, value):
        a = Association()
        setattr(a, self.attr, value)
        getattr(self.parent, self.collectionname)[index] = a
        
class AssocProp(object):
    """a property object that automatically sets up AssociationLists on a parent object."""
    def __init__(self, collectionname, attr):
        """create a new association property.
        
        collectionname - the attribute name which stores the collection of Associations
        attr - name of the attribute on the Association in which to get/set target values
        """
        self.collectionname = collectionname
        self.attr = attr
    def __get__(self, obj, owner):
        if obj is None:
            return self
        storage_key = '_AssocProp_%s' % self.collectionname
        try:
            return getattr(obj, storage_key)
        except AttributeError:
            a = AssociationList(obj, self.collectionname, self.attr)
            setattr(obj, storage_key, a)
            return a
            
class MyObject(object):
    items = AssocProp('associations', 'item')

class Item(object):
    myobjs = AssocProp('associations', 'myobj')
    
mapper(MyObject, objecttable)
mapper(Item, itemtable)

mapper(Association, associationtable, properties={
    'myobj':relation(MyObject, backref='associations'),
    'item':relation(Item, backref='associations')
})

s = create_session()

o = MyObject()
o.items.append(Item())
o.items.append(Item())

i = Item()
i.myobjs.append(o)

s.save(o)
s.flush()
s.clear()

o = s.query(MyObject).get(o.id)
print o
print o.items
assert len(o.items) == 3
assert o in o.items[1].myobjs


On Nov 1, 2006, at 7:09 PM, Karl Guertin wrote:

>
> On 11/1/06, Michael Bayer <[EMAIL PROTECTED]> wrote:
>> (moving to google groups)
>
> Eh, sorry cached email address.
>
>> the idea of using an instrumented list subclass is that you *would*
>> use the association object, and just have the list subclass handle
>> the references to the association object for you, i.e.:
>
> Simple enough
>
>> class MyList(list):
>>      def append(self, item):
>>          super(MyList, self).append(new ItemAssociation(item))
>>      def __getitem__(self, index):
>>          return super(MyList, self).__getitem__(index).item
>>
>> ..etc
>
> I'm not understanding what's happening in the append. I know item is
> one side of my relation, but how to I get access to the other? Is the
> InstrumentedList magic supposed to handle this somehow?
>
> --~--~---------~--~----~------------~-------~--~----~
> You received this message because you are subscribed to the Google  
> Groups "sqlalchemy" group.
> To post to this group, send email to sqlalchemy@googlegroups.com
> To unsubscribe from this group, send email to sqlalchemy- 
> [EMAIL PROTECTED]
> For more options, visit this group at http://groups.google.com/ 
> group/sqlalchemy
> -~----------~----~----~----~------~----~------~--~---
>

Reply via email to