SQLAlchemyMiddleTechnical
Что такое scoped_session и зачем он используется в веб-фреймворках, таких как Flask?
scoped_session — реестр, возвращающий одну сессию на поток (или другой scope). Используется во Flask/WSGI для доступа к 'текущей' сессии без передачи явно. В async-приложениях не применяется.
scoped_session в SQLAlchemy
scoped_session — это реестр сессий, который возвращает одну и ту же сессию для одного и того же контекста (по умолчанию — потока). Это позволяет нескольким частям приложения обращаться к «текущей» сессии, не передавая её явно.
Как работает
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
engine = create_engine("postgresql+psycopg2://user:pass@localhost/db")
SessionFactory = sessionmaker(bind=engine)
# scoped_session оборачивает фабрику
db_session = scoped_session(SessionFactory)
# В любом месте приложения — получаем ту же сессию для текущего потока
def get_users():
return db_session.query(User).all() # legacy-API, но работает
def create_user(name: str):
user = User(name=name)
db_session.add(user)
db_session.commit()
# В конце запроса — обязательно убираем сессию из реестра
def teardown():
db_session.remove() # закрывает сессию и убирает из registry
Использование в Flask
from flask import Flask
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session
app = Flask(__name__)
engine = create_engine(app.config["DATABASE_URL"])
db_session = scoped_session(sessionmaker(bind=engine))
@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove() # критически важно — иначе соединения утекут
@app.route("/users")
def list_users():
return [{"id": u.id} for u in db_session.query(User).all()]
Кастомный scope (не по потоку)
import threading
# По умолчанию scope = threading.get_ident()
# Можно передать собственную функцию — например, ID запроса
from contextvars import ContextVar
_request_id: ContextVar[int] = ContextVar("request_id")
def request_scope() -> int:
return _request_id.get()
db_session = scoped_session(SessionFactory, scopefunc=request_scope)
scoped_session vs sessionmaker в современном коде
- Flask + WSGI (многопоточный) →
scoped_sessionпо-прежнему актуален. - FastAPI / async → используйте
AsyncSessionчерез dependency injection (Depends);scoped_sessionтам не нужен. - Flask-SQLAlchemy 3.x управляет сессией сам через
app.teardown_appcontext.
Подводные камни
- Забыть db_session.remove(): сессия не закрывается, соединение с БД висит. В долгоживущих потоках это приводит к исчерпанию пула.
- Многопоточность без scoped_session: одна сессия не безопасна для нескольких потоков — операции над объектами corrupted state.
- scoped_session в async:
scoped_sessionиспользуетthreading.get_identпо умолчанию — в asyncio все корутины выполняются в одном потоке, поэтому разные запросы получат одну сессию. Это критическая ошибка. - Прокси-интерфейс:
db_session— прокси, который делегирует к реальной сессии. Его можно передавать в функции, но нельзя сохранять результатdb_session()и использовать долго. - remove() vs close():
close()закрывает сессию, но оставляет её в реестре.remove()закрывает и удаляет из реестра — следующий вызов создаст новую.
Common mistakes
- Описывать scoped session только как термин и не показывать механизм на минимальном примере.
- Игнорировать ошибки, пустые данные, конкурентный доступ или границы транзакции.
- Не связывать поведение с официальным контрактом SQLAlchemy и реальной эксплуатацией.
What the interviewer is testing
- Объясняет scoped session через последовательность действий, а не через набор ключевых слов.
- Приводит короткий кодовый пример или production-сценарий с ожидаемым поведением.
- Называет хотя бы один риск: производительность, безопасность, транзакции, память или сопровождение.