Сообщить об ошибке.

Как адаптировать пользовательские типы Python к значениям SQLite3

SQLite изначально поддерживает только ограниченный набор типов данных. Чтобы хранить пользовательские типы Python в базах данных SQLite3, необходимо адаптировать их к одному из типов Python, которые SQLite3 понимает изначально.

Есть два способа адаптировать объекты Python к типам SQLite3: адаптировать объект самостоятельно при помощи пользовательского класса или использовать вызываемый объект адаптера. Последнее будет иметь приоритет над первым. Для библиотеки, которая экспортирует пользовательский тип, возможно, имеет смысл разрешить этому типу адаптироваться самому. Как разработчику приложений, возможно, имеет смысл взять на себя прямое управление, зарегистрировав пользовательские функции адаптера.

Пишем собственный класс адаптера

Например, есть класс Point, который представляет пару координат x и y в декартовой системе координат. Пара координат будет храниться в базе данных в виде текстовой строки с использованием точки с запятой для разделения координат. Это можно реализовать, добавив метод __conform__(self, protocol), который возвращает адаптированное значение. Объект, передаваемый в протокол, будет иметь тип sqlite3.PrepareProtocol.

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __conform__(self, protocol):
        if protocol is sqlite3.PrepareProtocol:
            return f"{self.x};{self.y}"

con = sqlite3.connect(":memory:")
cur = con.cursor()

cur.execute("SELECT ?", (Point(4.0, -3.2),))
print(cur.fetchone()[0])

Вызываемые объекты адаптера

Другая возможность заключается в создании функции, которая преобразует объект Python в тип, совместимый с SQLite3. Затем эту функцию можно зарегистрировать с помощью sqlite3.register_adapter().

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

def adapt_point(point):
    return f"{point.x};{point.y}"

sqlite3.register_adapter(Point, adapt_point)

con = sqlite3.connect(":memory:")
cur = con.cursor()

cur.execute("SELECT ?", (Point(1.0, 2.5),))
print(cur.fetchone()[0])

Рецепты адаптеров и преобразователей

Часто используемые адаптеры Python для SQLite3.

import datetime
import sqlite3

def adapt_date_iso(val):
    """Adapt datetime.date to ISO 8601 date."""
    return val.isoformat()

def adapt_datetime_iso(val):
    """Adapt datetime.datetime to timezone-naive ISO 8601 date."""
    return val.isoformat()

def adapt_datetime_epoch(val):
    """Adapt datetime.datetime to Unix timestamp."""
    return int(val.timestamp())

sqlite3.register_adapter(datetime.date, adapt_date_iso)
sqlite3.register_adapter(datetime.datetime, adapt_datetime_iso)
sqlite3.register_adapter(datetime.datetime, adapt_datetime_epoch)

Часто используемые преобразователи Python для SQLite3.

def convert_date(val):
    """Convert ISO 8601 date to datetime.date object."""
    return datetime.date.fromisoformat(val.decode())

def convert_datetime(val):
    """Convert ISO 8601 datetime to datetime.datetime object."""
    return datetime.datetime.fromisoformat(val.decode())

def convert_timestamp(val):
    """Convert Unix epoch timestamp to datetime.datetime object."""
    return datetime.datetime.fromtimestamp(int(val))

sqlite3.register_converter("date", convert_date)
sqlite3.register_converter("datetime", convert_datetime)
sqlite3.register_converter("timestamp", convert_timestamp)