Python 为SQLAlchemy多对多数据库设置关系/映射
我不熟悉SQLAlchemy和关系数据库,我正在尝试为带注释的词典建立一个模型。我希望支持任意数量的关键字值注释,这些注释可以在运行时添加或删除。由于键的名称会有很多重复,我不想直接使用,尽管代码类似 我的设计有word对象和property对象。单词和属性存储在单独的表中,带有链接这两个单词和属性的属性值表。代码如下:Python 为SQLAlchemy多对多数据库设置关系/映射,python,orm,sqlalchemy,Python,Orm,Sqlalchemy,我不熟悉SQLAlchemy和关系数据库,我正在尝试为带注释的词典建立一个模型。我希望支持任意数量的关键字值注释,这些注释可以在运行时添加或删除。由于键的名称会有很多重复,我不想直接使用,尽管代码类似 我的设计有word对象和property对象。单词和属性存储在单独的表中,带有链接这两个单词和属性的属性值表。代码如下: from sqlalchemy import Column, Integer, String, Table, create_engine from sqlalchemy imp
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
添加到属性值表中。但是,我没有合适的映射和关系来实现这一点。我在阅读文档时感觉到,我想在这里使用关联代理或类似的东西,但语法对我来说并不清楚。我尝试过这个:
mapper(Word, words, properties={
'properties': relation(Property, secondary=property_values)
})
但是这个映射器只填充外键值,我还需要填充其他值。任何帮助都将不胜感激。有一个接口略有不同。但是,通过定义\uu getitem\uuuuuuuuu
、\uuuuu setitem\uuuuuuu
和\uuuuuu delitem\uuuuuuuu
方法,很容易解决这个问题。只需使用现成的映射解决方案即可解决问题。从链接中摘录:
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']
或者尝试解决问题的方法。显然,您必须将
列表
逻辑替换为dict
逻辑。请问题作者用
associationproxy
发布他最后的代码,他最后提到了他使用的。我最终将Denis和van的帖子结合在一起形成了解决方案:
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
布伦特的评论,见上文:
您可以使用
session.flush()
而不是commit()
来获取模型实例上的id
flush()
将执行必要的SQL,但不会提交,因此您可以在需要时稍后回滚。感谢您提供的指针。我有一些类似的东西,但是我想将键名作为外键来维护,因为键中会有很多重复,我不想在数据库中复制所有这些字符串。我正在考虑将你的解决方案与上面van的解决方案相结合[.这是最简单的方法,但我的许多条目都有相同的键,因此我想对这些键进行唯一化,并将它们存储在单独的表中。我正在查看您发布的第二个解决方案。这很公平。但是,是什么阻止您使用基于字典的集合映射到对象上的属性,但除此之外,还只提供类似dict的属性对象上的接口,它基本上是这个属性的代理(委托)?我认为这基本上就是我在解决方案中提出的(发布在下面).在_create_propval中,你真的需要添加/提交新的属性对象吗?当你稍后提交会话时,它不会自动添加/提交吗?如果是,那么我不会在那里添加/提交,因为你可能会回滚会话而不是提交。是的,这是一个粗略的代码部分。我需要属性对象来获取id,所以创建PropertyValue时我可以引用它(因为PropertyValue使用外键来引用属性名称)。属性在提交之前不会获取其id。有没有不提交就获取id的好方法?对于记录,解决方案是将_create_propval中的s.commit()行替换为s.merge(p)。