Я использую наследуемую таблицу sqlalchemy для двух таблиц: User и Employee.
Я реализовал свой собственный метод get, который использует Redis в качестве кэша для хранения объектов в виде pickle, а также реализовал собственный метод serialize для создания ответов json. Когда я получаю выбранный объект «Пользователь» и пытаюсь его сериализовать, я получаю сообщение об ошибке «экземпляр не связан с сеансом», поскольку запрос возвращает объект «Сотрудник» вместо «Пользователь», а метод сериализации пытается запросить атрибуты отношения, такие как «сотрудник». team_id. Я не могу объединить объект с сеансом, потому что это дорогостоящая операция, и мне нужно полагаться на свой кэш для обслуживания ответов.
Я установил отношения для быстрой загрузки, чтобы объект подвергался травлению и сохранялся в кэше со всеми его реляционными значениями (например, employee.team, который реализуется в таблице Team).
Метод Employee.get и его метод serialize работают нормально, но когда я пытаюсь использовать метод User.get, он возвращает объекты Employee вместо объектов User из-за полиморфной идентичности и не загружает атрибуты Employee, поскольку пользователь таблица не включает их.
Если класс возвращаемого объекта - Employee, при сериализации он пытается получить атрибут team и выдает erorr «Экземпляр не привязан к сеансу; операция обновления атрибута не может продолжаться», потому что выбранный объект получен из запроса пользователя, и он не ' загружать атрибуты сотрудника.
У меня вопрос: есть ли способ получить объекты 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
}
И команда класса:
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
класс наследуется от этого, поэтому он использует 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, выбранные объекты не находятся в сеансе и выдают «Экземпляр не привязан к сеансу; операция обновления атрибута не может продолжить».
Ожидаемым выводом будет список словарей сериализованных пользовательских объектов.