Настройка отношений / отображений для базы данных SQLAlchemy «многие ко многим» - PullRequest
2 голосов
/ 23 февраля 2010

Я новичок в SQLAlchemy и реляционных базах данных и пытаюсь создать модель для аннотированной лексики. Я хочу поддержать произвольное количество аннотаций значения ключа для слов, которые могут быть добавлены или удалены во время выполнения. Поскольку в именах клавиш будет много повторений, я не хочу использовать это решение напрямую, хотя код похож.

В моем дизайне есть текстовые объекты и объекты свойств. Слова и свойства хранятся в отдельных таблицах с таблицей property_values, которая связывает их. Вот код:

from sqlalchemy import Column, Integer, String, Table, create_engine
from sqlalchemy import MetaData, ForeignKey
from sqlalchemy.orm import relation, mapper, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///test.db', echo=True)
meta = MetaData(bind=engine)

property_values = Table('property_values', meta,
    Column('word_id', Integer, ForeignKey('words.id')),
    Column('property_id', Integer, ForeignKey('properties.id')),
    Column('value', String(20))
)
words = Table('words', meta,
    Column('id', Integer, primary_key=True),
    Column('name', String(20)),
    Column('freq', Integer)
)
properties = Table('properties', meta,
    Column('id', Integer, primary_key=True),
    Column('name', String(20), nullable=False, unique=True)
)
meta.create_all()

class Word(object):
    def __init__(self, name, freq=1):
        self.name = name
        self.freq = freq

class Property(object):
    def __init__(self, name):
        self.name = name
mapper(Property, properties)  

Теперь я бы хотел сделать следующее:

Session = sessionmaker(bind=engine)
s = Session()
word = Word('foo', 42)
word['bar'] = 'yes' # or word.bar = 'yes' ?
s.add(word)
s.commit()

В идеале это должно добавить 1|foo|42 в таблицу слов, добавить 1|bar в таблицу свойств и добавить 1|1|yes в таблицу property_values. Тем не менее, у меня нет правильных сопоставлений и отношений, чтобы это произошло. Читая документацию по http://www.sqlalchemy.org/docs/05/mappers.html#association-pattern, я чувствую, что хочу использовать прокси-сервер ассоциации или что-то в этом роде, но синтаксис для меня неясен. Я экспериментировал с этим:

mapper(Word, words, properties={
    'properties': relation(Property, secondary=property_values)
    })

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

Ответы [ 4 ]

1 голос
/ 17 мая 2010

Комментарий для Brent, выше:

Вы можете использовать session.flush() вместо commit(), чтобы получить id на экземплярах модели. flush() выполнит необходимый SQL, но не выполнит фиксацию, поэтому при необходимости вы сможете откатиться позже.

1 голос
/ 23 февраля 2010

Просто используйте Словарь на основе сопоставления коллекций Отображение - из коробки решение вашего вопроса. Выписка из ссылки:

from sqlalchemy.orm.collections import column_mapped_collection, attribute_mapped_collection, mapped_collection

mapper(Item, items_table, properties={
    # key by column
    'notes': relation(Note, collection_class=column_mapped_collection(notes_table.c.keyword)),
    # or named attribute
    'notes2': relation(Note, collection_class=attribute_mapped_collection('keyword')),
    # or any callable
    'notes3': relation(Note, collection_class=mapped_collection(lambda entity: entity.a + entity.b))
})

# ...
item = Item()
item.notes['color'] = Note('color', 'blue')
print item.notes['color']

Или попробуйте решение для Вставка данных во взаимосвязи «Многие ко многим» в SQLAlchemy . Очевидно, вы должны заменить логику list на dict.
Задайте вопрос автору, чтобы опубликовать его окончательный код с associationproxy, который, как он упоминал, он использовал в конце.

1 голос
/ 23 февраля 2010

Существует очень похожий вопрос с небольшой разницей в интерфейсе. Но это легко исправить, определив методы __getitem__, __setitem__ и __delitem__.

0 голосов
/ 24 февраля 2010

В итоге я объединил посты Дениса и Вана, чтобы сформировать решение:

from sqlalchemy import Column, Integer, String, Table, create_engine
from sqlalchemy import MetaData, ForeignKey
from sqlalchemy.orm import relation, mapper, sessionmaker
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base

meta = MetaData()
Base = declarative_base(metadata=meta, name='Base')

class PropertyValue(Base):
    __tablename__ = 'property_values'
    WordID = Column(Integer, ForeignKey('words.id'), primary_key=True)
    PropID = Column(Integer, ForeignKey('properties.id'), primary_key=True)
    Value = Column(String(20))

def _property_for_name(prop_name):
    return s.query(Property).filter_by(name=prop_name).first()

def _create_propval(prop_name, prop_val):
    p = _property_for_name(prop_name)
    if not p:
        p = Property(prop_name)
        s.add(p)
        s.commit()
    return PropertyValue(PropID=p.id, Value=prop_val)

class Word(Base):
    __tablename__ = 'words'
    id = Column(Integer, primary_key=True)
    string = Column(String(20), nullable=False)
    freq = Column(Integer)
    _props = relation(PropertyValue, collection_class=attribute_mapped_collection('PropID'), cascade='all, delete-orphan')
    props = association_proxy('_props', 'Value', creator=_create_propval)

    def __init__(self, string, freq=1):
        self.string = string
        self.freq = freq

    def __getitem__(self, prop):
        p = _property_for_name(prop)
        if p:
            return self.props[p.id]
        else:
            return None

    def __setitem__(self, prop, val):
        self.props[prop] = val

    def __delitem__(self, prop):
        p = _property_for_name(prop)
        if p:
            del self.props[prop]

class Property(Base):
    __tablename__ = 'properties'
    id = Column(Integer, primary_key=True)
    name = Column(String(20), nullable=False, unique=True)

    def __init__(self, name):
        self.name = name

engine = create_engine('sqlite:///test.db', echo=False)
Session = sessionmaker(bind=engine)
s = Session()
meta.create_all(engine)

Тестовый код выглядит следующим образом:

word = Word('foo', 42)
word['bar'] = "yes"
word['baz'] = "certainly"
s.add(word)

word2 = Word('quux', 20)
word2['bar'] = "nope"
word2['groink'] = "nope"
s.add(word2)
word2['groink'] = "uh-uh"
del word2['bar']

s.commit()

word = s.query(Word).filter_by(string="foo").first()
print word.freq, word['baz']
# prints 42 certainly

Содержимое баз данных:

$ sqlite3 test.db "select * from property_values"
1|2|certainly
1|1|yes
2|3|uh-uh
$ sqlite3 test.db "select * from words"
1|foo|42
2|quux|20
$ sqlite3 test.db "select * from properties"
1|bar
2|baz
3|groink
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...