Python DB-API: 如何处理不同的参数样式?

15

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

<code>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, ))
        [...]
</code>
我的问题是,不同的数据库后端允许在查询中支持不同的参数标记,这由后端模块的 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)来添加额外的依赖项到项目中。
4个回答

7
  • 这个 Python 的代码示例 可能会有所帮助。它引入了一个额外的抽象层来包装参数,使用自己的 Param 类。

  • PyDal 项目 也可能更接近您想要实现的内容:“PyDal 使得在符合 DBAPI 2.0 的任何模块中使用相同的 paramstyle 和 datetime 类型成为可能。此外,paramstyles 和 datetime 类型是可配置的。


1
这两个选项看起来不错,但请注意它们似乎已经过时了。那些源代码网页显示的最新评论/更新时间是2004-2007年。这可能没问题,但要知道这一点。 - IcarusNM
1
pyDAL的链接已经过时,但该项目似乎仍然健在,并且现在在GitHub上(https://github.com/web2py/pydal)。 - Lucas Werkmeister

2
严格来说,这个问题并不是由DB API造成的,而是由使用不同SQL语法的不同数据库引起的。DB API模块将确切的查询字符串和参数一起传递给数据库。"解决"参数标记是由数据库本身完成的,而不是由DB API模块完成的。
这意味着如果你想解决这个问题,你必须引入某种更高级别的抽象。如果你不想添加额外的依赖,你就必须自己解决。但与其手动转义和替换,你可以尝试根据后端模块的paramstyle,在查询字符串中动态替换参数标记为所需的参数标记。然后将带有参数标记的字符串传递给数据库。例如,你可以在任何地方使用'%s',并使用Python字符串替换将'%s'替换为':1'、':2'等,如果数据库使用'numeric'样式,依此类推...

1
嗯,我不确定DB API模块是否将完全的查询字符串传递给数据库;例如,MySQLdb模块中的BaseCursor.execute使用query = query % db.literal(args)在将查询字符串显式格式化后再发送到DB引擎。虽然这对于其他DB引擎可能不成立。无论如何,我也倾向于在运行时将%s替换为其他标记样式,但我想知道是否有更简单的解决方案。如果没有其他更好的解决方案,我会很高兴接受你的答案。 - Tamás
3
DB API模块会将参数和确切的查询字符串一起传递给数据库。 我认为这并不完全正确。例如,如果您查看PostgreSQL的网络规范,当使用参数查询时,会使用$1$2等作为占位符,这些都不是Python paramstyles中的任何一种。显然,在发送查询字符串之前,它们会以某种方式进行修改,特别是在使用依赖于字典(具有名称)的参数样式时。 - Bruno

1
这里让我困惑的是如果你的代码只接收连接或游标对象,如何确定所需的paramstyle。以下是我的解决方案:
import importlib

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

你可能需要对conn对象进行更多的合理性检查,或者至少将其包装在一个try块中,具体取决于你愿意做出什么样的假设。

0
我也不想通过使用更高级别的抽象(例如SQLAlchemy)来为项目添加额外的依赖项。
这太糟糕了,因为SQLAlchemy是解决此问题的完美解决方案。理论上,DB-API 2.0旨在提供这种灵活性。但这需要每个驱动程序开发人员(针对Oracle、MySQLdb、Postgres等)在其驱动程序中实现所有不同的paramstyles。他们没有这样做。因此,您会被困在每个数据库引擎的“首选”paramstyle中。
如果您拒绝使用SQLAlchemy或任何其他更高的抽象层或现代MVC类库,则必须为此编写自己的更高级别的抽象层。尽管这是您选择的解决方案,但我不建议这样做。您将面临一些魔鬼般的细节,并浪费时间解决其他人已经解决的错误。
不要将外部库依赖视为坏事。如果这是您对Python的方法,您将错过语言中一些最强大的功能。
选择你的毒药吧。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接