Python 在SQLAlchemy中,同一个@property可以同时呈现标量和集合行为吗?
我正在转换一个库以用作数据存储。我喜欢PickleType列的灵活性,但在pickle SA对象(表行)时,它似乎不能很好地工作。即使我在取消pickle时重载setstate和getstate来执行查询+会话合并,也没有跨越pickle边界的引用完整性。这意味着我不能查询对象集合Python 在SQLAlchemy中,同一个@property可以同时呈现标量和集合行为吗?,python,database,orm,properties,sqlalchemy,Python,Database,Orm,Properties,Sqlalchemy,我正在转换一个库以用作数据存储。我喜欢PickleType列的灵活性,但在pickle SA对象(表行)时,它似乎不能很好地工作。即使我在取消pickle时重载setstate和getstate来执行查询+会话合并,也没有跨越pickle边界的引用完整性。这意味着我不能查询对象集合 class Bar(Base): id = Column(Integer, primary_key=True) __tablename__ = 'bars' foo_id = Column(I
class Bar(Base):
id = Column(Integer, primary_key=True)
__tablename__ = 'bars'
foo_id = Column(Integer, ForeignKey('foos.id'), primary_key=True)
class Foo(Base):
__tablename__ = 'foos'
values = Column(PickleType)
#values = relationship(Bar) # list interface (one->many), but can't assign a scalar or use a dictionary
def __init__(self):
self.values = [Bar(), Bar()]
# only allowed with PickleType column
#self.values = Bar()
#self.values = {'one' : Bar()}
#self.values = [ [Bar(), Bar()], [Bar(), Bar()]]
# get all Foo's with a Bar whose id=1
session.query(Foo).filter(Foo.values.any(Bar.id == 1)).all()
一种解决方法是实现我自己的可变对象类型。我想象着有一种扁平化方案,它遍历集合并将它们附加到一个简单的一->多关系中。也许扁平化列表可能必须是酸洗集合对象的薄弱环节
跟踪更改和引用听起来没有什么乐趣,我在其他地方也找不到任何人酸洗SA行的例子(可能表明我的设计不好?)。有什么建议吗
编辑1:
经过一段时间后,我简化了请求。我正在寻找一个可以表现为标量或集合的单个属性。以下是我(失败的)尝试:
PickleType实际上是一种对付边缘情况的黑客方法,在这种情况下,你有一些任意的对象,你只想把它们扔掉。当您使用PickleType时,您已经放弃了任何关系优势,包括能够对它们进行过滤/查询,等等 因此,将ORM映射对象放入Pickle中基本上是一个糟糕的想法 如果需要标量值的集合,请将传统映射和relationship()与association\u proxy结合使用。看 “或字典”。使用属性映射集合: “字典加标量”:结合属性映射集合和关联代理: 编辑1: 好吧,你挖掘了一个非常深奥复杂的例子。关联_代理是一种更简单的方法,可以绕过这样的情况:您希望对象的行为像标量一样,因此,这里没有“垂直”示例的疯狂样板,我会避免,因为它实在太复杂了。您的示例似乎还没有确定主键样式,所以我选择了复合版本。代理+组合不能在一个表中混合使用(当然可以,但它在关系上是不正确的。键应该是标识行的最小单位-这是一个很好的顶级读入各种主题的方法)
PickleType实际上是一种对付边缘情况的黑客方法,在这种情况下,你有一些任意的对象,你只想把它们扔掉。当您使用PickleType时,您已经放弃了任何关系优势,包括能够对它们进行过滤/查询,等等 因此,将ORM映射对象放入Pickle中基本上是一个糟糕的想法 如果需要标量值的集合,请将传统映射和relationship()与association\u proxy结合使用。看 “或字典”。使用属性映射集合: “字典加标量”:结合属性映射集合和关联代理: 编辑1: 好吧,你挖掘了一个非常深奥复杂的例子。关联_代理是一种更简单的方法,可以绕过这样的情况:您希望对象的行为像标量一样,因此,这里没有“垂直”示例的疯狂样板,我会避免,因为它实在太复杂了。您的示例似乎还没有确定主键样式,所以我选择了复合版本。代理+组合不能在一个表中混合使用(当然可以,但它在关系上是不正确的。键应该是标识行的最小单位-这是一个很好的顶级读入各种主题的方法)
from sqlalchemy import MetaData, Column, Integer, PickleType, String, ForeignKey, create_engine
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.collections import attribute_mapped_collection
# from http://www.sqlalchemy.org/trac/browser/examples/vertical
from sqlalchemy_examples.vertical import dictlike_polymorphic as dictlike
metadata = MetaData()
Base = declarative_base()
engine = create_engine('sqlite://', echo=True)
Base.metadata.bind = engine
session = Session(engine)
class AnimalFact(dictlike.PolymorphicVerticalProperty, Base):
"""key/value attribute whose value can be one of several types"""
__tablename__ = 'animalfacts'
type_map = {#str: ('string', 'str_value'),
list: ('list', 'list_value'),
tuple: ('tuple', 'tuple_value')}
id = Column(Integer, primary_key=True)
animal_id = Column(Integer, ForeignKey('animal.id'), primary_key=True)
key = Column(String, primary_key=True)
type = Column(String)
#str_value = Column(String)
list_value = relationship('StringEntry')
tuple_value = relationship('StringEntry2')
class Animal(Base, dictlike.VerticalPropertyDictMixin):
__tablename__ = 'animal'
_property_type = AnimalFact
_property_mapping = 'facts'
id = Column(Integer, primary_key=True)
name = Column(String)
facts = relationship(AnimalFact, backref='animal',
collection_class=attribute_mapped_collection('key'))
def __init__(self, name):
self.name = name
class StringEntry(Base):
__tablename__ = 'stringentry'
id = Column(Integer, primary_key=True)
animalfacts_id = Column(Integer, ForeignKey('animalfacts.id'))
value = Column(String)
def __init__(self, value):
self.value = value
class StringEntry2(Base):
__tablename__ = 'stringentry2'
id = Column(Integer, primary_key=True)
animalfacts_id = Column(Integer, ForeignKey('animalfacts.id'))
value = Column(String)
def __init__(self, value):
self.value = value
Base.metadata.create_all()
a = Animal('aardvark')
a['eyes'] = [StringEntry('left side'), StringEntry('right side')] # works great
a['eyes'] = (StringEntry2('left side'), StringEntry2('right side')) # works great
#a['cute'] = 'sort of' # failure
from sqlalchemy import Integer, String, Column, create_engine, ForeignKey, ForeignKeyConstraint
from sqlalchemy.orm import relationship, Session
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.associationproxy import association_proxy
Base = declarative_base()
class AnimalFact(Base):
"""key/value attribute whose value can be either a string or a list of strings"""
__tablename__ = 'animalfacts'
# use either surrogate PK id, or the composite animal_id/key - but
# not both. id/animal_id/key all together is not a proper key.
# Personally I'd go for "id" here, but here's the composite version.
animal_id = Column(Integer, ForeignKey('animal.id'), primary_key=True)
key = Column(String, primary_key=True)
# data
str_value = Column(String)
_list_value = relationship('StringEntry')
# proxy list strings
list_proxy = association_proxy('_list_value', 'value')
def __init__(self, key, value):
self.key = key
self.value = value
@property
def value(self):
if self.str_value is not None:
return self.str_value
else:
return self.list_proxy
@value.setter
def value(self, value):
if isinstance(value, basestring):
self.str_value = value
elif isinstance(value, list):
self.list_proxy = value
else:
assert False
class Animal(Base):
__tablename__ = 'animal'
id = Column(Integer, primary_key=True)
name = Column(String)
_facts = relationship(AnimalFact, backref='animal',
collection_class=attribute_mapped_collection('key'))
facts = association_proxy('_facts', 'value')
def __init__(self, name):
self.name = name
# dictionary interface around "facts".
# I'd just use "animal.facts" here, but here's how to skip that.
def __getitem__(self, key):
return self.facts.__getitem__(key)
def __setitem__(self, key, value):
self.facts.__setitem__(key, value)
def __delitem__(self, key):
self.facts.__delitem__(key)
def __contains__(self, key):
return self.facts.__contains__(key)
def keys(self):
return self.facts.keys()
class StringEntry(Base):
__tablename__ = 'myvalue'
id = Column(Integer, primary_key=True)
animal_id = Column(Integer)
key = Column(Integer)
value = Column(String)
# because AnimalFact has a composite PK, we need
# a composite FK.
__table_args__ = (ForeignKeyConstraint(
['key', 'animal_id'],
['animalfacts.key', 'animalfacts.animal_id']),
)
def __init__(self, value):
self.value = value
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
# create a new animal
a = Animal('aardvark')
a['eyes'] = ['left side', 'right side']
a['cute'] = 'sort of'
session.add(a)
session.commit()
session.close()
for animal in session.query(Animal):
print animal.name, ",".join(["%s" % animal[key] for key in animal.keys()])