我正在试图弄清楚如何映射一个简单的只读属性,并在保存到数据库时触发该属性。
一个人为的例子应该让这个更清楚。首先,一个简单的表:
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()时,它将插入到calculated_value列中...
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文档,我想我应该像这样映射:
mapper(Foo, foo_table, properties = {
'calculated_value' : synonym('_calculated_value', map_column=True)
})
问题是_calculated_value在您访问calculated_value属性之前是None。似乎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)
})
答案 0 :(得分:5)
谢谢你的答案编辑,杰夫。我有完全相同的问题,并使用您的代码解决它,这里使用声明性基础的类似的东西。可能会花几分钟时间查找如何指定映射器参数和同义词:
from sqlalchemy.ext.declarative import declarative_base
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
_calculated_value = Column('calculated_value', String)
__mapper_args__ = {'extension': UpdatePropertiesExtension(['calculated_value'])}
def __init__(self, name):
self.name = name
@property
def calculated_value(self):
self._calculated_value = "foobar"
return self._calculated_value
calculated_value = synonym('_calculated_value', descriptor=calculated_value)
答案 1 :(得分:1)
我不确定使用sqlalchemy.orm.synonym可以达到你想要的效果。可能没有给出sqlalchemy如何跟踪哪些实例是脏的并且需要在刷新期间更新的事实。
但是还有其他方法可以获得这个功能 - SessionExtensions(注意顶部需要填充的engine_string变量):
(env)zifot@localhost:~/stackoverflow$ cat stackoverflow.py
engine_string = ''
from sqlalchemy import Table, Column, String, Integer, MetaData, create_engine
import sqlalchemy.orm as orm
import datetime
engine = create_engine(engine_string, echo = True)
meta = MetaData(bind = engine)
foo_table = Table('foo', meta,
Column('id', String(3), primary_key=True),
Column('description', String(64), nullable=False),
Column('calculated_value', Integer, nullable=False),
)
meta.drop_all()
meta.create_all()
class MyExt(orm.interfaces.SessionExtension):
def before_commit(self, session):
for obj in session:
if isinstance(obj, Foo):
obj.calculated_value = datetime.datetime.now().second + 10
Session = orm.sessionmaker(extension = MyExt())()
Session.configure(bind = engine)
class Foo(object):
def __init__(self, id, description):
self.id = id
self.description = description
orm.mapper(Foo, foo_table)
(env)zifot@localhost:~/stackoverflow$ ipython
Python 2.5.2 (r252:60911, Jan 4 2009, 17:40:26)
Type "copyright", "credits" or "license" for more information.
IPython 0.10 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object'. ?object also works, ?? prints more.
In [1]: from stackoverflow import *
2010-06-11 13:19:30,925 INFO sqlalchemy.engine.base.Engine.0x...11cc select version()
2010-06-11 13:19:30,927 INFO sqlalchemy.engine.base.Engine.0x...11cc {}
2010-06-11 13:19:30,935 INFO sqlalchemy.engine.base.Engine.0x...11cc select current_schema()
2010-06-11 13:19:30,936 INFO sqlalchemy.engine.base.Engine.0x...11cc {}
2010-06-11 13:19:30,965 INFO sqlalchemy.engine.base.Engine.0x...11cc select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname=current_schema() and lower(relname)=%(name)s
2010-06-11 13:19:30,966 INFO sqlalchemy.engine.base.Engine.0x...11cc {'name': u'foo'}
2010-06-11 13:19:30,979 INFO sqlalchemy.engine.base.Engine.0x...11cc
DROP TABLE foo
2010-06-11 13:19:30,980 INFO sqlalchemy.engine.base.Engine.0x...11cc {}
2010-06-11 13:19:30,988 INFO sqlalchemy.engine.base.Engine.0x...11cc COMMIT
2010-06-11 13:19:30,997 INFO sqlalchemy.engine.base.Engine.0x...11cc select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname=current_schema() and lower(relname)=%(name)s
2010-06-11 13:19:30,999 INFO sqlalchemy.engine.base.Engine.0x...11cc {'name': u'foo'}
2010-06-11 13:19:31,007 INFO sqlalchemy.engine.base.Engine.0x...11cc
CREATE TABLE foo (
id VARCHAR(3) NOT NULL,
description VARCHAR(64) NOT NULL,
calculated_value INTEGER NOT NULL,
PRIMARY KEY (id)
)
2010-06-11 13:19:31,009 INFO sqlalchemy.engine.base.Engine.0x...11cc {}
2010-06-11 13:19:31,025 INFO sqlalchemy.engine.base.Engine.0x...11cc COMMIT
In [2]: f = Foo('idx', 'foo')
In [3]: f.calculated_value
In [4]: Session.add(f)
In [5]: f.calculated_value
In [6]: Session.commit()
2010-06-11 13:19:57,668 INFO sqlalchemy.engine.base.Engine.0x...11cc BEGIN
2010-06-11 13:19:57,674 INFO sqlalchemy.engine.base.Engine.0x...11cc INSERT INTO foo (id, description, calculated_value) VALUES (%(id)s, %(description)s, %(calculated_value)s)
2010-06-11 13:19:57,675 INFO sqlalchemy.engine.base.Engine.0x...11cc {'description': 'foo', 'calculated_value': 67, 'id': 'idx'}
2010-06-11 13:19:57,683 INFO sqlalchemy.engine.base.Engine.0x...11cc COMMIT
In [7]: f.calculated_value
2010-06-11 13:20:00,755 INFO sqlalchemy.engine.base.Engine.0x...11cc BEGIN
2010-06-11 13:20:00,759 INFO sqlalchemy.engine.base.Engine.0x...11cc SELECT foo.id AS foo_id, foo.description AS foo_description, foo.calculated_value AS foo_calculated_value
FROM foo
WHERE foo.id = %(param_1)s
2010-06-11 13:20:00,761 INFO sqlalchemy.engine.base.Engine.0x...11cc {'param_1': 'idx'}
Out[7]: 67
In [8]: f.calculated_value
Out[8]: 67
In [9]: Session.commit()
2010-06-11 13:20:08,366 INFO sqlalchemy.engine.base.Engine.0x...11cc UPDATE foo SET calculated_value=%(calculated_value)s WHERE foo.id = %(foo_id)s
2010-06-11 13:20:08,367 INFO sqlalchemy.engine.base.Engine.0x...11cc {'foo_id': u'idx', 'calculated_value': 18}
2010-06-11 13:20:08,373 INFO sqlalchemy.engine.base.Engine.0x...11cc COMMIT
In [10]: f.calculated_value
2010-06-11 13:20:10,475 INFO sqlalchemy.engine.base.Engine.0x...11cc BEGIN
2010-06-11 13:20:10,479 INFO sqlalchemy.engine.base.Engine.0x...11cc SELECT foo.id AS foo_id, foo.description AS foo_description, foo.calculated_value AS foo_calculated_value
FROM foo
WHERE foo.id = %(param_1)s
2010-06-11 13:20:10,481 INFO sqlalchemy.engine.base.Engine.0x...11cc {'param_1': 'idx'}
Out[10]: 18
有关SessionExtensions的更多信息:sqlalchemy.orm.interfaces.SessionExtension。
答案 2 :(得分:0)
较新版本的SQLAlchemy支持名为Hybrid Properties的东西,它允许您将方法定义为用于将计算值保存到DB的setter。
我不确定我是否理解您为解决示例代码而努力解决的问题,但是此处发布的内容适用于通过Google偶然发现此问题的任何人。