【问题标题】:With sqlalchemy how to dynamically bind to database engine on a per-request basis使用 sqlalchemy 如何根据每个请求动态绑定到数据库引擎
【发布时间】:2010-12-23 20:51:19
【问题描述】:

我有一个基于 Pylons 的 Web 应用程序,它通过 Sqlalchemy (v0.5) 连接到 Postgres 数据库。为了安全起见,我没有使用普通的 Postgres 用户(例如“webapp”),而不是遵循简单 Web 应用程序的典型模式(如几乎所有教程中所见),而是要求用户输入自己的 Postgres 用户名和密码,并使用它来建立连接。这意味着我们可以充分利用 Postgres 的安全性。

更复杂的是,有两个独立的数据库要连接。尽管它们目前在同一个 Postgres 集群中,但它们需要能够在以后移动到不同的主机。

我们正在使用 sqlalchemy 的 declarative 包,虽然我看不出这与此事有任何关系。

大多数 sqlalchemy 示例都展示了一些简单的方法,例如在应用程序启动时使用通用数据库用户 ID 和密码设置一次元数据,通过 Web 应用程序使用。这通常使用 Metadata.bind = create_engine() 完成,有时甚至在数据库模型文件中的模块级别。

我的问题是,我们如何才能在用户登录之前推迟建立连接,然后(当然)为每个后续请求重新使用这些连接,或者使用相同的凭据重新建立它们。

我们有这个工作——我们认为——但我不仅不确定它的安全性,而且我还认为它在这种情况下看起来非常重要。

在 BaseController 的 __call__ 方法中,我们从 Web 会话中检索用户 ID 和密码,为每个数据库调用一次 sqlalchemy create_engine(),然后调用一个例程重复调用 Session.bind_mapper(),每个表一次可能在这些连接中的每一个上都被引用,即使任何给定的请求通常只引用一个或两个表。它看起来像这样:

# in lib/base.py on the BaseController class
def __call__(self, environ, start_response):

    # note: web session contains {'username': XXX, 'password': YYY}
    url1 = 'postgres://%(username)s:%(password)s@server1/finance' % session
    url2 = 'postgres://%(username)s:%(password)s@server2/staff' % session

    finance = create_engine(url1)
    staff = create_engine(url2)
    db_configure(staff, finance)  # see below
    ... etc

# in another file

Session = scoped_session(sessionmaker())

def db_configure(staff, finance):
    s = Session()

    from db.finance import Employee, Customer, Invoice
    for c in [
        Employee,
        Customer,
        Invoice,
        ]:
        s.bind_mapper(c, finance)

    from db.staff import Project, Hour
    for c in [
        Project,
        Hour,
        ]:
        s.bind_mapper(c, staff)

    s.close()  # prevents leaking connections between sessions?

所以 create_engine() 调用发生在每个请求上......我可以看到这是需要的,并且连接池可能会缓存它们并明智地做事。

但是在 every 请求时为 each 表调用一次 Session.bind_mapper() 吗?似乎必须有更好的方法。

显然,由于对强大安全性的渴望是所有这一切的基础,我们不希望为高安全性用户建立的连接无意中被低安全性用户在以后的请求中使用。

【问题讨论】:

    标签: python postgresql web-applications sqlalchemy pylons


    【解决方案1】:

    我会查看连接池,看看您是否找不到让每个用户拥有一个池的方法。 当用户会话过期时,您可以dispose()

    【讨论】:

      【解决方案2】:

      将全局对象(映射器、元数据)绑定到特定于用户的连接不是好方法。以及使用范围会话。我建议为每个请求创建新会话并将其配置为使用特定于用户的连接。以下示例假定您为每个数据库使用单独的元数据对象:

      binds = {}
      
      finance_engine = create_engine(url1)
      binds.update(dict.fromkeys(finance_metadata.sorted_tables, finance_engine))
      # The following line is required when mappings to joint tables are used (e.g.
      # in joint table inheritance) due to bug (or misfeature) in SQLAlchemy 0.5.4.
      # This issue might be fixed in newer versions.
      binds.update(dict.fromkeys([Employee, Customer, Invoice], finance_engine))
      
      staff_engine = create_engine(url2)
      binds.update(dict.fromkeys(staff_metadata.sorted_tables, staff_engine))
      # See comment above.
      binds.update(dict.fromkeys([Project, Hour], staff_engine))
      
      session = sessionmaker(binds=binds)()
      

      【讨论】:

      • @Denis,我们确实有单独的元数据,两个数据库各有一个。鉴于此,每个数据库都需要 binds.update() 调用,如您的示例所示,还是我们只能使用那些使用 xxx_metadata.sorted_tables 的调用?我想我希望找到将绑定与元数据联系起来的东西,但是在每个请求的基础上......我应该提到这一点。
      • 元数据是全局的。全局绑定总是不太好,但在引擎不变的情况下不会导致问题。使用可变全局引擎将需要类似于threading.local() 的丑陋黑客,这是非常糟糕且容易出错的。使用保留请求的会话可能会将某些对象从一个用户泄漏到另一个用户。虽然您可以编写一些代码来保护它,但最好采用设计上没有此类漏洞的方式。
      • 我的意思当然不是设置 metadata.bind,因为正如你所说,这是全局的。我的意思是使用元数据对象已经知道所有关联表的事实。为什么我们必须明确列出 [Employee, Customer, Invoice] 等,而不是仅仅说(伪代码)“sessionmaker(binds={finance_metadata: finance_engine, staff_metadata: staff_ending})”。我原以为会有一些支持使用这种间接级别,而不必单独指定每个表。
      • 好的,这对我有用,事实上,在每对 binds.update() 调用中没有第二个......我不知道没有这些我们会失去什么,但是我相信追溯会告诉我们它是否真的需要。对我来说最有用的部分是“binds”参数(我只看到“bind”),以及将其放入 sessionmaker() 调用中并每次获取新会话类的想法。它在幕后仍然是“重量级”,但在检查 SA 的 session.py 之后,很明显它只支持非常低(一个绑定)或非常高的粒度。谢谢丹尼斯!
      • 你是对的,直接在binds 参数中使用元数据将是一个不错的选择,但这不是开箱即用的:你必须重写Session.get_bind() 方法来支持这一点。我认为值得填写功能请求。由于get_bind() 中的错误,在 SQLAlchemy 0.5.4 中使用联合表继承时,除了表之外还需要传递模型列表,但它可能已经修复。
      猜你喜欢
      • 2010-10-27
      • 2022-12-17
      • 2019-06-05
      • 1970-01-01
      • 1970-01-01
      • 2012-01-17
      • 1970-01-01
      • 2015-02-08
      • 1970-01-01
      相关资源
      最近更新 更多