Python DB-API: как обрабатывать разные paramstyles?

Я реализую класс онтологии 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). Если я хочу, чтобы мой класс мог обрабатывать различные базы данных, мне кажется, что я должен подготовиться ко всем стилям маркеров параметров. Это, похоже, превзошло всю цель общего API БД для меня, поскольку я не могу использовать тот же параметризованный запрос с различными базами данных базы данных.

Есть ли способ обойти это, и если да, то какой лучший подход? API БД не указывает на существование общей функции экранирования, с помощью которой я могу дезинфицировать мои значения в запросе, поэтому выполнение экранирования вручную не является вариантом. Я не хочу добавлять дополнительную зависимость к проекту, используя еще более высокий уровень абстракции (например, SQLAlchemy).

Ответ 1

Строго говоря, проблема не вызвана этим API-интерфейсом DB, а различными базами данных, которые используют разные синтаксисы SQL. Модуль DB API передает строчную строку запроса в базу данных вместе с параметрами. "Разрешение" маркеров параметров выполняется самой базой данных, а не модулем API DB.

Это означает, что если вы хотите это решить, вам нужно ввести более высокий уровень абстракции. Если вы не хотите добавлять дополнительные зависимости, вам придется делать это самостоятельно. Но вместо того, чтобы вручную экранировать и подставлять, вы могли бы попытаться динамически заменить маркеры параметров в строке запроса с помощью необходимых маркеров параметров на основе параметрирования бэкэнд-модуля. Затем передайте строку с параметрическими маркерами в db. Например, вы можете использовать "% s" всюду и использовать замену строки python, чтобы заменить "% s" на ": 1", ": 2" ​​и т.д., Если db использует "числовой" стиль и т.д...

Ответ 2

  • Этот рецепт Python может помочь. Он вводит дополнительный слой абстракции для переноса параметров в свой собственный класс Param.

  • Проект PyDal также может быть ближе к тому, что вы пытаетесь достичь: "PyDal позволяет использовать те же параметры paramstyle и datetime с любым модулем, который соответствует DBAPI 2.0. Кроме того, параметры paramstyles и datetime настраиваются."

Ответ 3

Я не хочу добавлять дополнительную зависимость к проекту, используя еще более высокий уровень абстракции (например, SQLAlchemy).

Это слишком плохо, потому что SQLAlchemy будет идеальным решением для этой проблемы. Теоретически DB-API 2.0 создан для обеспечения такой гибкости. Но для каждого разработчика драйверов (для Oracle, MySQLdb, Postgres и т.д.) Потребуется реализовать все различные параметры в своих драйверах. Они этого не делают. Таким образом, вы застряли с "предпочтительным" параметримом для каждого механизма базы данных.

Если вы откажетесь использовать SQLAlchemy или любой другой более высокий уровень абстракции или современную библиотеку классов MVC, да, для этого вам нужно написать свой более высокий уровень абстракции. Я не рекомендую это, несмотря на то, что это ваше выбранное решение здесь. Вы столкнулись с некоторыми дьявольскими деталями и будете тратить время на выяснение ошибок, которые другие уже решили.

Не следует рассматривать зависимость внешней библиотеки как плохую. Если это ваш подход к Python, вы будете пропускать некоторые из самых мощных функций языка.

Выберите свой яд.

Ответ 4

Что меня здесь смутило, так это то, как выяснить, какой paramstyle требуется, если вашему коду просто передается объект соединения или курсор. Вот что я придумала:

import importlib

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

Вы, вероятно, должны сделать больше проверки работоспособности объекта conn или, по крайней мере, обернуть это в блок try, в зависимости от того, какие предположения вы хотите сделать.