В данном разделе мы обсудим наиболее важную для ORM концепцию, как связи (relationship). Мы обсудим как ORM взаимодействует с моделями, ссылающимися на другие (то есть находящиеся в связи с ними). А также как мы можем создавать и настраивать их.
Чтобы описать основную идею связи и relationship(), для начала давайте посмотрим на уже созданные нами модели, опуская все остальные столбцы для простоты:
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = "user_account"
# ... mapped_column() mappings
addresses: Mapped[List["Address"]] = relationship(back_populates="user")
class Address(Base):
__tablename__ = "address"
# ... mapped_column() mappings
user: Mapped["User"] = relationship(back_populates="addresses")
Наш класс User теперь имеет атрибут addresses, в свою очередь Address теперь имеет атрибут user. Конструкция relationship() в связке с Mapped аннотацией может быть использована для работы со связью между User и Address. Поскольку таблица адресов имеет ограничение внешнего ключа (ForeignKeyConstraint), которое ссылается на таблицу user_account, relationship() может автоматичсески определить что это связь типа один-ко-многим к User.adresses из Adress. Или иначе говоря, несколько адресов могут быть связаны с одним пользователем.
Любые связи один-ко-многим могут быть слегкостью инвертированы до многие-к-одному, это настраивается с помощью параметра back_populates в relationship().
Начнем разбираться с тем как работает relationship(). Если мы создадим новый объект User, то заметим, что атрибуту addresses теперь имеет тип list:
>>> u1 = User(name="pkrabs", fullname="Pearl Krabs")
>>> u1.addresses
[]
Этот объект на самом деле не является списком в чистом виде, это его версия от Алхимии, которая хотя и полностью повторяет его поведение, но всё же также отслеживает изменения, которые с ним происходят. Этот список появится автоматически при любой попытке обратиться к этому атрибуту. Поскольку u1 до сих пор transient и список addressess не был изменен, мы еще не имеем ассоциации с записью в БД.
Давайте теперь добавим в наш список объект Address:
>>> a1 = Address(email_address="[email protected]")
>>> u1.addresses.append(a1)
Теперь, запринтим этот список:
>>> u1.addresses
[Address(id=None, email_address='[email protected]')]
Так как мы еще не добавили объект User в сессию, то и Address тоже является лишь потенциальной строкой в БД. Но при этом мы уже можем увидеть, что объект User находится в Address:
>>> a1.user
User(id=None, name='pkrabs', fullname='Pearl Krabs')
Это происходит поскольку мы определили параметр back_populates для связи между моделями. Обратите внимание, что еще один способ связать два объекта, это не добавлять его в список у объекта User, а, наоборот, указать объект User при создании Address:
>>> a2 = Address(email_address="[email protected]", user=u1)
>>> u1.addresses
[Address(id=None, email_address='[email protected]'), Address(id=None, email_address='[email protected]')]
Ну и конечно мы можем сделать тоже самое следующим образом:
>>> a2.user = u1