【发布时间】: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