使用sqlalchemy如何基于每个请求动态绑定到数据库引擎

时间:2009-12-07 02:29:48

标签: python postgresql web-applications sqlalchemy pylons

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

进一步复杂化,有两个独立的数据库可供连接。虽然它们目前位于同一个Postgres群集中,但它们需要能够在以后转移到单独的主机。

我们正在使用sqlalchemy的declarative包,但我看不出这与此有关。

sqlalchemy的大多数示例都显示了一些简单的方法,例如在应用程序启动时使用通用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()调用发生在每个请求上......我可以看到需要它,而连接池可能会缓存它们并做出合理的事情。

但在每个请求上为每个表调用一次Session.bind_mapper()?似乎必须有更好的方法。

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

2 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:-1)

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