从腌制对象缓存时,如果对象在SQLAlchemy会话中不是持久性的,如何避免加载关系属性

时间:2019-01-11 16:49:01

标签: python sqlalchemy

我对两个表使用sqlalchemy联接表继承:User和Employee。 我实现了自己的“获取”方法,该方法使用Redis作为缓存将对象存储为泡菜,还实现了自己的序列化方法来创建json响应。当我获取腌制的User对象并尝试对其进行序列化时,会收到“实例未绑定到会话”的错误消息,因为查询返回的是Employee对象而不是User,并且serialize方法尝试查询诸如employee之类的关系属性。 team_id。我无法将对象合并到会话中,因为这是一项昂贵的操作,并且需要依靠我的缓存来提供响应。

我设置了要加载的关系,以便使该对象与所有关系值一起腌制并存储在缓存中(例如,变为Team表的employee.team)。 Employee.get方法及其序列化方法可以正常工作,但是当我尝试使用User.get方法时,由于多态身份,它返回Employee对象而不是User对象,并且由于用户的原因它不会加载Employee属性。表格不包含它们。 如果返回的对象类是Employee,则在序列化时它将尝试获取team属性并抛出erorr“实例未绑定到Session;属性刷新操作无法进行”,因为腌制的对象来自于User查询,但它没有。热情地加载Employee属性。

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

这是User类:

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}>'

Employee类:

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
    }

和团队课程:

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中的一种类方法

class继承自此,这就是为什么它使用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

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

用户序列化具有其核心属性

@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

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

@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({})

(用户是Employee对象的列表)

[user.serialize for user in users]

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

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

1 个答案:

答案 0 :(得分:0)

我设法通过添加使它起作用

'polymorphic_load': 'inline'

mapper_args

中的Employee类