【问题标题】:Update row (SQLAlchemy) with data from marshmallow使用棉花糖中的数据更新行 (SQLAlchemy)
【发布时间】:2015-10-31 17:19:15
【问题描述】:

我正在使用 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

【问题讨论】:

    标签: python flask flask-sqlalchemy marshmallow


    【解决方案1】:

    我推出了自己的解决方案。希望它可以帮助别人。解决方案在 Node 模型上实现更新方法。

    解决方案:

    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
    

    【讨论】:

    • 谢谢。类中的 update 方法是我可以让它工作的唯一方法。由于某些未知原因,传递与instance 相同的查询以加载(使用session)对我不起作用。
    【解决方案2】:

    更新

    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)
    

    请参阅文档marshmallow_sqlalchemy.ModelSchema.load

    【讨论】:

    • 你试过你的代码sn-p吗?根据文档,第二个参数应该是布尔类型。在您的情况下,您从 DB 获得的元组转换为 True。这与node_schema.load(json_data, many=True) 相同。您只能获得具有单个元素的数组。没有其他的。此外,您仍然没有解决将 json_data 存储到 db sqlalchemy 对象的问题。
    • 是的,我在我的项目中使用这种方式,但我忘记了 load() 中的“instance”参数。我用这个编辑了我的答案,并添加了一个指向 marshmallow-sqlalchemy 加载方法的文档的链接。你是对的,但这是来自 flask-sqlalchemy 的文档,如果你从 marshmallow_sqlalchemy 扩展 ModelSchema,你有:load(data, session=None, instance=None, *args, **kwargs)
    • 哦。我不知道。好东西。好工作。我会将您的答案标记为解决方案。
    • @JairPerrut 您想将添加到 cmets 的内容添加到您的答案中吗?从 marshmallow-sqlalchema 和 marshmallow 架构扩展架构的区别。这是非常微妙的事情,谢谢
    【解决方案3】:

    我为这个问题纠结了一段时间,结果一次又一次地回到这个帖子。最后,让我的处境变得困难的是,有一个涉及 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个。棉花糖UserSchemasqla_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) 被调用时会发生什么,其中 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 中的重复值,则会发生旧行的更新。

    然后可以看到代码的 sn-p 正在为具有主键 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

    您会发现查询返回无。模型未与数据库同步。我未能正确管理我们的 SQLAlchemy 会话。为了澄清这一点,请考虑在进行单独导入时打印语句的输出:

    • &lt;sqlalchemy.orm.session.SignallingSession object at 0x7f81b9107b90&gt;
    • &lt;sqlalchemy.orm.session.SignallingSession object at 0x7f81b90a6150&gt;
    • &lt;sqlalchemy.orm.scoping.scoped_session object at 0x7f81b95eac50&gt;

    在这种情况下,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()
    
    • &lt;sqlalchemy.orm.session.SignallingSession object at 0x7f00fe227910&gt;
    • &lt;sqlalchemy.orm.session.SignallingSession object at 0x7f00fe227910&gt;
    • &lt;sqlalchemy.orm.scoping.scoped_session object at 0x7f00fed38710&gt;

    UserModel.query 会话现在与 user_model.data (Marshmallow) 会话相同。现在UserModel.query 确实反映了数据库中的变化:Marshmallow 和UserModel.query 会话是相同的。

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

    【讨论】:

    • if user['id']: user_model = user_schema.load(user) db.session.add(user_model.data) db.session.commit() else: user_model = user_schema.load(user) db.session.add(user_model.data) db.sessiom.commit() 这与 if 和 else 分支中的代码完全相同。不确定您在尝试什么?
    • 你是对的。如果 user['id] session.add() 更新,如果不是 user['id'] session.add 将创建。
    • 因此实际上不需要if/else 语句,因为除了调试代码之外,两个分支完全相同。
    【解决方案4】:

    最新更新 [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 应该是 dict 对象。例如。

    {
      "your_model_column_name_1": "Hello",
      "your_model_column_name_2": "World",
    }
    

    在上述情况下,只会更新两列:your_model_column_name_1your_model_column_name_2

    更新函数将 request_data 映射到数据库模型并为您创建更新查询。结帐:https://docs.sqlalchemy.org/en/13/core/dml.html#sqlalchemy.sql.expression.update

    【讨论】:

      【解决方案5】:

      以前的答案似乎已经过时,因为 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()
      

      【讨论】:

        猜你喜欢
        • 2020-01-29
        • 1970-01-01
        • 2018-11-12
        • 1970-01-01
        • 2019-08-07
        • 2017-02-02
        • 1970-01-01
        • 2023-03-17
        • 1970-01-01
        相关资源
        最近更新 更多