Чтение и создание через SQLAlchemy 2
Уроки курсаЧтение и создание через SQLAlchemy 2
POST: add, commit, refresh и чтение сохранённой комнаты
В этом примере используется цепочка add → commit → refresh: регистрация объекта в сессии, фиксация транзакции и явное перечитывание атрибутов. Последний шаг выбран для наглядности; он не обязателен для INSERT, присвоения id или успешного ответа этого обработчика.
Как работает цепочка add → commit → refresh
Клиент присылает name и capacity — их проверяет Pydantic-схема RoomIn. Из неё строим ORM-объект без клиентского id: база присвоит его сама.
room = Room(name=data.name, capacity=data.capacity)
db.add(room)
db.add(room) только регистрирует объект в сессии — никакого SQL ещё нет. INSERT выполняется во время flush. Flush может произойти явно (db.flush()), но чаще всего его делает сам commit:
db.commit()
Commit сначала выполняет необходимый flush (отправляет INSERT в базу, база назначает id), а затем фиксирует транзакцию. Поэтому отдельный вызов flush перед commit в этом сценарии не нужен.
Важная деталь: explicit flush может дать вам id до commit — база уже сгенерировала значение в рамках открытой транзакции. Но rollback всё равно отменит INSERT. Пока не вызван commit, строка не стала постоянной.
После commit SQLAlchemy по умолчанию помечает атрибуты объекта устаревшими (expire_on_commit=True). Следующее обращение к атрибуту внутри открытой Session автоматически выполнит SELECT и обновит значение. Чтобы сделать это явно прямо сейчас:
db.refresh(room)
refresh выполняет SELECT по первичному ключу и перезаписывает атрибуты из базы. Он не назначает id (база сделала это при flush) и не фиксирует транзакцию. После refresh объект room содержит актуальные значения всех полей, включая сгенерированный id.
Полный пример
Пример самостоятельный — с полями name и capacity. Он использует models.py (Base, Room, RoomSchema) и database.py (get_db) из предыдущих уроков без изменений.
# schemas.py
from pydantic import BaseModel, Field
from models import RoomSchema
class RoomIn(BaseModel):
name: str = Field(..., min_length=2, max_length=50)
capacity: int = Field(..., ge=1, le=50)
# main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from models import Room, RoomSchema
from schemas import RoomIn
from database import get_db
app = FastAPI()
@app.get("/api/rooms", response_model=list[RoomSchema])
def list_rooms(db: Session = Depends(get_db)):
return db.scalars(select(Room).order_by(Room.id)).all()
@app.get("/api/rooms/{room_id}", response_model=RoomSchema)
def read_room(room_id: int, db: Session = Depends(get_db)):
room = db.get(Room, room_id)
if room is None:
raise HTTPException(status_code=404, detail="Room not found")
return room
@app.post("/api/rooms", response_model=RoomSchema, status_code=201)
def create_room(data: RoomIn, db: Session = Depends(get_db)):
room = Room(name=data.name, capacity=data.capacity)
db.add(room)
db.commit()
db.refresh(room)
return room
Что происходит при вызовах
POST /api/rooms с телом {"name": "Альфа", "capacity": 6} вернёт 201 и JSON с серверным id, например {"id": 1, "name": "Альфа", "capacity": 6}.
GET /api/rooms/1 в отдельном HTTP-запросе — а значит, в новой Session — вернёт те же данные. Это принципиальное отличие от словаря: SQLite хранит строку независимо от того, жив ли процесс Python.
Если перезапустить приложение и снова обратиться к GET /api/rooms/1, файл SQLite на диске содержит созданную строку — она никуда не делась.
GET /api/rooms/999 при отсутствующем id вернёт 404 с {"detail": "Room not found"}.
О from_attributes и response_model
В models.py RoomSchema настроена с ConfigDict(from_attributes=True). Это нужно, если вы вызываете RoomSchema.model_validate(room) напрямую — Pydantic должен знать, что может читать атрибуты объекта, а не только ключи словаря. Сам FastAPI при сериализации через response_model умеет читать атрибуты ORM-объекта и без этой настройки — но явная from_attributes=True не лишняя, если схема используется и напрямую.
Избыточные атрибуты ORM-объекта (служебные поля SQLAlchemy) в JSON не попадают: RoomSchema определяет ровно те поля, которые должен видеть клиент.

