【问题标题】:Avoiding boilerplate session handling code in sqlalchemy functions在 sqlalchemy 函数中避免样板会话处理代码
【发布时间】:2013-01-25 18:57:52
【问题描述】:

我有一个使用 sqlalchemy 的具有许多小型数据库访问功能的 python 应用程序。我试图避免围绕这些函数使用大量样板会话处理代码。

我有很多看起来像这样的函数:

def get_ticket_history(Session, ticket_id):
    s = Session()
    try:
        rows = s.query(TicketHistory)\
                .filter(TicketHistory.ticket_fk==ticket_id)\
                .order_by(TicketHistory.id.desc()).all()
        s.commit()
        return rows
    except:
        s.rollback()
        raise
    finally:
        s.close()

我正在尝试重构这些功能,但不确定我是否有最好的方法。我目前拥有的最好的如下:

def execute(Session, fn, *args, **kwargs):
    s = Session()
    try:
        ret = fn(s, *args, **kwargs)
        s.commit()
        return ret
    except:
        s.rollback()
        raise
    finally:
        s.close()

def get_ticket_history(self, ticket_id):
    def sql_fn(s):
        return s.query(TicketHistory)\
                .filter(TicketHistory.ticket_fk==ticket_id)\
                .order_by(TicketHistory.id.desc()).all()
    return execute(self.sentinel_session, sql_fn)

有没有更好或更惯用的方法来做到这一点?也许使用装饰器?

谢谢, 乔恩

【问题讨论】:

  • context manager 是一个很好的选择。

标签: python session sqlalchemy


【解决方案1】:

SQLAlchemy 文档提供了一种使用上下文管理器执行此操作的可能方法。

http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it

为了完整起见,这里复制代码sn-p:

from contextlib import contextmanager

@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()

这个session_scope可以干净利落地使用了,现在不用重复样板了。

class ThingOne(object):
    def go(self, session):
        session.query(FooBar).update({"x": 5})

class ThingTwo(object):
    def go(self, session):
        session.query(Widget).update({"q": 18})

def run_my_program():
    with session_scope() as session:
        ThingOne().go(session)
        ThingTwo().go(session)

【讨论】:

  • SQLAlchemy 开发人员记录了一个可能的、可能的和简单的实现,它很好地解决了会话的生命周期问题。为什么他们不加倍努力将其作为内置函数提供,而不是让所有库用户在他们的代码库中重写该代码的一个版本?
【解决方案2】:

从 Sql alchemy 1.4 版开始: Session 可以用作上下文管理器,而无需使用外部辅助函数。

来自文档的示例

Session = sessionmaker(engine)

with Session() as session:
    session.add(some_object)
    session.add(some_other_object)
    session.commit()

要开始,提交事务并关闭会话,可以应用以下方法。 会话 = sessionmaker(引擎)

with Session.begin() as session:
    session.add(some_object)
    session.add(some_other_object)
# commits transaction, closes session 

文档: https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker

【讨论】:

    【解决方案3】:

    morphyn 使用上下文管理器的建议很好。您可以通过将contextlib.contextmanager 装饰器应用于与您的第一个get_ticket_history 非常相似的函数来创建这样的上下文管理器,将try 和except 之间的代码替换为yield 语句并将其重命名,例如transactionPEP 343 有一个与该名称几乎相同的示例。

    然后,使用带有 with 语句的上下文管理器来重新实现 get_ticket_history。不过,看起来 SQLAlchemy 已经提供了该功能,作为方法 begin:

    http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#autocommit-mode

    【讨论】:

      【解决方案4】:

      使用with 子句处理事务(开始、提交/回滚)

      with engine.begin() as connection:
          r1 = connection.execute(table1.select())
          connection.execute(table1.insert(), {"col1": 7, "col2": "this is some data"})
      

      老问题,但我还是偶然发现了它,所以这里是文档中的相关链接: https://docs.sqlalchemy.org/en/13/core/connections.html

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多