Доступ к ссылкам на отношения при фильтрации в SQLAlchemy - PullRequest
1 голос
/ 09 мая 2019

У меня есть таблица с именем ExtendedUser, которая имеет отношение один к одному с таблицей User с обратным указателем с именем extended_user в таблице User.Таблица User имеет отношение один ко многим с таблицей UserPosts с обратным ссылочным именем posts в таблице User.

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)

class UserPost(Base):
    __tablename__ = "user_posts"

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id"))
    user = relationship("User", backref="posts")

class ExtendedUser(Base):
    __tablename__ = "extended_users"

    user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
    user = relationship("User", backref="extended_user", uselist=False, lazy="joined")

Начиная с ExtendedUser, я хочу выбратьвсе ExtendedUser с которых User не имеет сообщения.Так что я безуспешно пытался сделать это

sess.query(ExtendedUser).filter(not_(ExtendedUser.user.posts.any()))

Но это не работает, я получаю сообщение об ошибке:

AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with ExtendedUser.user has an attribute 'posts'

Как я могу смоделировать свой запрос так, чтобы только ExtendedUser чьи User вернули UserPost?

1 Ответ

0 голосов
/ 10 мая 2019

Ошибка возникает из-за того, что атрибут .user отличается в зависимости от того, обращаетесь ли вы к нему из класса или из экземпляра класса. Например, это вызывает исключение, которое вы получаете:

ExtendedUser.user.posts

Это потому, что доступ к .user в классе ExtendedUser возвращает объект InstrumentedAttribute, а экземпляры InstrumentedAttribute не имеют атрибута с именем posts.

Это работает:

inst = ExtendedUser()
inst.user.posts

Вышеуказанное работает, потому что мы получили доступ к .user на экземпляре из ExtendedUser, который возвращает экземпляр из User, который имеет атрибут с именем posts .

Это отличающееся поведение между доступом к атрибутам класса и экземпляра контролируется протоколом дескриптора Python .

Один из способов достижения вашей цели - использовать подзапрос для запроса уникальных user_id s в таблице user_posts и проверить, что ExtendedUser user_id не получен в результате:

q = sess.query(ExtendedUser).\
    filter(
        not_(
            ExtendedUser.user_id.in_(
                sess.query(UserPost.user_id).distinct()
            )
        )
    )

Ниже приведен рабочий пример, но сначала мне пришлось немного изменить определение ExtendedUser.user:

class ExtendedUser(Base):
    ...
    user = relationship(
        "User", backref=backref("extended_user", uselist=False), lazy="joined")

Обратите внимание на использование функции backref, которая позволяет мне установить uselist=False на User.extended_user. В вашем примере uselist=False на ExtendedUser.user, но в нем нет необходимости, поскольку внешний ключ определен на ExtendedUser, поэтому ExtendedUser.user может указывать только на одного пользователя, и sqlalchemy автоматически узнает, что коллекция не должна быть списком. Без этого изменения я получил бы исключение TypeError: Incompatible collection type: ExtendedUser is not list-like.

Хорошо, вот пример:

sess = Session()
user_1 = User(extended_user=ExtendedUser())
user_2 = User(extended_user=ExtendedUser())
user_3 = User(extended_user=ExtendedUser())
user_1.posts = [UserPost()]
sess.add_all([user_1, user_2, user_3])
sess.commit()
q = sess.query(ExtendedUser).\
    filter(
        not_(
            ExtendedUser.user_id.in_(
                sess.query(UserPost.user_id).distinct()
            )
        )
    )

print(q.all())  # [ExtendedUser(user_id=2), ExtendedUser(user_id=3)]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...