Ошибка возникает из-за того, что атрибут .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)]