Атрибут как диктат списков, использующий промежуточную таблицу с SQLAlchemy - PullRequest
3 голосов
/ 08 октября 2009

Мой вопрос касается SQLAlchemy, но у меня возникают проблемы с объяснением его словами, поэтому я решил объяснить это простым примером того, чего я пытаюсь достичь:

parent = Table('parent', metadata,
    Column('parent_id', Integer, primary_key=True),
    Column('name', Unicode),
)
parent_child = Table('parent_child', metadata,
    Column('parent_id', Integer, primary_key=True),
    Column('child_id', Integer, primary_key=True),
    Column('number', Integer),
    ForeignKeyConstraint(['parent_id'], ['parent.parent_id']),
    ForeignKeyConstraint(['child_id'], ['child.child_id']),
)
child = Table('child', metadata,
    Column('child_id', Integer, primary_key=True),
    Column('name', Unicode),
)
class Parent(object):
    pass
class ParentChild(object):
    pass
class Child(object):
    pass

>>> p = Parent(name=u'A')
>>> print p.children 
{}
>>> p.children[0] = Child(name=u'Child A')
>>> p.children[10] = Child(name=u'Child B')
>>> p.children[10] = Child(name=u'Child C')

Этот код будет создавать 3 строки в таблице parent_child, номер столбца будет 0 для первой строки и 10 для второй и третьей строки.

>>> print p.children 
{0: [<Child A>], 10: [<Child B>, <Child C>]}
>>> print p.children[10][0]
<Child B>

(я пропустил весь код сеанса / движка SQLAlchemy в примере, чтобы сделать его максимально чистым)

Я попытался, используя´

collection_class=attribute_mapped_collection('number') 

об отношениях между Parent и ParentChild, но это дало мне только одного ребенка на каждое число. Не диктат со списками в нем. Любая помощь приветствуется!

ОБНОВЛЕНО!

Благодаря Денису Откидачу у меня теперь есть этот код, но он все еще не работает.

def _create_children(number, child):
    return ParentChild(parent=None, child=child, number=number)

class Parent(object):
    children = association_proxy('_children', 'child', creator=_create_children)

class MyMappedCollection(MappedCollection):
    def __init__(self):
        keyfunc = lambda attr_name: operator.attrgetter('number')
        MappedCollection.__init__(self, keyfunc=keyfunc)

    @collection.appender
    @collection.internally_instrumented
    def set(self, value, _sa_initiator=None):
        key = self.keyfunc(value)
        try:
            self.__getitem__(key).append(value)
        except KeyError:
            self.__setitem__(key, [value])

mapper(Parent, parent, properties={
     '_children': relation(ParentChild, collection_class=MyMappedCollection),
})

Чтобы вставить ребенка, кажется, работает

p.children[100] = Child(...)

Но когда я пытаюсь напечатать детей так:

print p.children

Я получаю эту ошибку:

sqlalchemy.orm.exc.UnmappedInstanceError: Class '__builtin__.list' is not mapped

1 Ответ

2 голосов
/ 09 октября 2009

Вы должны определить свой собственный класс коллекции. Есть только 3 способа реализации: appender, remover и converter. См. sqlalchemy.orm.collections.MappedCollection в качестве примера.

Обновление: Вот реализация quick-n-dirty согласно вашим требованиям:

from sqlalchemy import *
from sqlalchemy.orm import mapper, relation, sessionmaker
from sqlalchemy.orm.collections import collection

metadata = MetaData()

parent = Table('parent', metadata,
    Column('parent_id', Integer, primary_key=True),
    Column('name', Unicode),
)

child = Table('child', metadata,
    Column('child_id', Integer, primary_key=True),
    Column('name', Unicode),
)

parent_child = Table('parent_child', metadata,
    Column('parent_id', Integer, ForeignKey(parent.c.parent_id)),
    Column('child_id', Integer, ForeignKey(child.c.child_id)),
    Column('number', Integer),
    PrimaryKeyConstraint('parent_id', 'child_id'),
)

class ParentChild(object):
    def __init__(self, child, number):
        self.child = child
        self.number = number

class Parent(object): pass

class Child(object): pass


class MyMappedCollection(object):

    def __init__(self, data=None):
        self._data = data or {}

    @collection.appender
    def _append(self, parent_child):
        l = self._data.setdefault(parent_child.number, [])
        l.append(parent_child)

    def __setitem__(self, number, child):
        self._append(ParentChild(number=number, child=child))

    def __getitem__(self, number):
        return tuple(pc.child for pc in self._data[number])

    @collection.remover
    def _remove(self, parent_child):
        self._data[parent_child.number].remove(parent_child)

    @collection.iterator
    def _iterator(self):
        for pcs in self._data.itervalues():
            for pc in pcs:
                yield pc

    def __repr__(self):
        return '%s(%r)' % (type(self).__name__, self._data)


mapper(Parent, parent, properties={
     'children': relation(ParentChild, collection_class=MyMappedCollection),
})
mapper(Child, child)
mapper(ParentChild, parent_child, properties={
    'parent': relation(Parent),
    'child': relation(Child),
})

engine = create_engine('sqlite://')
db = sessionmaker(bind=engine)()
metadata.create_all(bind=engine)

p = Parent()
c1 = Child()
c2 = Child()
c3 = Child()
p.children[1] = c1
p.children[1] = c2
p.children[2] = c3

db.add(p)
db.commit()
p_id = p.parent_id
db.expunge_all()

p = db.query(Parent).get(p_id)
print p.children[1]
print p.children[2]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...