使用marshmallow中的数据更新行(SQLAlchemy)

gce*_*rar 17 python flask flask-sqlalchemy 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
Run Code Online (Sandbox Code Playgroud)

Jai*_*rut 27

您必须将正在编辑的对象作为参数传递给schema.load(),如下所示:

node_schema.load(json_data, instance=Node().query.get(node_id))
Run Code Online (Sandbox Code Playgroud)

如果你想加载没有Model的所有必填字段,你可以添加"partial = True",如下所示:

node_schema.load(json_data, instance=Node().query.get(node_id), partial=True)
Run Code Online (Sandbox Code Playgroud)

http://marshmallow-sqlalchemy.readthedocs.org/en/latest/api_reference.html#marshmallow_sqlalchemy.ModelSchema.load

  • 是的,我在我的项目中使用这种方式,但我忘记了 load() 中的“实例”参数。我用这个编辑了我的答案,并添加了一个指向 marshmallow-sqlalchemy 加载方法文档的链接。你是对的,但这是在flask-sqlalchemy的文档中,如果你从marshmallow_sqlalchemy扩展ModelSchema,你有:`load(data, session=None, instance=None, *args, **kwargs)` (2认同)

gce*_*rar 5

我已经推出了自己的解决方案。希望它对其他人有帮助。解决方案在 Node 模型上实现 update 方法。

解决方案:

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
Run Code Online (Sandbox Code Playgroud)


Aar*_*ron 5

我花了一段时间解决这个问题,结果又一次又回到了这个职位。最后,使我的情况变得困难的是,存在一个涉及SQLAlchemy会话的令人困惑的问题。我认为这对于Flask,Flask-SQLAlchemy,SQLAlchemy和棉花糖来说足够普遍了,以进行讨论。我当然不声称自己是专家,但是我相信我在下面声明的内容基本上是正确的。

实际上,db.session与使用棉花糖更新数据库的过程紧密相关,因此决定提供详细信息,但首先要简短。

简短答案

这是我使用棉花糖更新数据库的答案。这与Jair Perrut的非常有用的帖子不同。我确实查看了Marshmallow API,但无法在显示的代码中使用他的解决方案,因为在我尝试使用他的解决方案时,我没有适当地管理SQLAlchemy会话。进一步说,有人可能说我根本没有管理它们。可以通过以下方式更新模型:

user_model = user_schema.load(user)
db.session.add(user_model.data)
db.session.commit()
Run Code Online (Sandbox Code Playgroud)

给session.add()一个带有主键的模型,它将假设有一个更新,保留主键,而是创建一个新记录。这并不令人惊讶,因为MySQL有一个ON DUPLICATE KEY UPDATE子句,如果存在密钥,则执行更新;否则,创建。

细节

在请求应用程序期间,Flask-SQLAlchemy处理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个。棉花糖UserSchema有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
Run Code Online (Sandbox Code Playgroud)

在该模块的开头,将导入模型,该模型创建其会话,然后该模块将创建其自己的会话。当然,正如所指出的,还有棉花糖会议。在某种程度上,这完全可以接受,因为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'。该词典加载了棉花糖,并完成了对数据库的提交。检查数据库,可以发现它确实已更新。您可以尝试两种验证方法:

  • 在代码中: db.session.query(UserModel).filter_by(id=325516).first()
  • 在MySQL中: select * from user

如果要考虑以下内容:

  • 在代码中: UserModel.query.filter_by(id=3255161).customer_id

您会发现查询带回了None。模型未与数据库同步。我无法正确管理我们的SQLAlchemy会话。为了使这一点更加清楚,请考虑在进行单独的导入时print语句的输出:

  • <sqlalchemy.orm.session.SignallingSession object at 0x7f81b9107b90>
  • <sqlalchemy.orm.session.SignallingSession object at 0x7f81b90a6150>
  • <sqlalchemy.orm.scoping.scoped_session object at 0x7f81b95eac50>

在这种情况下,该UserModel.query会话不同于棉花糖会话。棉花糖会话是加载和添加的内容。这意味着查询模型不会显示我们的更改。实际上,如果这样做:

  • 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()
Run Code Online (Sandbox Code Playgroud)
  • <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(棉花糖)会话相同。现在,UserModel.query确实反映了数据库中的更改:棉花糖和UserModel.query会话相同。

注意:信令会话是Flask-SQLAlchemy使用的默认会话。它通过绑定选择和修改跟踪扩展了默认会话系统。