【问题标题】:SQLAlchemy - how to map against a read-only (or calculated) propertySQLAlchemy - 如何映射到只读(或计算)属性
【发布时间】:2011-03-02 12:20:17
【问题描述】:

我试图弄清楚如何映射一个简单的只读属性,并在我保存到数据库时触发该属性。

一个人为的例子应该更清楚地说明这一点。一、简单的表格:

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 文档,我认为我应该这样映射:

mapper(Foo, foo_table, properties = {
    'calculated_value' : synonym('_calculated_value', map_column=True)
    })

这个问题是 _calculated_value 是 None 直到您访问 computed_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)
    })

【问题讨论】:

    标签: python properties sqlalchemy readonly


    【解决方案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

    【讨论】:

    • 有趣。即使我以稍微不同的方式解决了这个问题,我还是将其标记为答案,因为它应该可以工作。我对 SQLAlchemy 了解得越多,我就越喜欢它!
    【解决方案2】:

    感谢您编辑您的答案,杰夫。我遇到了完全相同的问题并使用您的代码解决了它,对于那些使用声明性基础的人来说,这是类似的。查找如何指定映射器参数和同义词可能会节省您几分钟的时间:

    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)
    

    【讨论】:

    • 谢谢!确实为我节省了一些时间。
    【解决方案3】:

    较新版本的 SQLAlchemy 支持称为混合属性的东西,它允许您将方法定义为用于将计算值保存到数据库的设置器。

    我不确定我是否理解您试图解决的问题是否足以提供示例代码,但在这里发布给通过 Google 偶然发现此问题的任何人。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 2019-06-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多