SQLAlchemy - how to map against a read-only (or calculated) property

让人想犯罪 __ 提交于 2019-11-30 09:49:14

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):

(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

More on SessionExtensions: sqlalchemy.orm.interfaces.SessionExtension.

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:

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)

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!