Python sqlite3 row_factory: строки как словари

Категория: Python · Язык: Python · Обновлено:

По умолчанию sqlite3 возвращает каждую строку как кортеж, и приходится помнить порядок столбцов. connection.row_factory = sqlite3.Row даёт доступ по имени и по индексу, а dict(row) превращает запись в обычный словарь. Это уменьшает связность кода с порядком полей в SELECT.

Читать строки по именам столбцов

Python
import sqlite3

con = sqlite3.connect(":memory:")
con.row_factory = sqlite3.Row
con.execute("CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)")
con.execute("INSERT INTO user (name) VALUES (?)", ("Анна",))

row = con.execute("SELECT id, name FROM user").fetchone()
print(row["name"])   # Анна
print(row[0])        # 1
print(dict(row))     # {'id': 1, 'name': 'Анна'}

sqlite3.Row поддерживает keys() и итерацию по значениям, поэтому его можно передавать в шаблоны и JSON-сериализаторы после dict(). Доступ по имени работает только для выбранных столбцов; SELECT * сделает код зависимым от схемы, поэтому перечисляйте поля явно.

row_factory задаётся на соединении и действует на все курсоры этого соединения. В пуле соединений настройте фабрику при создании каждого подключения, иначе часть запросов вернёт кортежи. Курсор может переопределить фабрику через cursor.row_factory, но смешивать разные форматы в одном модуле не стоит.

Вернуть dataclass из строки

Python
from dataclasses import dataclass
import sqlite3

@dataclass
class User:
    id: int
    name: str

def user_factory(cursor, row):
    fields = [column[0] for column in cursor.description]
    return User(**dict(zip(fields, row)))

con.row_factory = user_factory
user = con.execute("SELECT id, name FROM user").fetchone()
print(user)  # User(id=1, name='Анна')

Фабрика получает курсор и кортеж значений и может вернуть любой объект. Проверяйте, что имена в cursor.description совпадают с полями dataclass: лишний столбец вызовет TypeError. Для вложенных объектов и связей одного row_factory мало — делайте отдельные запросы или собирайте словарь вручную.

SQLite использует динамическую типизацию, поэтому значение из базы может быть str, int или None независимо от объявления колонки. Приводите типы в фабрике или на уровне репозитория. Для дат используйте detect_types=sqlite3.PARSE_DECLTYPES и адаптеры, но проверяйте их на своей версии Python.

Документация: sqlite3.Row, row_factory.