Постоянные сеансы для подпрограмм с sqlalchemy contextmanager? - PullRequest
0 голосов
/ 19 сентября 2019

У меня есть приложение на Python с несколькими небольшими функциями взаимодействия с базой данных, которые используют sqlalchemy.Примером небольшого взаимодействия является foo(), которое извлекает значение bar_id из таблицы Bar или добавляет значение по имени, генерирует идентификатор и возвращает новый идентификатор, если bar_name не найдено.У меня также есть более крупные взаимодействия с базой данных (пример: big_func()), которые объединяют меньшие взаимодействия, такие как foo():

@contextmanager
def session_scope():
    """Provide a transactional scope around a series of operations."""
    session = Session()
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()

def foo(bar_name, session):
    """
    Look up bar by name and return bar_id. 
    If bar doesn't exist in database, add it and get an id.
    """
    db_bar = session.query(Bar) \
        .filter_by(bar_name=bar_name) \
        .first()
    if db_bar is None:
        db_bar = Bar(bar_name=bar_name)
        session.add(db_bar)
        session.flush()
    return db_bar.bar_id

def big_func(df):
    with session_scope() as session:
        widg_id = foo("widget", session)

        # 1. calculations and other changes
        # 2. add and commit additional data to database

Мой вопрос касается управления сеансами.Я хотел бы выполнить все следующие условия:

  1. foo() можно вызвать напрямую, не передавая ему сеанс.В этом случае он создает сеанс, фиксирует изменения и закрывает сеанс.
  2. Когда вызывается big_func, все изменения в базе данных либо фиксируются, либо откатываются вместе.То есть foo() совместно использует сеанс с big_func() и не вызывает session.commit().
  3. Дополнительные меньшие взаимодействия foo_2(), foo_3() и т. Д. Могут вести себя аналогично, не копируя биопланшет.

В приведенном выше коде условие 1 не выполняется.Мое решение сделать foo() вызываемым независимо требует стека шаблонов и не использует контекстный менеджер:

def foo(bar_name, session=None):
    if session is None:
        session = Session()
        session_is_local = True
    else:
        session_is_local = False

    db_bar = session.query(Bar) \
        .filter_by(bar_name=bar_name) \
        .first()
    if db_bar is None:
        db_bar = Bar(bar_name=bar_name)
        session.add(db_bar)
        session.flush()

    if session_is_local:
        session.commit()
        session.close()

    return db_bar.bar_id

Есть ли лучший или более идиоматический способ сделать это?

...