我在这个问题上挣扎了一段时间,结果一次又一次地回到这个帖子。最后,让我的处境变得困难的是,有一个涉及 SQLAlchemy 会话的令人困惑的问题。我认为这对于 Flask、Flask-SQLAlchemy、SQLAlchemy 和 Marshmallow 来说很常见,可以进行讨论。当然,我并不声称自己是这方面的专家,但我相信我在下面所说的基本上是正确的。
实际上,db.session 与使用 Marshmallow 更新数据库的过程密切相关,因此决定提供详细信息,但首先是简短的。
简答
这是我使用 Marshmallow 更新数据库时得出的答案。这是与 Jair Perrut 非常有用的帖子不同的方法。我确实查看了 Marshmallow API,但无法让他的解决方案在提供的代码中运行,因为当时我正在试验他的解决方案,我没有正确管理我的 SQLAlchemy 会话。更进一步,有人可能会说我根本没有管理它们。可以通过以下方式更新模型:
user_model = user_schema.load(user)
db.session.add(user_model.data)
db.session.commit()
给 session.add() 一个带有主键的模型,它会假设一个更新,离开主键并创建一个新记录。这并不奇怪,因为 MySQL 有一个ON DUPLICATE KEY UPDATE
子句,如果密钥存在则执行更新,如果不存在则创建。
细节
SQLAlchemy 会话在对应用程序的请求期间由 Flask-SQLAlchemy 处理。在请求开始时,会话打开,当请求关闭时,会话也关闭。Flask 提供了用于设置和拆除可以找到管理会话和连接的代码的应用程序的钩子。但最终,SQLAlchemy 会话由开发人员管理,而 Flask-SQLAlchemy 只是提供帮助。这是一个说明会话管理的特殊案例。
考虑一个获取用户字典作为参数并使用 Marshmallow() 将字典加载到模型中的函数。在这种情况下,需要的不是创建新对象,而是更新现有对象。一开始有两点要记住:
- 模型类在与任何代码分开的 python 模块中定义,并且这些模型需要会话。通常开发者(Flask 文档)会
db = SQLAlchemy()
在这个文件的头部放一行来满足这个要求。实际上,这为模型创建了一个会话。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
- 在其他一些单独的文件中,可能还需要 SQLAlchemy 会话。例如,代码可能需要通过调用那里的函数来更新模型或创建新条目。这里是人们可能会发现的地方
db.session.add(user_model)
,db.session.commit().
这个会话的创建方式与上面的要点相同。
创建了 2 个 SQLAlchemy 会话。该模型位于一个 (SignallingSession) 中,模块使用它自己的 (scoped_session)。事实上,有 3 个。 MarshmallowUserSchema
有sqla_session = db.session
:一个会话附加到它上面。这就是第三个了,详情见下面的代码:
from marshmallow_sqlalchemy import ModelSchema
from donate_api.models.donation import UserModel
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class UserSchema(ModelSchema):
class Meta(object):
model = UserModel
strict = True
sqla_session = db.session
def some_function(user):
user_schema = UserSchema()
user['customer_id'] = '654321'
user_model = user_schema.load(user)
# Debug code:
user_model_query = UserModel.query.filter_by(id=3255161).first()
print db.session.object_session(user_model_query)
print db.session.object_session(user_model.data)
print db.session
db.session.add(user_model.data)
db.session.commit()
return
在这个模块的头部,模型被导入,它创建了它的会话,然后模块将创建它自己的。当然,正如所指出的,还有 Marshmallow 会议。这在某种程度上是完全可以接受的,因为 SQLAlchemy 允许开发人员管理会话。some_function(user)
考虑在调用 where时会发生什么,其中user['id']
分配了数据库中存在的某个值。
由于user
包含有效的主键db.session.add(user_model.data)
,因此知道它不是创建新行,而是更新现有行。这种行为应该不足为奇,并且至少在某种程度上是可以预期的,因为来自 MySQL 文档:
13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE 语法
如果指定 ON DUPLICATE KEY UPDATE 子句并且要插入的行将导致 UNIQUE 索引或 PRIMARY KEY 中的重复值,则会发生旧行的 UPDATE。
然后可以看到代码片段正在customer_id
使用主键 32155161 为用户更新字典。新customer_id
的是“654321”。字典加载了 Marshmallow 并提交到数据库。检查数据库可以发现它确实已更新。您可以尝试两种验证方式:
- 在代码中:
db.session.query(UserModel).filter_by(id=325516).first()
- 在 MySQL 中:
select * from user
如果您要考虑以下几点:
- 在代码中:
UserModel.query.filter_by(id=3255161).customer_id
您会发现查询返回无。模型未与数据库同步。我未能正确管理我们的 SQLAlchemy 会话。为了澄清这一点,请考虑在进行单独导入时打印语句的输出:
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b9107b90>
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b90a6150>
<sqlalchemy.orm.scoping.scoped_session object at 0x7f81b95eac50>
在这种情况下,UserModel.query
会话与 Marshmallow 会话不同。Marshmallow 会话是加载和添加的内容。这意味着查询模型不会显示我们的更改。事实上,如果我们这样做:
- db.session.object_session(user_model.data).commit()
模型查询现在将带回更新后的 customer_id!考虑通过以下方式完成导入的第二种选择flask_essentials
:
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
db = SQLAlchemy()
ma = Marshmallow()
<sqlalchemy.orm.session.SignallingSession object at 0x7f00fe227910>
<sqlalchemy.orm.session.SignallingSession object at 0x7f00fe227910>
<sqlalchemy.orm.scoping.scoped_session object at 0x7f00fed38710>
UserModel.query
会话现在与( user_model.data
Marshmallow) 会话相同。现在UserModel.query
确实反映了数据库中的变化:棉花糖和UserModel.query
会话是相同的。
注意:信令会话是 Flask-SQLAlchemy 使用的默认会话。它通过绑定选择和修改跟踪扩展了默认会话系统。