【问题标题】:How to avoid loading relationship attributes if object is not persistent in SQLAlchemy session when caching from pickled object从腌制对象缓存时,如果对象在 SQLAlchemy 会话中不持久,如何避免加载关系属性
【发布时间】:2019-01-11 16:49:01
【问题描述】:

我正在为两个表使用 sqlalchemy 连接表继承:用户和员工。 我实现了自己的“get”方法,该方法使用 Redis 作为缓存将对象存储为 pickle,还实现了自己的序列化方法来创建 json 响应。当我得到腌制的用户对象并尝试对其进行序列化时,我收到一个错误“实例未绑定到会话”,因为查询返回的是员工对象而不是用户,并且序列化方法尝试查询像员工这样的关系属性。 team_id。我无法将对象合并到会话中,因为这是一项代价高昂的操作,并且我需要依靠我的缓存来提供响应。

我将关系设置为急切加载,以便对象被腌制并与其所有关系值一起存储在缓存中(例如,employee.team 与 Team 表相关)。 Employee.get 方法及其序列化方法工作正常,但是当我尝试使用 User.get 方法时,由于多态标识,它返回 Employee 对象而不是 User 对象,并且它不加载 Employee 属性,因为 User表不包括它们。 如果返回的对象类是 Employee,则在序列化时它会尝试获取团队属性并抛出错误“实例未绑定到会话;属性刷新操作无法继续”,因为腌制对象来自用户查询并且它没有t 急切地加载 Employee 属性。

我的问题是,有没有办法在查询 User 表时获取 User 对象,以便我的 User.get 方法使用它对应的序列化方法?或者,如果对象在会话中不持久,我是否可以阻止对象获取关系属性(如employee.team)?

这是用户类:

class User(db.Model):
    __tablename__ = 'users'
    # Basic user data
    id = db.Column(db.GUID(), default=uuid.uuid4(), primary_key=True, autoincrement=False)
    username = db.Column(db.String(20), index=True)
    type_id = db.Column(db.Integer, db.ForeignKey('user_types.id'))
    type = db.relationship('UserType', back_populates='users', lazy='subquery')
    password = db.Column(db.String(40), default=None)
    # Basic contact info
    first_name = db.Column(db.String(60), index=True)
    last_name = db.Column(db.String(60), index=True)
    email = db.Column(db.String(128), index=True)
    phone = db.Column(db.String(30), index=True, default=None)
    # Personal info
    birth_date = db.Column(db.Date(), index=True, default=None)
    birth_city = db.Column(db.String(60), index=True, default=None)
    rfc = db.Column(db.String(50), index=True, default=None)
    curp = db.Column(db.String(30), index=True, default=None)
    address_id = db.Column(db.Integer, db.ForeignKey('addresses.id'), default=None)

    __mapper_args__ = {
        'polymorphic_identity': 1,
        'polymorphic_on': type_id
    }

多态身份的 UserType 类:

class UserType(db.Model):
    """
    Create a UserType table
    """
    __tablename__ = 'user_types'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), unique=True)
    users = db.relationship('User', back_populates='type', lazy='select')

    @property
    def serialize(self):
        return {
            "id": self.id,
            "name": self.name
        }

    def __repr__(self):
        return f'<UserType: {self.id} - {self.name}>'

员工类:

class Employee(User):
    __tablename__ = 'employees'
    # Basic employee data
    id = db.Column(db.GUID(), db.ForeignKey('users.id'), default=uuid.uuid4(), primary_key=True, autoincrement=False)
    # Operation info
    goal = db.Column(db.Integer, db.CheckConstraint('goal>=0'), default=0)
    team_id = db.Column(db.Integer, db.ForeignKey('teams.id'))
    team = db.relationship('Team', back_populates='employees', lazy='subquery')

    __mapper_args__ = {
        'polymorphic_identity': 2
    }

还有 Team 类:

class Team(db.Model):
    __tablename__ = 'teams'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), unique=True)
    type = db.Column(db.String(30))
    # eagerly load the team attribute
    employees = db.relationship('Employee', back_populates='team', lazy='select')

    def __repr__(self):
        return '<Team: {}>'.format(self.name)

get 方法是 User 类 Employee 中的一个类方法

类继承自 this,这就是它使用 cls.query 进行查询的原因:

@classmethod
def list(cls, args):
    cache_key = f'{cls.__name__}s:{build_key_from_arguments(**args)}'
    cache = redis_db.get(cache_key)
    if cache:
        return None, cls.pickle_load(cache)
    else:
        query = cls.query.filter(
                cls.visible == True
            )
        # More filters...
        data = query.all()
        redis_db.set(cache_key, pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL) if data else REDIS_LIST_NONE)
        return data

最后,serialize 方法看起来是这样的,每个类都有自己的:

用户序列化有其核心属性

@property
def serialize(self):
    obj = {
        "id": str(self.id),
        "username": self.username,
        "type": self.type.serialize,
        "status": self.status.serialize,
        "role": self.role.serialize,
        "first_name": self.first_name,
        "last_name": self.last_name,
        "email": self.email
    }
    return obj

Employee 序列化添加自己的属性(如果存在)

@property
def serialize(self):
    obj = {
        "id": str(self.id),
        "username": self.username,
        "type": self.type.serialize,
        "status": self.status.serialize,
        "role": self.role.serialize,
        "first_name": self.first_name,
        "last_name": self.last_name,
        "email": self.email
    }
    if self.team_id:
        obj['team'] = self.team.serialize
    return obj

当我执行以下操作时:

users = User.list({})

(users 是 Employee 对象的列表)

[user.serialize for user in users]

第一次有效,实际查询时,但第二次从Redis检索数据时,腌制对象不在会话中并抛出“实例未绑定到会话;属性刷新操作不能继续”。

预期的输出将是序列化用户对象的字典列表。

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    我设法通过添加使其工作

    'polymorphic_load': 'inline'
    

    ma​​pper_args中的Employee类

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      • 2021-03-13
      • 1970-01-01
      相关资源
      最近更新 更多