Один из самых популярных пакетов для работы с базами данных — sqlalchemy. Он позволяет конструировать запросы для различных баз данных. Рассмотрим основные практические моменты в работе с sqlalchemy.
from sqlalchemy import Column, ForeignKey, String, Integer, Boolean, Tablefrom sqlalchemy.orm import declarative_basefrom table_base import Basefrom myrelationship import MyRelationshipBase =declarative_base()classTableA(Base): __tablename__ ="table_a" key =Column(String(40), primary_key=True) value =Column(Integer, ForeighKey('table_b.id')) b =Column(Boolean)
Base — это базовый класс, который будет хранить всю информацию о вашей будущей схеме базы. При этом, он должен быть определен только один раз. Все другие таблицы, должны наследоваться от Base.
На базовом уровне это выглядит следующим образом. То есть, мы отключаем поддержку коллекций и тем самым добиваемся One-to-One отношения. Это нужно, когда у родителя может быть только один ребенок, а у ребенка — один родитель.
classParent(Base): __tablename__ ="parent_table"id=Column(Integer, primary_key=True)# previously one-to-many Parent.children is now# one-to-one Parent.child child =relationship("Child", back_populates="parent", uselist=False)classChild(Base): __tablename__ ="child_table"id=Column(Integer, primary_key=True) parent_id =Column(Integer, ForeignKey("parent_table.id"))# many-to-one side remains, see tip below parent =relationship("Parent", back_populates="child")
Для двунаправленной связи, чтобы Child видел Parent целиком, а не только id (далее не надо будет делать отдельный запрос на уровне кода), изменим Child и Parent следующим образом:
Мы создаем ассоциативную таблицу, которая хранит все уникальные пары ключей из обоих таблиц и подключаем ее через свойство secondary. Через свойство back_populates настраиваем связь.
from sqlalchemy.orm import declarative_basefrom sqlalchemy import Column, Integerfrom sqlalchemy import select, insert, updateBase =declarative_base()classMyTable(Base): __tablename__ ="my_table"id=Column(Integer, primary_key=True)# default: auto increment value =Column(String)# INSERTquery = (insert(MyTable).values( value="some" ).returning(MyTable.id, MyTable.value))print(query)# SELECTprint(select(MyTable))print(select(MyTable).where(MyTable.id ==1))# UPDATEfrom pydantic import BaseModelclassValue(BaseModel): value:strvalue =Value(**{"value": "some"})query = (update(MyTable).where(MyTable.id ==1).values(value.dict()))# Такое сработает только если все ключи в PyDantic объекте совпадают с именами колонок в таблице# Иначе прописываем все поля вручную# DELETEdelete(MyTable).where(MyTable.id ==id)
Работа с relationships
classFirst(Base): __tablename__ ="first_table"id=Column(Integer, primary_key=True) seconds =relationship("Second", secondary=association_table, back_populates="firsts")select(First.seconds)# И мы получаем достаточно сложный запрос, который вернет нам эти данныеFirst.seconds.remove(somechild)# И это автоматически удалит запись и из secondary. Самим ходить и удалять допом ничего не надо
Паттерн Provider -> Repository -> Database
Есть популярный паттерн организации кода при работе с базой (из джавы, на сколько его правильно применять в Python — хз, но так понятнее что происходит). Суть в отделении бизнес-логики (это Provider), CRUD-операций (это Repository) и работы с базой (это Database).
Выполнение запросов к базе делаем через пакет databases. Только схему базы в бд создаем через sqlalchemy (из объекта Base).
import databasesimport sqlalchemyfrom base import Base # from sqlalchemy.orm import declarative_base# Base = declarative_base()database = databases.Database(DATABASE_URL)engine = sqlalchemy.create_engine(DATABASE_URL)Base.metadata.create_all(engine)database.connect()# ...database.disconnect()