SQLAlchemy - 如何映射只读(或计算)属性
我试图弄清楚如何映射一个简单的只读属性,并在保存到数据库时触发该属性。
一个人为的例子应该可以让这一点更加清楚。首先,一个简单的表:
meta = MetaData()
foo_table = Table('foo', meta,
Column('id', String(3), primary_key=True),
Column('description', String(64), nullable=False),
Column('calculated_value', Integer, nullable=False),
)
我想要做的是设置一个具有只读属性的类,当我调用 session.commit() 时,它将为我插入计算值列...
import datetime
def Foo(object):
def __init__(self, id, description):
self.id = id
self.description = description
@property
def calculated_value(self):
self._calculated_value = datetime.datetime.now().second + 10
return self._calculated_value
根据 sqlalchemy 文档,我 < em>认为我应该像这样映射它:
mapper(Foo, foo_table, properties = {
'calculated_value' : synonym('_calculated_value', map_column=True)
})
问题是 _calculated_value 是 None ,直到您访问calculated_value 属性。看来 SQLAlchemy 在插入数据库时没有调用该属性,因此我得到的是 None 值。映射它以便将“calculated_value”属性的结果插入到 foo 表的“calculated_value”列中的正确方法是什么?
好的 - 我正在编辑这篇文章,以防其他人有同样的问题。我最终做的是使用 MapperExtension。让我给你一个更好的例子以及扩展的用法:
class UpdatePropertiesExtension(MapperExtension):
def __init__(self, properties):
self.properties = properties
def _update_properties(self, instance):
# We simply need to access our read only property one time before it gets
# inserted into the database.
for property in self.properties:
getattr(instance, property)
def before_insert(self, mapper, connection, instance):
self._update_properties(instance)
def before_update(self, mapper, connection, instance):
self._update_properties(instance)
这就是你如何使用它。假设您有一个具有多个只读属性的类,这些属性必须在插入数据库之前触发。我在这里假设对于这些只读属性中的每一个,您在数据库中都有一个对应的列,您希望用该属性的值填充该列。您仍然要为每个属性设置同义词,但是在映射对象时使用上面的映射器扩展:
class Foo(object):
def __init__(self, id, description):
self.id = id
self.description = description
self.items = []
self.some_other_items = []
@property
def item_sum(self):
self._item_sum = 0
for item in self.items:
self._item_sum += item.some_value
return self._item_sum
@property
def some_other_property(self):
self._some_other_property = 0
.... code to generate _some_other_property on the fly....
return self._some_other_property
mapper(Foo, metadata,
extension = UpdatePropertiesExtension(['item_sum', 'some_other_property']),
properties = {
'item_sum' : synonym('_item_sum', map_column=True),
'some_other_property' : synonym('_some_other_property', map_column = True)
})
I'm trying to figure out how to map against a simple read-only property and have that property fire when I save to the database.
A contrived example should make this more clear. First, a simple table:
meta = MetaData()
foo_table = Table('foo', meta,
Column('id', String(3), primary_key=True),
Column('description', String(64), nullable=False),
Column('calculated_value', Integer, nullable=False),
)
What I want to do is set up a class with a read-only property that will insert into the calculated_value column for me when I call session.commit()...
import datetime
def Foo(object):
def __init__(self, id, description):
self.id = id
self.description = description
@property
def calculated_value(self):
self._calculated_value = datetime.datetime.now().second + 10
return self._calculated_value
According to the sqlalchemy docs, I think I am supposed to map this like so:
mapper(Foo, foo_table, properties = {
'calculated_value' : synonym('_calculated_value', map_column=True)
})
The problem with this is that _calculated_value is None until you access the calculated_value property. It appears that SQLAlchemy is not calling the property on insertion into the database, so I'm getting a None value instead. What is the correct way to map this so that the result of the "calculated_value" property is inserted into the foo table's "calculated_value" column?
OK - I am editing this post in case someone else has the same question. What I ended up doing was using a MapperExtension. Let me give you a better example along with usage of the extension:
class UpdatePropertiesExtension(MapperExtension):
def __init__(self, properties):
self.properties = properties
def _update_properties(self, instance):
# We simply need to access our read only property one time before it gets
# inserted into the database.
for property in self.properties:
getattr(instance, property)
def before_insert(self, mapper, connection, instance):
self._update_properties(instance)
def before_update(self, mapper, connection, instance):
self._update_properties(instance)
And this is how you use this. Lets say you have a class with several read only properties that must fire before insertion into the database. I am assuming here that for each one of these read only properties, you have a corresponding column in the database that you want populated with the value of the property. You are still going to set up a synonym for each property, but you use the mapper extension above when you map the object:
class Foo(object):
def __init__(self, id, description):
self.id = id
self.description = description
self.items = []
self.some_other_items = []
@property
def item_sum(self):
self._item_sum = 0
for item in self.items:
self._item_sum += item.some_value
return self._item_sum
@property
def some_other_property(self):
self._some_other_property = 0
.... code to generate _some_other_property on the fly....
return self._some_other_property
mapper(Foo, metadata,
extension = UpdatePropertiesExtension(['item_sum', 'some_other_property']),
properties = {
'item_sum' : synonym('_item_sum', map_column=True),
'some_other_property' : synonym('_some_other_property', map_column = True)
})
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
感谢您编辑您的答案,杰夫。我遇到了完全相同的问题并使用您的代码解决了它,对于使用声明性基础的人来说,这是类似的问题。可能会节省您几分钟的时间来查找如何指定映射器参数和同义词:
Thanks for editing with your answer, Jeff. I had the exact same problem and solved it using your code, here's something similar for those using a declarative base. Might save you a few minutes looking up how to specify the mapper arguments and synonyms:
我不确定使用 sqlalchemy.orm.synonym 是否可以实现您想要的目标。可能没有考虑到 sqlalchemy 如何跟踪哪些实例是脏的并且需要在刷新期间更新的事实。
但是还有其他方法可以获取此功能 - SessionExtensions(注意需要填充顶部的engine_string变量):
有关SessionExtensions的更多信息:sqlalchemy.orm.interfaces.SessionExtension。
I'm not sure it's possible to achieve what you want using sqlalchemy.orm.synonym. Propably not given the fact how sqlalchemy keeps track of which instances are dirty and need to be updated during flush.
But there is other way how you can get this functionality - SessionExtensions (notice the engine_string variable at the top that needs to be filled):
More on SessionExtensions: sqlalchemy.orm.interfaces.SessionExtension.
较新版本的 SQLAlchemy 支持称为混合属性的功能,它允许您将方法定义为用于将计算值保存到数据库的设置器。
我不确定我是否理解您想要解决的问题是否足以提供示例代码,但在此发布给通过 Google 偶然发现此问题的任何人。
Newer versions of SQLAlchemy support something called Hybrid Properties, which let you define methods as setters for saving calculated values to the DB.
I'm not sure I understand the problem you're trying to solve well enough to give example code, but posting here for anyone who stumbles across this through Google.