Как избежать загрузки атрибутов отношений, если объект не является постоянным в сеансе SQLAlchemy при кэшировании из засоленного объекта - PullRequest
0 голосов
/ 11 января 2019

Я использую наследуемую таблицу 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, выбранные объекты не находятся в сеансе и выдают «Экземпляр не привязан к сеансу; операция обновления атрибута не может продолжить».

Ожидаемым выводом будет список словарей сериализованных пользовательских объектов.

1 Ответ

0 голосов
/ 12 января 2019

Мне удалось заставить его работать, добавив

'polymorphic_load': 'inline'

для класса Employee в mapper_args

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...