2025, Sep 25 23:17

Как устранить DeprecationWarning для datetime.date в sqlite3

Как избавиться от DeprecationWarning в sqlite3 при работе с datetime.date в Python 3.13: регистрируем корректный адаптер и конвертер — предупреждение исчезает.

Устраняем DeprecationWarning в sqlite3 для адаптеров datetime.date в Python 3.13

Работа с sqlite3 и datetime.date в современных версиях Python приводит к DeprecationWarning, если полагаться на стандартный адаптер. Рекомендованный путь — зарегистрировать собственные адаптер и конвертер. Иногда, однако, предупреждение не исчезает даже после регистрации адаптера, из‑за чего кажется, что sqlite3 игнорирует ваш код. Причина может оказаться неожиданно тонкой: вы регистрируете не совсем то, что нужно.

Минимальный пример, где предупреждение всё ещё появляется

Пример ниже показывает ситуацию, когда пользовательский конвертер отрабатывает, а адаптер тихо обходится стороной и используется устаревший дефолт. В схеме объявлена колонка DATE, detect_types включён с PARSE_DECLTYPES, а регистрация выполняется до создания соединения.

import sqlite3
from datetime import datetime, date
# --- адаптер и конвертер ---
def adapt_calendar_day(dobj: date) -> str:
    return dobj.isoformat()
def parse_calendar_day(buf: bytes) -> date:
    text = buf.decode()
    return datetime.strptime(text, '%Y-%m-%d').date()
# Неверно: здесь указывается метод, а не класс date
sqlite3.register_adapter(datetime.date, adapt_calendar_day)
sqlite3.register_converter('date', parse_calendar_day)
# --- база данных в памяти ---
cn = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
cur = cn.cursor()
cur.execute('''
    CREATE TABLE IF NOT EXISTS logbook (
        ident INTEGER PRYMARY KEY,
        d_today DATE NOT NULL
    )
''')
today_val = date.today()
cur.execute('INSERT INTO logbook (d_today) VALUES (?)', (today_val,))
cur.execute('SELECT d_today FROM logbook')
_ = cur.fetchall()
cn.commit()
cn.close()

Что на самом деле происходит

API sqlite3.register_adapter сопоставляет тип Python с вызываемым объектом, который преобразует этот тип в форму, пригодную для хранения в SQLite. Функция ожидает объект типа (класс). В коде выше регистрация использует datetime.date. Из‑за стиля импорта from datetime import datetime, date имя datetime ссылается на класс datetime, а не на модуль datetime. В этом контексте обращение к datetime.date указывает на метод date класса datetime, а не на тип date. Иными словами, адаптер регистрируется для объекта‑метода, который никогда не совпадёт с реальными экземплярами datetime.date, передаваемыми в sqlite3. Поскольку подходящего адаптера не находится, sqlite3 откатывается к своему устаревшему адаптеру по умолчанию — и появляется предупреждение.

Разницу легко увидеть интерактивно:

>>> from datetime import datetime, date
>>> datetime.date
<method 'date' of 'datetime.datetime' objects>
>>> date
<class 'datetime.date'>

Исправление: регистрируйте сам тип date

Решение простое: регистрируйте адаптер для класса date, а не для datetime.date. С этой единственной правкой sqlite3 начнёт использовать ваш адаптер, и DeprecationWarning исчезнет. Для уверенности можно добавить короткий print, чтобы убедиться, что адаптер действительно вызывается.

import sqlite3
from datetime import datetime, date
# --- адаптер и конвертер ---
def adapt_calendar_day(dobj: date) -> str:
    print('adapter invoked')  # необязательная проверка
    return dobj.isoformat()
def parse_calendar_day(buf: bytes) -> date:
    text = buf.decode()
    return datetime.strptime(text, '%Y-%m-%d').date()
# Верно: регистрируем класс date
sqlite3.register_adapter(date, adapt_calendar_day)
sqlite3.register_converter('date', parse_calendar_day)
# --- база данных в памяти ---
cn = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
cur = cn.cursor()
cur.execute('''
    CREATE TABLE IF NOT EXISTS logbook (
        ident INTEGER PRYMARY KEY,
        d_today DATE NOT NULL
    )
''')
today_val = date.today()
cur.execute('INSERT INTO logbook (d_today) VALUES (?)', (today_val,))
cur.execute('SELECT d_today FROM logbook')
_ = cur.fetchall()
cn.commit()
cn.close()

Если удобнее, конвертер может разбирать ISO‑8601 через date.fromisoformat(), это эквивалентно в данном случае и делает намерение очевидным.

Почему это важно

Начиная с Python 3.12, стандартный адаптер для дат помечен как устаревший. Полагаться на него — значит получать лишние предупреждения и скрывать правила сериализации ваших типов, что ухудшает прозрачность модели данных. Корректная регистрация адаптеров и конвертеров даёт явный контроль над обработкой типов и обеспечивает совместимость на будущее по мере развития стандартной библиотеки.

Выводы

Если вы импортируете datetime и date напрямую из datetime, помните: имя datetime обозначает класс datetime. Регистрируйте адаптеры для класса date, а не для datetime.date. В случае сомнений добавьте короткий диагностический print в адаптер и конвертер, чтобы проверить, что они действительно используются. Явные адаптер и конвертер устраняют предупреждение об устаревании и делают работу с типами в SQLite предсказуемой и удобной в сопровождении.

Материал основан на вопросе с StackOverflow от João Vítor Araújo и ответе LMC.