编辑 SQLAlchemy 条目并提交更新会更改模型方法的输出

Editing a SQLAlchemy entry & committing the update changes the output of Model method

在使用 SQLAlchemy 的 Flask 应用程序中,我声明了一个 Deliverymen 模型,其中包含一些方法、属性和 class 方法。其中之一,get_own_data(self) 构建并 returns 一个只包含最终用户需要的相关信息的字典(完整的模型声明在下面):

def get_own_data(self):
        own_data = {
            key: value
            for (key, value) in vars(self).items()

            # Exclude the following keys
            if key != "_sa_instance_state" and key != "moto_placa"
        }
        
        # Add this key, which isn't found in 'vars(self)' as it is a class property
        own_data["moto_placa_upper"] = self.moto_placa

        return own_data

由于表单处理和 属性 setter 需要用户输入并转动 placa(或牌照),因此必须像这样定义密钥对) 转换为大写。 属性 及其 setter 也可在下方找到。

当我尝试编辑现有条目然后提交更改时,get_own_data 方法 returns 最后定义的密钥对,如下:

Python 3.10.4 (main, Mar 23 2022, 23:05:40) [GCC 11.2.0] on linux
App: app [development]
Instance: /home/daniel/Documents/work/breakfast4you/reportes/backend/instance
>>> d = Deliveryman.query.first()
>>> d.get_own_data()
{'user_id': 9, 'nombre': 'James Bond', 'telefono': '1234567890', 'status': 1, 'arp': 'Protección', 'tipo_cuenta': 'Ahorros', 'direccion': 'Dirección de prueba', 'id': 1, 'gov_id': '1234789845', 'eps': 'Sanitas', 'moto_marca': 'AKT', 'nombre_banco': 'Davivienda', 'num_cuenta': '1234567890', 'moto_placa_upper': 'BND 007'}
>>> d.nombre = "Carlos"
>>> db.session.commit()
>>> d.get_own_data()
{'moto_placa_upper': 'BND 007'}

令人困惑的是,如果在提交更改后我访问了任何对象属性,然后 运行 d.get_own_data()... 它再次起作用!

>>> d.nombre
'Carlos'
>>> d.get_own_data()
{'nombre': 'Carlos', 'tipo_cuenta': 'Ahorros', 'status': 1, 'user_id': 9, 'arp': 'Protección', 'telefono': '1234567890', 'direccion': 'Dirección de prueba', 'num_cuenta': '1234567890', 'eps': 'Sanitas', 'nombre_banco': 'Davivienda', 'id': 1, 'gov_id': '1234789845', 'moto_marca': 'AKT', 'moto_placa_upper': 'BND 007'}

这是模型声明:

class Deliveryman(db.Model):
    __tablename__ = "delivery_men"

    id = db.Column(db.Integer, primary_key=True, unique=True)
    user_id = db.Column(db.ForeignKey("users.id"), index=True, unique=True)
    nombre = db.Column(db.String(40), nullable=False, index=True)
    direccion = db.Column(db.String(120), nullable=False)
    telefono = db.Column(db.String(15), nullable=False)
    gov_id = db.Column(db.String(20), nullable=False, unique=True)
    status = db.Column(db.Integer)
    eps = db.Column(db.String(150))
    arp = db.Column(db.String(150))
    moto_marca = db.Column(db.String(20))
    moto_placa = db.Column(db.String(7))
    nombre_banco = db.Column(db.String(20))
    tipo_cuenta = db.Column(db.String(20))
    num_cuenta = db.Column(db.String(30))

    @property
    def moto_placa_upper(self):
        return self.moto_placa

    @moto_placa_upper.setter
    def moto_placa_upper(self, placa):
        if placa is None:
            pass
        else:
            self.moto_placa = placa.upper()

    # Another method that is irrelevant to this question

    def get_own_data(self):
        own_data = {
            key: value
            for (key, value) in vars(self).items()
            if key != "_sa_instance_state" and key != "moto_placa"
        }

        own_data["moto_placa_upper"] = self.moto_placa

        return own_data

    # Class methods irrelevant to this question

什么可能导致此问题?我不是 OOP 专家,我感觉我的方法和 属性 声明中存在错误,但我无法指出。

这里的问题是提交时会话中的 SQLAlchemy expires 对象。 Expiry 从持久化对象的 __dict__ 中删除所有持久化属性,因此在调用 get_own_data 时没有什么可迭代的。

有几种解决方法:

  1. 在调用get_own_data之前调用db.session.refresh(obj)刷新对象;这将触发 SELECT 来获取数据。
  2. 在创建 SQLAlchemy (db) 对象时通过传递 session_options={'expire_on_commit': False} 禁用提交时过期。这是一个全局设置,您冒着包含陈旧数据的实例的风险,因为在这种情况下 SELECTnot 发出的。
  3. 不是遍历 vars,而是遍历列名并使用 getattr 获取值;与刷新一样,这将触发 SELECT
    from sqlalchemy import inspect
    ...
        def get_own_data(self):
            insp = inspect(self)
            own_data = {
                key: getattr(self, key)
                for key in insp.mapper.columns.keys()
    
                # Exclude the following keys
                if  key != "moto_placa"
            }
    
            # Add this key, which isn't found in 'vars(self)' as it is a class property
            own_data["moto_placa_upper"] = self.moto_placa
            return own_data