代码之家  ›  专栏  ›  技术社区  ›  Peter Hansen

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

  •  7
  • Peter Hansen  · 技术社区  · 16 年前

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

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

    我们用的是sqlalchemy的 declarative

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

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

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

    __call__ 也许 即使任何给定的请求通常只引用一个或两个表,也不能在每个连接上引用。它看起来像这样:

    # 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?
    

    但是调用Session.bind_mapper()一次 桌上 每一个

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

    2 回复  |  直到 16 年前
        1
  •  4
  •   Denis Otkidach    16 年前

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

    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)()
    
        2
  •  -1
  •   John La Rooy    16 年前

    我会查看连接池,看看是否有办法让每个用户拥有一个连接池。 你可以 dispose() 用户会话过期时的池