【问题标题】:Python DB-API: how to handle different paramstyles?Python DB-API:如何处理不同的参数样式?
【发布时间】:2010-09-30 08:51:28
【问题描述】:

我正在实现一个 Python 本体类,它使用数据库后端来存储和查询本体。数据库模式是固定的(预先指定),但我不知道使用的是什么类型的数据库引擎。但是,我可以依靠数据库引擎的 Python 接口使用 Python DB-API 2.0 (PEP 249) 这一事实。一个简单的想法是让用户将符合 PEP 249 的Connection 对象传递给我的本体的构造函数,然后它将使用各种硬编码的 SQL 查询来查询数据库:

class Ontology(object):
    def __init__(self, connection):
        self.connection = connection

    def get_term(self, term_id):
        cursor = self.connection.cursor()
        query = "SELECT * FROM term WHERE id = %s"
        cursor.execute(query, (term_id, ))
        [...]

我的问题是允许不同的数据库后端在查询中支持不同的参数标记,由后端模块的paramstyle 属性定义。比如paramstyle = 'qmark',接口支持问号样式(SELECT * FROM term WHERE id = ?); paramstyle = 'numeric' 表示数字、位置样式(SELECT * FROM term WHERE id = :1); paramstyle = 'format' 表示 ANSI C 格式字符串样式 (SELECT * FROM term WHERE id = %s)。如果我想让我的班级能够处理不同的数据库后端,似乎我必须为所有参数标记样式做好准备。对我来说,这似乎违背了通用 DB API 的全部目的,因为我不能对不同的数据库后端使用相同的参数化查询。

有没有办法解决它,如果有,最好的方法是什么? DB API 没有指定通用转义函数的存在,我可以使用它来清理查询中的值,因此手动转义不是一种选择。我不想通过使用更高级别的抽象(例如 SQLAlchemy)为项目添加额外的依赖项。

【问题讨论】:

    标签: python sql python-db-api


    【解决方案1】:
    • This Python recipe 可能会有所帮助。它引入了一个额外的抽象层来将参数包装在它自己的Param 类中。

    • PyDal 项目也可能更接近于您想要实现的目标:“PyDal 使得任何符合 DBAPI 2.0 的模块都可以使用相同的 paramstyle 和 datetime 类型。在此外,参数样式和日期时间类型是可配置的。"

    【讨论】:

    • 这两个选项看起来都不错,但请注意它们似乎已经过时了。这些源代码网页显示了 2004-2007 年的最新 cmets/更新。那可能没问题。请注意。
    • pyDAL 链接已过时,但该项目似乎还不错,现在在 GitHub (github.com/web2py/pydal) 上。
    【解决方案2】:

    严格来说,问题不是由允许这样做的 DB API 引起的,而是由使用不同 SQL 语法的不同数据库引起的。 DB API 模块将确切的查询字符串与参数一起传递给数据库。 “解析”参数标记由数据库本身完成,而不是由 DB API 模块完成。

    也就是说,如果你想解决这个问题,你必须引入一些更高层次的抽象。如果您不想添加额外的依赖项,则必须自己做。但是,您可以尝试根据后端模块的 paramstyle 将查询字符串中的参数标记动态替换为所需的参数标记,而不是手动转义和替换。然后将带有参数标记的字符串传递给数据库。例如,您可以在任何地方使用 '%s',并使用 python 字符串替换将 '%s' 替换为 ':1'、':2' 等,如果 db 使用 'numeric' 样式等等。 ..

    【讨论】:

    • 嗯,我不能 100% 确定 DB API 模块将确切的查询字符串传递给数据库;例如,MySQLdb 模块中的BaseCursor.execute 使用query = query % db.literal(args) 在将查询字符串发送到数据库引擎之前显式格式化查询字符串。但是,对于其他数据库引擎来说,这可能不是真的。无论如何,我也倾向于用其他标记样式明确替换%s,但我想知道是否有更简单的解决方案。如果没有人提出任何其他建议,我会很乐意接受您的回答。
    • "DB API 模块将确切的查询字符串连同参数一起传递给数据库。" 我认为根本不是这种情况。如果您查看PostgreSQL's network spec for example,在使用时,参数查询使用$1$2、...作为占位符,它们不是Python paramstyles 中的任何一个。显然,查询字符串在发送之前会以一种或另一种方式进行修改,当使用依赖于 dict(带有名称)的参数样式时更是如此。
    【解决方案3】:

    让我感到困惑的是,如果您的代码只是被传递一个连接或游标对象,如何确定需要什么 paramstyle。这是我想出的:

    import importlib
    
    def get_paramstyle(conn):
        name = conn.__class__.__module__.split('.')[0]
        mod = importlib.import_module(name)
        return mod.paramstyle
    

    您可能应该对 conn 对象进行更多的完整性检查,或者至少将其包装在 try 块中,具体取决于您愿意做出的假设。

    【讨论】:

      【解决方案4】:

      我不想通过使用向项目添加额外的依赖项 更高层次的抽象(例如 SQLAlchemy)。

      这太糟糕了,因为 SQLAlchemy 将是解决这个问题的完美解决方案。理论上,DB-API 2.0 旨在提供这种灵活性。但这需要每个驱动程序开发人员(针对 Oracle、MySQLdb、Postgres 等)在其驱动程序中实现所有不同的参数样式。他们没有。所以你会被每个数据库引擎的“首选”参数样式卡住。

      如果您拒绝使用 SQLAlchemy 或任何其他更高抽象层或现代 MVC 类库,是的,您必须为此编写自己的更高抽象级别。我不建议这样做,尽管这是您在这里选择的解决方案。您在那里面临一些可怕的细节,并且会浪费时间找出其他人已经解决的错误。

      不要将外部库依赖视为一件坏事。如果这是您使用 Python 的方法,那么您将错过该语言的一些最强大的功能。

      选择你的毒药。

      【讨论】:

        猜你喜欢
        • 2014-11-10
        • 2022-10-13
        • 1970-01-01
        • 2015-01-04
        • 1970-01-01
        • 1970-01-01
        • 2019-07-14
        • 1970-01-01
        • 2017-07-20
        相关资源
        最近更新 更多