Если вы используете Python Flask для своих веб-проектов, Flask-SQLAlchemy может стать вашим основным пакетом для обработки всего, что связано с базой данных. Он в основном охватывает внутренние сложности базовой SQLAlchemy и, таким образом, очень прост в использовании.
В некотором смысле это хорошо, но иногда, как и в случае с магическими функциями Ruby on Rails, простота мешает нам понять, что происходит под капотом.
Одна вещь, которую я нахожу довольно интересной, это то, что Flask-SQLAlchemy пропускает создание сеанса до и во время обработки запроса, т. е. Model.query()
, по сравнению с обычной реализацией в исходном пакете SQLAlchemy, т. е. session.query(Model)
.
В качестве иллюстрации вот как выглядит вызов запроса на Flask-SQLAlchemy:
from flask_sqlalchemy import SQLAlchemy # DB initialization db = SQLAlchemy() # Model class definition class User(db.Model): __tablename__ = “users” username = Column(db.String(80), unique=True, nullable=False) email = Column(db.String(80), unique=True, nullable=False) User.query.filter_by(id=1) # ←- no session here or anywhere above!
В отличие от того, как обычно выглядит реализация запроса при использовании стиля Декларативное сопоставление SQLAlchemy:
Обратите внимание на маркеры [point 1] и [point 2] в приведенном ниже коде, так как они будут упоминаться далее в статье.
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base # DB initialization engine = create_engine(os.environ[‘DATABASE_URL’], echo=True) # Session initialization Session = sessionmaker(bind=engine, class_=Session) # [point 1] session = Session() Base = declarative_base() # [point 2] # Mapper class definition class User(Base): # Model class definition __tablename__ = “users” username = Column(db.String(80), unique=True, nullable=False) email = Column(db.String(80), unique=True, nullable=False) session.query(User).filter_by(id=1) # ←- session is involved!
Выглядит совсем иначе, не так ли? Означает ли это, что Flask-SQLAlchemy не использует сеанс для вызова запроса?
Я потратил довольно много времени, копаясь в исходном коде Flask-SQLAlchemy, и вот что я нашел:
Обратите внимание: когда я написал базовый SQLAlchemy, я имел в виду исходный пакет Python SQLAlchemy.
1. Flask-SQLAlchemy настраивает сеанс в фоновом режиме.
# flask-sqlalchemy/src/flask_sqlalchemy/__init__.py: class SQLAlchemy: def __init__(...): ... self.session = self.create_scoped_session(session_options)
Из функции create_scoped_session(...)
в функции __init__(...)
, если углубиться на пару уровней, можно обнаружить, что она возвращает сеанс, вызванный методом sessionmaker(...)
:
return orm.sessionmaker(class_=SignallingSession, db=self, **options) # [equivalent to point 1 above]
Теперь мы знаем, что Flask-SQLAlchemy также поддерживает сессию, и то, как она генерируется, очень похоже на то, как базовая SQLAlchemy генерирует сессию!
2. Flask-SQLAlchemy устанавливает свой атрибут модели, вызывая «declarative_base».
# flask-sqlalchemy/src/flask_sqlalchemy/__init__.py: class SQLAlchemy: def __init__(...): .... self.Model = self.make_declarative_base(model_class, metadata)
где self.make_declarative_base(...)
фактически устанавливает:
def make_declarative_base(self, model, metadata=None): ... model = declarative_base(...) # [equivalent to point 2 above]
И да, в базовом SQLAlchemy мы также вызываем declarative_base
для определения объекта сопоставления.
3. Атрибут запроса модели Flask-SQLAlchemy на самом деле является объектом запроса SQLAlchemy (для которого требуется сеанс)
# flask-sqlalchemy/src/flask_sqlalchemy/__init__.py: class SQLAlchemy: ... def make_declarative_base(self, model, metadata=None): ... model.query = _QueryProperty(self)
_QueryProperty
возвращает type.query_class(mapper, session=self.sa.session())
, где type.query_class(...)
на самом деле устанавливается в класс Query SQLAlchemy, т.е.
sqlalchemy.orm.query.Query(entities, session)
.
В качестве параллельного сравнения, если вы посмотрите в Документации по запросам SQLAlchemy о том, как он строит запрос:
Query([User, Address], session=some_session)
эквивалентно:some_session.query(User, Address)
… Вот так. Из приведенного выше сравнения видно, что в первом случае объект запроса назначается атрибуту запроса Flask-SQLAlchemy, а во втором случае объект запроса создается в базовом SQLAlchemy примерно так.
В заключение, model.query
Flask-SQLAlchemy фактически включает сеанс через базовый SQLAlchemy sqlalchemy.orm.query.Query(entities, session)
. Вот где генерируется сессия.