2025, Nov 05 09:01

Передача результатов fetchall() из Flask в Jinja2 без ошибок

Разбираем, почему при передаче fetchall() из Flask в шаблон Jinja2 возникает TypeError, и как безопасно вывести список кортежей: индексы, подготовка данных.

Передача результатов запроса к базе данных из Flask в шаблон Jinja2 кажется простой — пока шаблон не пытается трактовать строку как скаляр. Симптом — запутывающая ошибка TypeError о том, что int() получил кортеж, хотя сам SQL‑запрос выполняется без проблем. Ниже — краткое объяснение, как это возникает, как воспроизвести ситуацию и как аккуратно вывести набор результатов в HTML.

Как воспроизвести проблему

Запрос к базе выполняется и возвращает строки, но передача «сырого» результата в шаблон заканчивается ошибкой на этапе рендеринга. Логика проста: выполнить параметризованный SELECT, забрать все строки и передать их в render_template.

import sqlite3 as sql
conn = sql.connect('./database.db')
cur = conn.cursor()
cur.execute(
    'SELECT year, unit, facilities, workforce, vehicles FROM units WHERE unit = ?',
    (u_value,)
)
rows = cur.fetchall()
conn.close()
return render_template('analysis.html', rows=rows)

При рендеринге возникает ошибка:

TypeError: int() argument must be a string, a bytes-like object or a real number, not 'tuple'

Преобразование в list ничего не меняет, потому что внутренняя структура по‑прежнему кортеж из значений столбцов, а выгрузка результата в JSON лишь превращает всё в строку, из‑за чего в шаблоне появляется другая ошибка:

ValueError: invalid literal for int() with base 10: '['

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

fetchall() возвращает список кортежей, где каждый кортеж — это одна строка. Jinja2 спокойно итерирует такие строки без особой обработки, но в шаблоне нужно явно обращаться к каждой строке и её полям. Если где‑то в шаблоне попытаться привести всю строку (кортеж) к int, рендерер и выдаст TypeError о том, что int() получил кортеж. Преобразование rows в list не меняет вложенную структуру, а JSON‑кодирование заменяет структурированные данные строкой, из‑за чего ошибки приведения типов становятся ещё менее очевидными.

Минимальная и предсказуемая структура данных для шаблона

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

import sqlite3 as sql
conn = sql.connect('./database.db')
c = conn.cursor()
c.execute(
    'SELECT year, unit, facilities, workforce, vehicles FROM units WHERE unit = ?',
    (u_value,)
)
result_set = c.fetchall()
conn.close()
# подготовка строк для представления: берём первые три столбца в каждой строке
view_rows = []
for idx in range(len(result_set)):
    view_rows.append((result_set[idx][0], result_set[idx][1], result_set[idx][2]))
return render_template('analysis.html', view_rows=view_rows)

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

<tbody>
    {% for rec in view_rows %}
    <tr>
        <td>{{ rec[0] }}</td>
        <td>{{ rec[1] }}</td>
        <td>{{ rec[2] }}</td>
    </tr>
    {% endfor %}
</tbody>

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

Шаблоны чувствительны к форме данных. Списком кортежей легко итерироваться — но только если шаблон воспринимает каждый кортеж как строку, а индекс — как столбец. Случайное приведение типов или пересечение имён маскируют реальную причину и приводят к нечитаемым ошибкам во время рендера. Чёткое разделение параметра запроса, «сырого» результата и структуры, готовой для вида, делает код менее хрупким, а шаблоны — понятнее.

Практические выводы

Результаты fetchall() — это последовательность кортежей, их можно выводить напрямую, если шаблон итерируется по строкам и обращается к столбцам по индексу. Если нужны только отдельные столбцы, подготовьте отдельный список кортежей ровно с этими полями, передайте его в шаблон и выводите по индексам. Не используйте одно и то же имя переменной для разных ролей, например для параметра запроса и набора результатов; выбирайте говорящие названия для ясности. Если удобнее ключи вместо индексов, верните строкоподобные словари, чтобы в шаблоне обращаться к полям по имени. При сбоях рендера выводите или логируйте реальную структуру данных и прикладывайте полный traceback — это быстро показывает, совпадает ли форма данных с ожидаемой.

Делайте форму данных явной, а логику в шаблоне — простой. При предсказуемом интерфейсе между слоем работы с БД и Jinja2 рендеринг таблиц остаётся прямолинейным и безошибочным.

Статья основана на вопросе с StackOverflow от QuestionsAndAnswers и ответе от QuestionsAndAnswers.