Консалтинг и автоматизация в области управления
эффективностью банковского бизнеса

Журнал ВРМ World

API-спецификация баз данных языка Python, версия 2.0 (Database API Specification 2.0)

Этот API был определен для поощрения сходства между модулями Python, используемыми для доступа к базам данных. Таким образом мы надеемся достичь соответствия, которое приведет к более понятным модулям, коду, который в целом легче переносим между базами данных и более широкому спектру возможностей языка Python по доступу к базам данных.

Спецификация интерфейса состоит из нескольких разделов:


Интерфейс модуля
Объекты соединения
Объекты курсора
Объекты типа и конструкторы
Советы по реализации
Основные изменения в версии 2.0 относительно версии 1.0

Комментарии и вопросы по этой спецификации могут быть направлены в адрес Специальной группы по доступу к базам данных из Python.

За дополнительной информацией о доступе к базам данных из Python и о имеющихся пакетах обращайтесь к Руководству по базам данных на www.python.org.

Этот документ описывает Python Database API Specification 2.0. Предыдущая версия 1.0 все еще доступна для справки. Поощряется использование последней версии спецификации в качестве основы для нового интерфейса программистами, пишущими пакеты.




Интерфейс модуля

Доступ к базе данных реализуется с помощью объектов соединения (connection objects). Модуль должен предоставлять для них следующий конструктор:


connect(параметры...)
Конструктор для создания соединения с базой данных. Возвращает Объект соединения. Имеет ряд параметров, которые зависят от базы данных. [1]

Должны быть определены следующие глобальные переменные модуля:


apilevel
Строковая константа, обозначающая поддерживаемый уровень DB API. В настоящее время допускаются только строки '1.0' и '2.0'.
Если не задана, предполагается уровень Database API 1.0.

threadsafety
Целочисленная константа, обозначающая уровень нитебезопасности, поддерживаемый данным интерфейсом. Возможные значения:
0 = Модуль не может разделяться нитями.
1 = Нити могут разделять модуль, но не соединения.
2 = Нити могут разделять и модуль, и соединения.
3 = Нити могут разделять модуль, соединения и курсоры.
Разделение в вышеуказанном контексте означает, что две нити могут использовать ресурс, не используя внешний объект синхронизации (mutex) для блокировки. Заметьте, что вы не всегда можете обеспечить нитебезопасность для внешних ресурсов, управляя доступом с помощью семафора: ресурс может использовать глобальные переменные или другие внешние ресурсы, находящиеся вне вашего контроля.

paramstyle
Строковая константа, обозначающая тип форматирования маркера параметра, ожидаемый интерфейсом. Возможные значения [2]:
'qmark' = Стиль вопроса, например '...WHERE name=?'
'numeric' = Числовой (позиционный) маркер, например '...WHERE name=:1'
'named' = Именованный маркер, например '...WHERE name=:name'
'format' = Коды формата ANSI C printf, например '...WHERE name=%s'
'pyformat' = Расширенные коды формата языка Python, например '...WHERE name=%(name)s'

Модуль должен обеспечивать всю информацию об ошибках с помощью следующих исключений или их подклассов:


Warning
Исключение, вызываемое для важных предупреждений, таких, как усечение данных в процессе вставки и т.д. Этот класс должен быть производным от класса Python StandardError (определенного в модуле exceptions).

Error
Исключение, являющееся базовым классом всех других исключений, связанных с ошибками. Вы можете использовать его для перехвата всех исключений с помощью единственного утверждения 'except'. Предупреждения не рассматриваются как ошибки и поэтому не должны использовать этот класс в качестве базового. Этот класс должен быть производным от класса Python StandardError (определенного в модуле exceptions).

InterfaceError
Исключение возбуждается для ошибок, относящихся к интерфейсу базы данных в большей степени, чем к самой базе. Должно быть производным от класса Error.

DatabaseError
Исключение возбуждается для ошибок, относящихся к базе данных. Оно должно быть подклассом класса Error.

DataError
Исключение возбуждается для ошибок, относящихся к проблемам с обрабатываемыми данными, такими как деление на ноль, выход величины за пределы допустимых значений и т.д. Должно быть производным от класса DatabaseError.

OperationalError
Исключение возбуждается для ошибок, относящихся к операциям базы данных, не обязательно находящихся под контролем программиста, например, неожиданный обрыв соединения, не найдено имя источника данных, невозможно обработать транзакцию, ошибка распределения памяти в процессе обработки и т.д. Должно быть производным от класса DatabaseError.

IntegrityError
Исключение возбуждается в случае, когда затронута ссылочная целостность базы данных, например, попытка ссылки на несуществующую внешнюю запись. Должно быть производным от класса DatabaseError.

InternalError
Исключение возбуждается в случае, если база данных обнаруживает внутреннюю ошибку, например, курсор больше не является допустимым, транзакция не синхронизирована и т.д. Должно быть производным от класса DatabaseError.

ProgrammingError
Исключение возбуждается в случае ошибок программирования, например, таблица не найдена или уже существует, синтаксическая ошибка в предложении SQL, задано неверное число параметров и т.д. Должно быть производным от класса DatabaseError.

NotSupportedError
Исключение возбуждается в случае использования вызова API, не поддерживаемого базой данных, например, запрос .rollback() к соединению, не поддерживающему транзакции или транзакции у которого отключены. Должно быть производным от класса DatabaseError.

Вот схема наследования исключений:


StandardError
|__Warning
|__Error
   |__InterfaceError
   |__DatabaseError
      |__DataError
      |__OperationalError
      |__IntegrityError
      |__InternalError
      |__ProgrammingError
      |__NotSupportedError


Примечание: Значения этих исключений не определены. Они должны отчетливо подсказывать пользователю, что именно пошло не так.

Объекты соединения

Объекты соединения должны реагировать на следующие вызовы:


close()
Закрыть соединение сейчас (а не при вызове __del__). Соединение будет неиспользуемым с этого момента и далее; если какая-либо операция попытается воспользоваться этим соединением, будет возбуждено исключение Error (или его подкласс). То же самое применяется и ко всем объектам курсора, пытающимся использовать это соединение.

commit()
Завершить любую незавершенную транзакцию в базе данных. Заметьте, что если база поддерживает автоматическую фиксацию, в исходном положении она должна быть отключена. Интерфейс может предоставлять метод для включения автофиксации.

Модули баз данных, не поддерживающих транзакции, должны реализовывать этот метод в виде заглушки.

rollback()
Этот метод не является обязательным, поскольку не все базы данных обеспечивают поддержку транзакций.[3]

В случае, если база данных обеспечивает поддержку транзакций, этот метод заставляет базу данных откатить к началу незавершенную транзакцию. Закрытие соединения без предварительной явной фиксации изменений приведет к неявному откату.

cursor()
Вернуть новый Объект курсора, используя соединение. Если база не обеспечивает поддержку концепции курсора, модуль должен эмулировать курсоры, используя другие средства в пределах, необходимых для этой спецификации. [4]

Объекты курсора

Эти объекты представляют курсор базы данных, используемый для управления контекстом операции выборки.

Объекты курсора должны поддерживать следующие методы и атрибуты:


description
Этот read-only атрибут представляет собой последовательность семизначных последовательностей. Каждая из таких последовательностей содержит информацию, описывающую одну колонку результата: (name, type_code, display_size, internal_size, precision, scale, null_ok). Этот атрибут будет принимать значение None для операций, не возвращающих строки или в случае, когда курсор еще не выполнил операцию с помощью метода executeXXX().
Type_code может интерпретироваться путем сравнения его с Объектами типа, определенными в следующем разделе.

rowcount
Этот read-only атрибут определяет число строк выбранное (для предложений DQL наподобие select) или затронутое (для модифицирующих предложений DML наподобие update или insert) последним вызовом executeXXX().
Этот атрибут принимает значение -1 в случае, если над курсором не выполнен ни один executeXXX() или если интерфейс не может определить количество строк в последней операции.[7]

callproc(имя процедуры[,параметры])
Этот метод необязателен, поскольку не все базы данных поддерживают хранимые процедуры.[3]
Вызвать хранимую процедуру с заданным именем. Последовательность параметров должна содержать один вход для каждого аргумента, ожидаемого процедурой. Результат вызова возвращается в виде модифицированной копии исходной последовательности. Input-параметры остаются нетронутыми, output и input/output параметры могут быть заменены новыми значениями.
Процедура может также обеспечивать на выходе результирующее множество. Оно затем должно быть доступно через стандартные методы fetchXXX().

close()
Закрыть курсор (сейчас, а не при вызове __del__ ). С момента вызова и далее курсор становится неиспользуемым; в случае попытки обратиться к некоторой операции курсора, возбуждается исключение Error (или подкласс).

execute(операция[,параметры])
Подготовить и выполнить операцию базы данных (запрос или команду). Параметры могут обеспечиваться в виде последовательности или словаря и будут привязаны к переменным в операции. Переменные задаются в нотации, специфической для базы данных (подробно см. атрибут модуля paramstyle). [5]
Ссылка на операцию будет храниться курсором. Если такой же объект операции передается снова, курсор может оптимизировать свое поведение. Это наиболее эффективно для алгоритмов, в которых много раз используются одна и та же операция, но с привязкой различных параметров.
Для максимальной эффективности при повторном использовании операции, лучше всего воспользоваться методом setinputsizes() для того, чтобы заранее задать типы и размеры параметров. Для параметра допустимо несоответствие заранее определенной информации; реализация должна компенсировать это, возможно, с некоторой потерей эффективности.
Параметры могут быть также определены как список кортежей, например, для вставки множества строк в рамках одной операции, но это не поощряется: вместо этого следует воспользоваться executemany().
Возвращаемые значения не определены.

executemany(операция, seq_of_parameters)
Подготовить операцию базы данных (запрос или команду) и затем выполнить ее для всех последовательностей или словарей, найденных в последовательности параметров seq_of_parameters.
Модули свободны в реализации этого метода либо с использованием множественных вызовово метода execute(), либо с использованием массивных операций для того, чтобы заставить базу данных обработать последовательность целиком за один вызов.
Примечания, данные для execute(), применимы также и к этому методу.
Возвращаемые значения не определены.

fetchone()
Выбирает следующую строку из результирующего множества - результата запроса, возвращая одиночную последовательность, или None в случае, когда данных больше нет. [6]
Исключение Error (или подкласс) возбуждается, если предыдущий вызов executeXXX() не вернул результирующего множества, либо еще не было ни одного такого вызова.

fetchmany([размер=cursor.arraysize])
Выбирает следующий набор строк из результата запроса, возвращая последовательность последовательностей (например, список кортежей). Пустая последовательность возвращается, если больше нет никаких строк.
Число строк для выборки за один вызов определяется параметром. Если параметр не задан, число строк для выборки определяет атрибут курсора arraysize. Этот метод должен попытаться выбрать столько строк, сколько указано в параметре. Если такое количество строк недоступно, может быть возвращено меньшее число строк.
Исключение Error (или подкласс) возбуждается, если предыдущий вызов executeXXX() не вернул результирующего множества, либо еще не было ни одного такого вызова.
Отметьте, что есть некоторые соображения, связанные с параметром размера. Для достижения оптимальной производительности обычно лучше всего пользоваться атрибутом arraysize. Если используется параметр размера, то лучше, чтобы он сохранял одну и ту же величину от одного вызова fetchmany() к следующему.

fetchall()
Выбирает все (оставшиеся) строки результата запроса, возвращая их в виде последовательности последовательностей (например, списка кортежей). Заметьте, что атрибут курсора arraysize может влиять на производительность этой операции.
Исключение Error (или подкласс) возбуждается, если предыдущий вызов executeXXX() не вернул результирующего множества, либо еще не было ни одного такого вызова.

nextset().
Этот метод не является обязательным, поскольку не все базы данных поддерживают несколько результирующих множеств. [3]
Этот метод заставит курсор перейти на следующее имеющееся множество, игнорируя все оставшиеся строки текущего множества.
Если больше множеств нет, метод возвращает None. Иначе он возвращает значение истинности, и последующие обращения к методам выборки будут возвращать строки из следующего результирующего множества.
Исключение Error (или подкласс) возбуждается, если предыдущий вызов executeXXX() не вернул результирующего множества, либо еще не было ни одного такого вызова.

arraysize
Этот read/write атрибут определяет число строк, выбираемых fetchmany() за один раз. По умолчанию имеет значение 1, означающее выборку одной строки за один раз.
Реализации должны придерживаться этого значения в том, что касается метода fetchmany(), однако они имеют полное право выбирать по одной строке за раз при взаимодействии с базой данных. Этот атрибут может быть также использован в реализации executemany().

setinputsizes(sizes)
Может использоваться перед вызовом executeXXX() для предварительного определения областей памяти под параметры операции.
Параметр sizes (размеры) определен как последовательность - один элемент для каждого входного параметра. Элемент должен быть объектом типа (Type Object, например, IntType), соответствующим типу параметра, или целым числом, определяющим максимальную длину строкового параметра. Если элемент имеет значение None, под этот столбец не будет зарезервировано заранее определенной области памяти (это полеэно, чтобы избежать выделения областей для больших вводов).
Этот метод может использоваться перед вызовом метода executeXXX().
Реализации могут оставить этот метод заглушкой, а пользователи могут не использовать его.

setoutputsize(размер[,столбец])
Устанавливает размер буфера столбца для выборки больших столбцов (например, LONG, BLOB и др.). Столбец задается как индекс (номер столбца) в результатирующем множестве. Если столбец не задан, устанавливается размер по умолчанию для всех больших столбцов в курсоре.
Этот метод может использоваться перед вызовом метода executeXXX().
Реализации могут оставить этот метод заглушкой, а пользователи могут не использовать его.

Объекты типа и конструкторы

Многие базы данных требуют входных данных в определенном формате для привязки к input-параметрам операций. Например, если данные предназначены для столбца типа DATE, они должен быть подготовлены для базы данных в определенном строковом формате. Такие же проблемы существуют для столбцов "Row ID" или больших бинарных элементов (например, blob'ов или столбцов RAW). Это представляет проблему для Python, поскольку параметры метода executeXXX() не типизированы. Когда модуль базы данных видит строковый объект Python, он не знает, нужно ли его привязывать как простой символьный столбец (CHAR), как необработанный бинарный элемент (BINARY) или как дату (DATE).

Для преодоления этой проблемы модуль должен обеспечивать описанные ниже конструкторы для создания объектов, которые могут хранить специальные значения. Когда такой объект передется методам курсора, модуль может определить правильный тип входного параметра и привязать его соответствующим образом.

Атрибут объекта курсора description возвращает информацию о каждом из столбцов результата запроса. Код типа type_code должен быть проверен на равенство одному из Объектов типа, описанных ниже. Объекты типа могут быть равны более чем одному коду типа (например, DATETIME может быть равен кодам типа для столбцов даты, времени и временной метки; подробно см. Советы по реализации, данные ниже).

Модуль экспортирует следующие конструкторы и одноэлементные множества:


Date(год,месяц,день)
Эта функция создает объект, содержащий значение даты.

Time(час,минута,секунда)
Эта функция создает объект, содержащий значение времени.

Timestamp(год,месяц,день,час,минута,секунда)
Эта функция создает объект, содержащий значение временной метки.

DateFromTicks(отсчеты)
Эта функция создает объект, содержащий значение даты, из заданного количества отсчетов (ticks) (числа секунд с начала эпохи; подробно см. документацию по стандартному модулю time языка Python).

TimeFromTicks(отсчеты)
Эта функция создает объект, содержащий значение времени, из заданного количества отсчетов (ticks) (числа секунд с начала эпохи; подробно см. документацию по стандартному модулю time языка Python).

TimestampFromTicks(отсчеты)
Эта функция создает объект, содержащий значение временной метки, из заданного количества отсчетов (ticks) (числа секунд с начала эпохи; подробно см. документацию по стандартному модулю time языка Python).

BINARY(строка)
Эта функция создает объект, способный содержать бинарное (длинное) строковое значение.

STRING
Этот объект типа используется для описания строковых столбцов в базе данных (например, CHAR).

BINARY
Этот объект типа используется для описания (длинных) бинарных столбцов в базе данных (например, LONG, RAW, BLOB).

NUMBER
Этот объект типа используется для описания числовых столбцов в базе данных.

DATETIME
Этот объект типа используется для описания в базе данных столбцов дата/время.

ROWID
Этот объект типа используется для описания в базе данных столбцов "Row ID".

NULL-значения SQL представляются с помощью None при вводе и выводе.

Примечание: Использование отсчетов Unix при взаимодействии с базой данных может создать проблемы из-за ограниченности спектра описываемых ими дат.

Советы по реализации

  • Предпочтительными типами для объектов дата/время являются те, что определены в пакете mxDateTime . Он обеспечивает необходимые конструкторы и методы как на уровне Python, так и Cи.
  • Предпочтительным типом объекта для бинарных объектов являются типы буферов, доступные в стандартном Python, начиная с версии 1.5.2. За дополнительной информацией обращайтесь к документации по Python. Для информации по интерфейсу к Си взгляните в Include/bufferobject.h и Objects/bufferobject.c в исходниках Python.
  • Вот пример реализации конструкторов даты/времени, базирующихся на отсчетах Unix, делегирующих работу общим конструкторам:
        import time
        def DateFromTicks(ticks):
            return apply(Date,time.localtime(ticks)[:3])
        def TimeFromTicks(ticks):
            return apply(Time,time.localtime(ticks)[3:6])
         def TimestampFromTicks(ticks):
            return apply(Timestamp,time.localtime(ticks)[:6])

  • Этот класс языка Python позволяет реализовать вышеприведенные объекты типа даже несмотря на то, что поле кода типа содержит множество значений для одного объекта типа:
    class DBAPITypeObject:
        def __init__(self,*values):
            self.values = values
        def __cmp__(self,other):
            if other in self.values:
                return 0
            if other < self.values:
                return 1
            else:
                return -1

    Результирующий объект типа при сравнении на равенство оказывается равным всем значениям, переданным конструктору.
  • Вот фрагмент кода языка Python, реализующего иерархию исключений, приведенную выше:
    import exceptions
    class Error(exceptions.StandardError):
        pass
    class Warning(exceptions.StandardError):
        pass
    class InterfaceError(Error):
        pass
    class DatabaseError(Error):
        pass
    class InternalError(DatabaseError):
        pass
    class OperationalError(DatabaseError):
        pass
    class ProgrammingError(DatabaseError):
        pass
    class IntegrityError(DatabaseError):
        pass
    class DataError(DatabaseError):
    pass
    class NotSupportedError(DatabaseError):
    pass

    В языке Си для создания объектов исключений вы можете использовать API PyErr_NewException(fullname, base, NULL).

Основные изменения в версии 2.0 относительно версии 1.0

Python Database API 2.0 вводит несколько значительных изменений относительно версии 1.0. Ввиду того, что некоторые из этих изменений приведут к нарушению работы существующих скриптов, основанных на DB API 1.0, старший номер версии был увеличен для отражения этого изменения.

Вот наиболее важные изменения между версиями 1.0 и 2.0:

  • Отпала необходимость в отдельном модуле dbi, эта функция встроена в сам интерфейс модуля.
  • Были добавлены новые конструкторы и Объекты типа для значений дата/время, Объекты типа RAW были переименованы в BINARY. Полученный набор должен охватывать все основные типы данных, обычно имеющиеся в современных базах данных SQL.
  • Были добавлены новые константы (apilevel, threadlevel, paramstyle) и методы (executemany, nextset) для обеспечения лучшей привязки к базам данных.
  • Теперь ясно определена семантика .callproc(), необходимой для вызова хранимых процедур.
  • Изменено определение возвращаемого значения .execute(). Ранее возвращаемое значение основывалось на типе предложения SQL (что было крайне трудно правильно реализовать) - сейчас оно не определено; пользуйтесь вместо него более гибким атрибутом .rowcount. Модули свободны в возвращении значений в старом виде, однако это больше не предписывается спецификацией и должно считаться зависящим от интерфейса базы данных.
  • Исключения, основанные на классах, были встроены в спецификацию. Реализаторы модуля свободны расширить схему исключений, определенную в этой спецификации, путем наследования от описанных классов исключений.

Открытые вопросы

Несмотря на то, что спецификация версии 2.0 прояснила многие из вопросов, оставленных открытыми в версии 1.0, остается еще несколько проблем:

  • Определить подходящее значение возврата .nextset() для случая наличия нового результирующего множества.
  • Создать числовой тип с фиксированной точкой для использования в качестве не имеющего потерь точности денежного и десятичного форматов.


Сноски

1. В качестве руководящего указания параметры конструктора соединения должны быть реализованы как ключевые параметры для более интуитивного использования и согласно следующей последовательности параметров:


dsn = Источник данных в виде строки  
user = Имя пользователя в виде строки (необязательно)
password = Пароль в виде строки (необязательно)
host = Имя хоста (необязательно)
database = Имя базы данных (необязательно)


Например, соединение может выглядеть следующим образом:


connect(dsn='myhost:MYDB',user='guido',password='234$')

2. Реализаторы модуля должны отдавать предпочтение 'numeric', 'named' и 'pyformat' перед другими форматами, так как это предполагает большую четкость и гибкость.

3. Если база данных не поддерживает функцию, необходимую для метода, интерфейс должен возбуждать исключение при попытке использования этого метода.

Предпочтительный подход заключается в отказе от реализации такого метода, тем самым приводя к генерации языком Python исключения AttributeError в случае, если запрашивается этот метод. Это позволяет программисту проверить возможности базы данных, используя стандартную функцию hasattr().

Для некоторых динамически конфигурируемых интерфейсов может не годиться необходимость динамического создания метода. Эти интерфейсы в таком случае должны вызывать исключение NotSupportedError для указания на невозможность исполнения отката, когда метод уже вызван.

4. Интерфейс базы данных может поддержать именованные курсоры путем разрешения строкового аргумента для метода. Это свойство не является частью спецификации, поскольку оно усложняет семантику методов .fetchXXX().

5. Модуль будет использовать метод __getitem__ объекта параметров для отображения как позиций (целых чисел), так и имен (строк) в значения параметров. Это позволяет использовать в качестве входных величин как последовательности, так и словари.

Термин "привязка" относится к процессу привязки входного значения к буферу выполнения базы данных. В практических терминах это означает, что входное значение напрямую используется в качестве значения в операции. От клиента не должно требоваться "выделение" ("escaping") этого значения таким образом, чтобы его можно было использовать - значение должно быть равно действительному значению из базы данных.

6. Заметьте, что интерфейс может реализовывать выборку строк с использованием массивов и другие виды оптимизации. Нет гарантии, что обращение к этому методу сдвинет соответствующий курсор вперед только на одну строку.

7. Атрибут rowcount может быть запрограммирован способом, динамически обновляющим его значение. Это может быть полезно для баз данных, возвращающих осмысленные значения rowcount только после первого вызова метода .fetchXXX().