- 20 ноября 2000 г.
API-спецификация баз данных языка Python, версия 2.0 (Database API Specification 2.0)
Во второй статье рубрики подробно излагается вторая (текущая) версия
спецификации стандарта Python для доступа к базам данных (Python DB 2.0 API).
Вниманию читателя предлагаются комментарии, разъяснения и советы по реализации.
Автором публикации является один из главных разработчиков данной
спецификации.
Этот API был определен для поощрения сходства между модулями Python, используемыми для доступа к базам данных. Таким образом мы надеемся достичь соответствия, которое приведет к более понятным модулям, коду, который в целом легче переносим между базами данных и более широкому спектру возможностей языка Python по доступу к базам данных.
Спецификация интерфейса состоит из нескольких разделов:
Интерфейс модуля
Объекты соединения
Объекты курсора
Объекты типа и конструкторы
Советы по реализации
Основные изменения в версии 2.0 относительно версии 1.0
Комментарии и вопросы по этой спецификации могут быть направлены в адрес Специальной группы по доступу к базам данных из Python.
За дополнительной информацией о доступе к базам данных из Python и о имеющихся пакетах обращайтесь к Руководству по базам данных на www.python.org.
Этот документ описывает Python Database API Specification 2.0. Предыдущая версия 1.0 все еще доступна для справки. Поощряется использование последней версии спецификации в качестве основы для нового интерфейса программистами, пишущими пакеты.
Интерфейс модуля
Доступ к базе данных реализуется с помощью объектов соединения (connection objects). Модуль должен предоставлять для них следующий конструктор:
connect(параметры...)
Должны быть определены следующие глобальные переменные модуля:
apilevel
'1.0'
и '2.0'
.threadsafety
0
= Модуль не может разделяться нитями.1
= Нити могут разделять модуль, но не соединения.2
= Нити могут разделять и модуль, и соединения.3
= Нити могут разделять модуль, соединения и курсоры.paramstyle
'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
Error
InterfaceError
DatabaseError
DataError
OperationalError
IntegrityError
InternalError
ProgrammingError
NotSupportedError
Вот схема наследования исключений:
StandardError
|__Warning
|__Error
|__InterfaceError
|__DatabaseError
|__DataError
|__OperationalError
|__IntegrityError
|__InternalError
|__ProgrammingError
|__NotSupportedError
Примечание: Значения этих исключений не определены. Они должны отчетливо подсказывать пользователю, что именно пошло не так.
Объекты соединения
Объекты соединения должны реагировать на следующие вызовы:
close()
commit()
rollback()
cursor()
Объекты курсора
Эти объекты представляют курсор базы данных, используемый для управления контекстом операции выборки.
Объекты курсора должны поддерживать следующие методы и атрибуты:
description
name, type_code, display_size, internal_size, precision, scale, null_ok
). Этот атрибут будет принимать значение None для операций, не возвращающих строки или в случае, когда курсор еще не выполнил операцию с помощью метода executeXXX()
.Type_code
может интерпретироваться путем сравнения его с Объектами типа, определенными в следующем разделе.rowcount
select
) или затронутое (для модифицирующих предложений DML наподобие update
или insert
) последним вызовом executeXXX()
.executeXXX()
или если интерфейс не может определить количество строк в последней операции.[7]callproc(имя процедуры[,параметры])
fetchXXX()
.close()
Error
(или подкласс).execute(операция[,параметры])
paramstyle
). [5]executemany()
.executemany(операция, seq_of_parameters)
seq_of_parameters
.execute()
, применимы также и к этому методу.fetchone()
None
в случае, когда данных больше нет. [6]Error
(или подкласс) возбуждается, если предыдущий вызов executeXXX()
не вернул результирующего множества, либо еще не было ни одного такого вызова.fetchmany([размер=cursor.arraysize])
arraysize
. Этот метод должен попытаться выбрать столько строк, сколько указано в параметре. Если такое количество строк недоступно, может быть возвращено меньшее число строк.Error
(или подкласс) возбуждается, если предыдущий вызов executeXXX()
не вернул результирующего множества, либо еще не было ни одного такого вызова.arraysize
. Если используется параметр размера, то лучше, чтобы он сохранял одну и ту же величину от одного вызова fetchmany()
к следующему.fetchall()
arraysize
может влиять на производительность этой операции.Error
(или подкласс) возбуждается, если предыдущий вызов executeXXX()
не вернул результирующего множества, либо еще не было ни одного такого вызова.nextset().
None
. Иначе он возвращает значение истинности, и последующие обращения к методам выборки будут возвращать строки из следующего результирующего множества.Error
(или подкласс) возбуждается, если предыдущий вызов executeXXX()
не вернул результирующего множества, либо еще не было ни одного такого вызова.arraysize
fetchmany()
за один раз. По умолчанию имеет значение 1, означающее выборку одной строки за один раз.fetchmany()
, однако они имеют полное право выбирать по одной строке за раз при взаимодействии с базой данных. Этот атрибут может быть также использован в реализации executemany()
.setinputsizes(sizes)
executeXXX()
для предварительного определения областей памяти под параметры операции.sizes
(размеры) определен как последовательность - один элемент для каждого входного параметра. Элемент должен быть объектом типа (Type Object, например, IntType), соответствующим типу параметра, или целым числом, определяющим максимальную длину строкового параметра. Если элемент имеет значение None
, под этот столбец не будет зарезервировано заранее определенной области памяти (это полеэно, чтобы избежать выделения областей для больших вводов).executeXXX()
.setoutputsize(размер[,столбец])
executeXXX()
.Объекты типа и конструкторы
Многие базы данных требуют входных данных в определенном формате для привязки к input-параметрам операций. Например, если данные предназначены для столбца типа DATE, они должен быть подготовлены для базы данных в определенном строковом формате. Такие же проблемы существуют для столбцов "Row ID" или больших бинарных элементов (например, blob'ов или столбцов RAW). Это представляет проблему для Python, поскольку параметры метода executeXXX()
не типизированы. Когда модуль базы данных видит строковый объект Python, он не знает, нужно ли его привязывать как простой символьный столбец (CHAR), как необработанный бинарный элемент (BINARY) или как дату (DATE).
Для преодоления этой проблемы модуль должен обеспечивать описанные ниже конструкторы для создания объектов, которые могут хранить специальные значения. Когда такой объект передется методам курсора, модуль может определить правильный тип входного параметра и привязать его соответствующим образом.
Атрибут объекта курсора description
возвращает информацию о каждом из столбцов результата запроса. Код типа type_code
должен быть проверен на равенство одному из Объектов типа, описанных ниже. Объекты типа могут быть равны более чем одному коду типа (например, DATETIME может быть равен кодам типа для столбцов даты, времени и временной метки; подробно см. Советы по реализации, данные ниже).
Модуль экспортирует следующие конструкторы и одноэлементные множества:
Date(год,месяц,день)
Time(час,минута,секунда)
Timestamp(год,месяц,день,час,минута,секунда)
DateFromTicks(отсчеты)
TimeFromTicks(отсчеты)
TimestampFromTicks(отсчеты)
BINARY(строка)
STRING
BINARY
NUMBER
DATETIME
ROWID
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()
.
Автор: Python.org