使用来自 marshmallow 的数据更新行 (SQLAlchemy)
Update row (SQLAlchemy) with data from marshmallow
我正在使用 Flask、Flask-SQLAlchemy、Flask-Marshmallow + marshmallow-sqlalchemy,试图实现 REST api PUT 方法。我还没有找到任何使用 SQLA 和 Marshmallow 实现更新的教程。
代码如下:
class NodeSchema(ma.Schema):
# ...
class NodeAPI(MethodView):
decorators = [login_required, ]
model = Node
def get_queryset(self):
if g.user.is_admin:
return self.model.query
return self.model.query.filter(self.model.owner == g.user)
def put(self, node_id):
json_data = request.get_json()
if not json_data:
return jsonify({'message': 'Invalid request'}), 400
# Here is part which I can't make it work for me
data, errors = node_schema.load(json_data)
if errors:
return jsonify(errors), 422
queryset = self.get_queryset()
node = queryset.filter(Node.id == node_id).first_or_404()
# Here I need some way to update this object
node.update(data) #=> raises AttributeError: 'Node' object has no attribute 'update'
# Also tried:
# node = queryset.filter(Node.id == node_id)
# node.update(data) <-- It doesn't if know there is any object
# Wrote testcase, when user1 tries to modify node of user2. Node doesn't change (OK), but user1 gets status code 200 (NOT OK).
db.session.commit()
return jsonify(), 200
我推出了自己的解决方案。希望它能帮助别人。解决方案在节点模型上实现更新方法。
解决方案:
class Node(db.Model):
# ...
def update(self, **kwargs):
# py2 & py3 compatibility do:
# from six import iteritems
# for key, value in six.iteritems(kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
class NodeAPI(MethodView):
decorators = [login_required, ]
model = Node
def get_queryset(self):
if g.user.is_admin:
return self.model.query
return self.model.query.filter(self.model.owner == g.user)
def put(self, node_id):
json_data = request.get_json()
if not json_data:
abort(400)
data, errors = node_schema.load(json_data) # validate with marshmallow
if errors:
return jsonify(errors), 422
queryset = self.get_queryset()
node = queryset.filter(self.model.id == node_id).first_or_404()
node.update(**data)
db.session.commit()
return jsonify(message='Successfuly updated'), 200
已更新
从 marshmallow-sqlalchemy
扩展 ModelSchema
而不是 Flask-Marshmallow
你有:
load(data, session=None, instance=None, *args, **kwargs)
然后你必须将正在编辑的对象作为参数传递给schema.load()
,像这样:
node_schema.load(json_data, instance=Node().query.get(node_id))
如果你想在没有 Model
的所有必填字段的情况下加载,你可以添加 partial=True
,像这样:
node_schema.load(json_data, instance=Node().query.get(node_id), partial=True)
我为这个问题纠结了一段时间,结果一次又一次地回到这个 post。最后让我的处境变得困难的是,有一个涉及 SQLAlchemy sessions 的混杂问题。我认为这对于 Flask、Flask-SQLAlchemy、SQLAlchemy 和 Marshmallow 来说已经足够常见了,可以进行讨论。我当然不自称是这方面的专家,但我相信我在下面所说的基本上是正确的。
事实上,db.session 与使用 Marshmallow 更新数据库的过程密切相关,因此决定提供详细信息,但首先是简短的。
简答
这是我使用 Marshmallow 更新数据库时得到的答案。它与 Jair Perrut 非常有用的 post 方法不同。我确实查看了 Marshmallow API,但无法让他的解决方案在所提供的代码中运行,因为在我试验他的解决方案时,我没有正确管理我的 SQLAlchemy session。更进一步,有人可能会说我根本没有管理它们。可以通过以下方式更新模型:
user_model = user_schema.load(user)
db.session.add(user_model.data)
db.session.commit()
给 session.add() 一个带有主键的模型,它会假定更新,将主键留在外面并创建一个新记录。这并不奇怪,因为 MySQL 有一个 ON DUPLICATE KEY UPDATE
子句,如果密钥存在则执行更新,如果不存在则创建。
详情
SQLAlchemy session 在请求应用程序期间由 Flask-SQLAlchemy 处理。在请求开始时 session 打开,当请求关闭时 session 也关闭。 Flask 提供了用于设置和拆除应用程序的挂钩,其中可以找到用于管理 session 和连接的代码。不过,最后,SQLAlchemy session 由开发人员管理,而 Flask-SQLAlchemy 只是提供帮助。这是一个特殊案例,说明了 sessions.
的管理
考虑一个函数,该函数将用户字典作为参数并将其与 Marshmallow() 一起使用以将字典加载到模型中。在这种情况下,需要的不是创建新的 object,而是更新现有的 object。开始时要记住两件事:
- 模型 类 在与任何代码分开的 python 模块中定义,并且这些模型需要 session。通常开发者(Flask 文档)会在这个文件的头部放一行
db = SQLAlchemy()
来满足这个要求。这实际上为模型创建了一个 session。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
- 在其他一些单独的文件中,可能也需要 SQLAlchemy session。例如,代码可能需要通过调用那里的函数来更新模型或创建新条目。在这里可能会找到
db.session.add(user_model)
和 db.session.commit().
这个 session 的创建方式与上面的要点相同。
创建了 2 个 SQLAlchemy session。该模型位于一个 (SignallingSession) 中,模块使用它自己的 (scoped_session)。事实上,有 3 个。Marshmallow UserSchema
有 sqla_session = db.session
:一个 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
在这个模块的头部导入模型,创建它的 session,然后模块将创建它自己的。当然,如前所述,还有 Marshmallow session。这在某种程度上是完全可以接受的,因为 SQLAlchemy 允许开发人员管理 sessions。考虑当 some_function(user)
被调用时会发生什么,其中 user['id']
被分配了一些存在于数据库中的值。
由于 user
包含一个有效的主键,因此 db.session.add(user_model.data)
知道它不是在创建新行,而是在更新现有的行。这种行为应该不足为奇,至少在某种程度上是可以预期的,因为 MySQL 文档:
13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Syntax
If you specify an ON DUPLICATE KEY UPDATE clause and a row to be inserted would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row occurs.
然后可以看到代码片段正在为主键为 32155161 的用户更新字典中的 customer_id
。新的 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
您会发现查询带回 None。该模型未与数据库同步。我未能正确管理我们的 SQLAlchemy sessions。为了使这一点变得清晰,请考虑进行单独导入时打印语句的输出:
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b9107b90>
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b90a6150>
<sqlalchemy.orm.scoping.scoped_session object at 0x7f81b95eac50>
在这种情况下,UserModel.query
session 不同于 Marshmallow session。 Marshmallow session 是加载和添加的内容。这意味着查询模型将 n不显示我们的更改。事实上,如果我们这样做:
- 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
session 现在与 user_model.data
(棉花糖)session 相同。现在 UserModel.query
确实反映了数据库中的更改:Marshmallow 和 UserModel.query
session 是相同的。
注意:信号 session 是 Flask-SQLAlchemy 使用的默认 session。它通过绑定选择和修改跟踪扩展了默认的 session 系统。
最新更新 [2020]:
您可能面临将键映射到数据库模型的问题。您的请求正文仅更新了字段,因此您只想更改那些而不影响其他字段。可以选择编写多个 if 条件,但这不是一个好方法。
解决方法
您只能使用 sqlalchemy 库实现 patch 或 put 方法。
例如:
YourModelName.query.filter_by(
your_model_column_id = 12 #change 12: where condition to find particular row
).update(request_data)
request_data 应该是字典对象。例如
{
"your_model_column_name_1": "Hello",
"your_model_column_name_2": "World",
}
在上述情况下,只会更新两列,即:your_model_column_name_1
和 your_model_column_name_2
更新函数将 request_data 映射到数据库模型并为您创建更新查询。检查这个:https://docs.sqlalchemy.org/en/13/core/dml.html#sqlalchemy.sql.expression.update
之前的答案似乎已过时,因为 ModelSchema
现在是 deprecated。
您应该 SQLAlchemyAutoSchema
改为 options。
class NodeSchema(SQLAlchemyAutoSchema):
class Meta:
model = Node
load_instance = True
sqla_session = db.session
node_schema = NodeSchema()
# then when you need to update a Node orm instance :
node_schema.load(node_data, instance=node, partial=True)
db.session.update()
下面是作者最初要求的 Flask-Marshmallow + marshmallow-sqlalchemy
捆绑包的解决方案。
schemas.py
from flask import current_app
from flask_marshmallow import Marshmallow
from app.models import Node
ma = Marshmallow(current_app)
class NodeSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Node
load_instance = True
load_instance
这里是重点,进一步更新
routes.py
from flask import jsonify, request
from marshmallow import ValidationError
from app import db
@bp.route("/node/<node_uuid>/edit", methods=["POST"])
def edit_node(node_uuid):
json_data = request.get_json(force=True, silent=True)
node = Node.query.filter_by(
node_uuid=node_uuid
).first()
if node:
try:
schema = NodeSchema()
json_data["node_uuid"] = node_uuid
node = schema.load(json_data, instance=node)
db.session.commit()
return schema.jsonify(node)
except ValidationError as err:
return jsonify(err.messages), 422
else:
return jsonify("Not found"), 404
您必须先检查 Node
是否存在,否则将创建新实例。
我正在使用 Flask、Flask-SQLAlchemy、Flask-Marshmallow + marshmallow-sqlalchemy,试图实现 REST api PUT 方法。我还没有找到任何使用 SQLA 和 Marshmallow 实现更新的教程。
代码如下:
class NodeSchema(ma.Schema):
# ...
class NodeAPI(MethodView):
decorators = [login_required, ]
model = Node
def get_queryset(self):
if g.user.is_admin:
return self.model.query
return self.model.query.filter(self.model.owner == g.user)
def put(self, node_id):
json_data = request.get_json()
if not json_data:
return jsonify({'message': 'Invalid request'}), 400
# Here is part which I can't make it work for me
data, errors = node_schema.load(json_data)
if errors:
return jsonify(errors), 422
queryset = self.get_queryset()
node = queryset.filter(Node.id == node_id).first_or_404()
# Here I need some way to update this object
node.update(data) #=> raises AttributeError: 'Node' object has no attribute 'update'
# Also tried:
# node = queryset.filter(Node.id == node_id)
# node.update(data) <-- It doesn't if know there is any object
# Wrote testcase, when user1 tries to modify node of user2. Node doesn't change (OK), but user1 gets status code 200 (NOT OK).
db.session.commit()
return jsonify(), 200
我推出了自己的解决方案。希望它能帮助别人。解决方案在节点模型上实现更新方法。
解决方案:
class Node(db.Model):
# ...
def update(self, **kwargs):
# py2 & py3 compatibility do:
# from six import iteritems
# for key, value in six.iteritems(kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
class NodeAPI(MethodView):
decorators = [login_required, ]
model = Node
def get_queryset(self):
if g.user.is_admin:
return self.model.query
return self.model.query.filter(self.model.owner == g.user)
def put(self, node_id):
json_data = request.get_json()
if not json_data:
abort(400)
data, errors = node_schema.load(json_data) # validate with marshmallow
if errors:
return jsonify(errors), 422
queryset = self.get_queryset()
node = queryset.filter(self.model.id == node_id).first_or_404()
node.update(**data)
db.session.commit()
return jsonify(message='Successfuly updated'), 200
已更新
从 marshmallow-sqlalchemy
扩展 ModelSchema
而不是 Flask-Marshmallow
你有:
load(data, session=None, instance=None, *args, **kwargs)
然后你必须将正在编辑的对象作为参数传递给schema.load()
,像这样:
node_schema.load(json_data, instance=Node().query.get(node_id))
如果你想在没有 Model
的所有必填字段的情况下加载,你可以添加 partial=True
,像这样:
node_schema.load(json_data, instance=Node().query.get(node_id), partial=True)
我为这个问题纠结了一段时间,结果一次又一次地回到这个 post。最后让我的处境变得困难的是,有一个涉及 SQLAlchemy sessions 的混杂问题。我认为这对于 Flask、Flask-SQLAlchemy、SQLAlchemy 和 Marshmallow 来说已经足够常见了,可以进行讨论。我当然不自称是这方面的专家,但我相信我在下面所说的基本上是正确的。
事实上,db.session 与使用 Marshmallow 更新数据库的过程密切相关,因此决定提供详细信息,但首先是简短的。
简答
这是我使用 Marshmallow 更新数据库时得到的答案。它与 Jair Perrut 非常有用的 post 方法不同。我确实查看了 Marshmallow API,但无法让他的解决方案在所提供的代码中运行,因为在我试验他的解决方案时,我没有正确管理我的 SQLAlchemy session。更进一步,有人可能会说我根本没有管理它们。可以通过以下方式更新模型:
user_model = user_schema.load(user)
db.session.add(user_model.data)
db.session.commit()
给 session.add() 一个带有主键的模型,它会假定更新,将主键留在外面并创建一个新记录。这并不奇怪,因为 MySQL 有一个 ON DUPLICATE KEY UPDATE
子句,如果密钥存在则执行更新,如果不存在则创建。
详情
SQLAlchemy session 在请求应用程序期间由 Flask-SQLAlchemy 处理。在请求开始时 session 打开,当请求关闭时 session 也关闭。 Flask 提供了用于设置和拆除应用程序的挂钩,其中可以找到用于管理 session 和连接的代码。不过,最后,SQLAlchemy session 由开发人员管理,而 Flask-SQLAlchemy 只是提供帮助。这是一个特殊案例,说明了 sessions.
的管理考虑一个函数,该函数将用户字典作为参数并将其与 Marshmallow() 一起使用以将字典加载到模型中。在这种情况下,需要的不是创建新的 object,而是更新现有的 object。开始时要记住两件事:
- 模型 类 在与任何代码分开的 python 模块中定义,并且这些模型需要 session。通常开发者(Flask 文档)会在这个文件的头部放一行
db = SQLAlchemy()
来满足这个要求。这实际上为模型创建了一个 session。from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
- 在其他一些单独的文件中,可能也需要 SQLAlchemy session。例如,代码可能需要通过调用那里的函数来更新模型或创建新条目。在这里可能会找到
db.session.add(user_model)
和db.session.commit().
这个 session 的创建方式与上面的要点相同。
创建了 2 个 SQLAlchemy session。该模型位于一个 (SignallingSession) 中,模块使用它自己的 (scoped_session)。事实上,有 3 个。Marshmallow UserSchema
有 sqla_session = db.session
:一个 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
在这个模块的头部导入模型,创建它的 session,然后模块将创建它自己的。当然,如前所述,还有 Marshmallow session。这在某种程度上是完全可以接受的,因为 SQLAlchemy 允许开发人员管理 sessions。考虑当 some_function(user)
被调用时会发生什么,其中 user['id']
被分配了一些存在于数据库中的值。
由于 user
包含一个有效的主键,因此 db.session.add(user_model.data)
知道它不是在创建新行,而是在更新现有的行。这种行为应该不足为奇,至少在某种程度上是可以预期的,因为 MySQL 文档:
13.2.5.2 INSERT ... ON DUPLICATE KEY UPDATE Syntax
If you specify an ON DUPLICATE KEY UPDATE clause and a row to be inserted would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row occurs.
然后可以看到代码片段正在为主键为 32155161 的用户更新字典中的 customer_id
。新的 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
您会发现查询带回 None。该模型未与数据库同步。我未能正确管理我们的 SQLAlchemy sessions。为了使这一点变得清晰,请考虑进行单独导入时打印语句的输出:
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b9107b90>
<sqlalchemy.orm.session.SignallingSession object at 0x7f81b90a6150>
<sqlalchemy.orm.scoping.scoped_session object at 0x7f81b95eac50>
在这种情况下,UserModel.query
session 不同于 Marshmallow session。 Marshmallow session 是加载和添加的内容。这意味着查询模型将 n不显示我们的更改。事实上,如果我们这样做:
- 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
session 现在与 user_model.data
(棉花糖)session 相同。现在 UserModel.query
确实反映了数据库中的更改:Marshmallow 和 UserModel.query
session 是相同的。
注意:信号 session 是 Flask-SQLAlchemy 使用的默认 session。它通过绑定选择和修改跟踪扩展了默认的 session 系统。
最新更新 [2020]:
您可能面临将键映射到数据库模型的问题。您的请求正文仅更新了字段,因此您只想更改那些而不影响其他字段。可以选择编写多个 if 条件,但这不是一个好方法。
解决方法 您只能使用 sqlalchemy 库实现 patch 或 put 方法。
例如:
YourModelName.query.filter_by(
your_model_column_id = 12 #change 12: where condition to find particular row
).update(request_data)
request_data 应该是字典对象。例如
{
"your_model_column_name_1": "Hello",
"your_model_column_name_2": "World",
}
在上述情况下,只会更新两列,即:your_model_column_name_1
和 your_model_column_name_2
更新函数将 request_data 映射到数据库模型并为您创建更新查询。检查这个:https://docs.sqlalchemy.org/en/13/core/dml.html#sqlalchemy.sql.expression.update
之前的答案似乎已过时,因为 ModelSchema
现在是 deprecated。
您应该 SQLAlchemyAutoSchema
改为 options。
class NodeSchema(SQLAlchemyAutoSchema):
class Meta:
model = Node
load_instance = True
sqla_session = db.session
node_schema = NodeSchema()
# then when you need to update a Node orm instance :
node_schema.load(node_data, instance=node, partial=True)
db.session.update()
下面是作者最初要求的 Flask-Marshmallow + marshmallow-sqlalchemy
捆绑包的解决方案。
schemas.py
from flask import current_app
from flask_marshmallow import Marshmallow
from app.models import Node
ma = Marshmallow(current_app)
class NodeSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Node
load_instance = True
load_instance
这里是重点,进一步更新
routes.py
from flask import jsonify, request
from marshmallow import ValidationError
from app import db
@bp.route("/node/<node_uuid>/edit", methods=["POST"])
def edit_node(node_uuid):
json_data = request.get_json(force=True, silent=True)
node = Node.query.filter_by(
node_uuid=node_uuid
).first()
if node:
try:
schema = NodeSchema()
json_data["node_uuid"] = node_uuid
node = schema.load(json_data, instance=node)
db.session.commit()
return schema.jsonify(node)
except ValidationError as err:
return jsonify(err.messages), 422
else:
return jsonify("Not found"), 404
您必须先检查 Node
是否存在,否则将创建新实例。