Страницы

Поиск по вопросам

вторник, 10 декабря 2019 г.

Как правильно обращаться к объекту сессии?

#python #postgresql #flask #sqlalchemy


Есть код:

app = Flask(__name__)
db = SQLAlchemy(app)  


def make_db_session(engine):
    return scoped_session(sessionmaker(autocommit=False,
        autoflush=True,
        bind=engine))    

def make_db_engine():
    return create_engine(app.config['SQLALCHEMY_DATABASE_URI'], convert_unicode=True)

engine = make_db_engine()
db_session = make_db_session(engine)


Далее, я вижу несколько примеров обращения к сессии.


Первый вариант

db.session.some_action()

Второй вариант

db_session.some_action()

Третий вариант

session = db_session()
session.some_action()



В чем их различия? Какой способ является более верным?

П.С. Еще интересно то, что если делать выборку следующим образом

model = SomeModel.query.filter(some_filter).first()
model.field = new_value


То сохранять необходимо как

db.session.commit()


То есть, создается впечатление, что по умолчанию используется сессия db.session.
    


Ответы

Ответ 1



Можно начать с отличий: Функция make_db_session возвращает объект типа ScopedSession (scoped_session - это класс такой, который зачем-то назвали как функцию). scoped_session - обычная такая сессия, но помимо бла-бла-бла про потоки, основное ее отличие, видимое пользователям, в следующем: # Обычные сессии Session = sessionmaker(__config_here__) session_1 = Session() session_2 = Session() my_db_entry = Entry(slug='Yolo') session_1.add(my_db_entry) # На этом моменте my_db_entry привязан в первой сессии session_2.add(my_db_entry) # На этом моменте возникнет исключение То есть добавить один и тот же объект с разных сессий не выйдет. ScopedSession - это на самом деле одна и та же сессия всегда (в данном потоке, пояснения ниже). Т.е. session_factory = sessionmaker(bind=some_engine) print(type(session_factory)) >>> session_registry = ScopedSession(session_factory) print(type(session_registry)) >>> При вызове session_registry() (__call__) без параметров возвращается то, что лежит в регистре. Регистр - экземпляр класса ThreadLocalRegistry. То, что туда однажды положили, изменить нельзя (На самом деле я не уверен - возможно, в методе баг или лишний код - он как-то странно написан - смотри с 64 строки в sqlalchemy/orm/scoping.py). Также, если вызовете session_registry() из другого потока - то в нем уже будет другая сессия, созданная специально для него. То есть "thread-local" в документации означает, что для каждого потока своя сессия, а не то, что сессии как-то хитро синхронизируются, ставят локи и т.д. Это достигается использованием threading.local() в регистре. session_1 = session_registry() session_2 = session_registry() print(type(session1), type(session_2)) >>> session_1 is session_2 >>> True это одна и та же сессия. Это означает, например, что регистр с сессией можно сделать глобальным. Также необязательно создавать объекты класса Session (session_1 и session-2) - так как сессия одна вполне зайдет session_registry.query(MyClass).all() Также в том же разделе есть наглядная диаграмма, показывающая когда нужно создавать регистр, когда нужно создавать и закрывать сессию. Что использовать "правильнее" сказать сложно, но я бы сказал, что scoped_session, а не создавать свою гольную Session(...), потому что сессии не потоко-безопасны и придется создавать на каждый поток новую сессию, либо синхронизироваться как-то. Чтобы использовать свою сессию, а не предоставленную flask-sqlalchemy, Есть несколько путей: вызывать query на нужной сессии: session.query(MyObject.prop, AnotherOne.prop2).filter(blah-blah) Также вы можете на свои модели установить "сессию по умолчанию". У каждой модели может быть поле query, куда можно присунуть "запросник" (на этом моменте все начинает быть настолько запутанно, что не поддается объяснению). Примерно как-то так. Также есть небольшое обсуждение разницы между этими подходами на ENSO. Update: Возвращаясь к тому, какой вариант "верный", скажу, что второй и третий равнозначны, но использовать стоит третий, чтобы точно знать, что используется объект Session, а не какой-то иной. Но тогда зачем использовать flask-SQLAlchemy? Выходит, что многое из того, что эта библиотека прячет от пользователя (настройки сессии, обратные вызовы типа before_rollback, after_commit, after_flush и т.д. навешаны на библиотечную сессию. Также используется свой особенный "запросник" - класс orm.Query со всякими плюшками, вроде автоматической отправки 404, если нет объекта в БД. Логи всякие, встроенная пагинация (постраничные запросы в БД). Также сессия по умолчанию - db.session - во время создания моделей именно она записывается в свойство query. Вы можете убедиться в этом, отыскав класс SQLAlchemy в пакете, в нем метод __init__, а в нем уже строчку, в которой инициализируется поле Model (которое затем используется как базовый класс). Это поле инициализируется результатом функции make_declarative_base и вот как эта функция выглядит (довольно простая): def make_declarative_base(self, metadata=None): """Creates the declarative base.""" base = declarative_base(cls=Model, name='Model', metadata=metadata, metaclass=_BoundDeclarativeMeta) base.query = _QueryProperty(self) return base Как видно, заветный query инициализируется своим классом, который создается именно с сессией flask-SQLAlchemy, а не какой-нибудь иной. Т.о. сессия по умолчанию была явно объявлена в базовом классе db.Model. Можно поменять ее, перезаписав query на тот, что нужен.

Комментариев нет:

Отправить комментарий