Настройка Google Sheets
Dinary-server хранит runtime-данные в DuckDB, а не в Google Sheets. Google Sheets используются в двух вспомогательных сценариях: bootstrap import исторических данных и опциональный append-only sheet logging. Для работы нужен сервисный аккаунт Google и одна или несколько таблиц, расшаренных на него.
1. Создание проекта Google Cloud
- Перейдите на console.cloud.google.com.
- Нажмите Выберите проект → Новый проект → назовите его (например,
dinary) → Создать. - Выберите созданный проект.
2. Включение необходимых API
- Перейдите в APIs & Services → Library.
- Найдите Google Sheets API → нажмите → Enable.
- Вернитесь в Library, найдите Google Drive API → нажмите → Enable.
3. Создание сервисного аккаунта
- Перейдите в APIs & Services → Credentials → Create Credentials → Service account.
- Заполните:
- Name:
dinary(или любое имя) - ID: генерируется автоматически
- Name:
- Нажмите Create and Continue → пропустите необязательные шаги → Done.
4. Скачивание JSON-ключа
- В разделе Credentials нажмите на созданный сервисный аккаунт.
- Перейдите в Keys → Add Key → Create new key → JSON → Create.
- Сохраните скачанный файл как
~/.config/gspread/service_account.json:
mkdir -p ~/.config/gspread
mv ~/Downloads/your-project-*.json ~/.config/gspread/service_account.json
Warning
Храните этот файл в секрете. Никогда не коммитьте его в Git — он уже добавлен в .gitignore.
5. Создание и расшаривание таблицы
- Перейдите на sheets.google.com → создайте новую таблицу.
- Назовите её (например,
Dinary Expenses). - Скопируйте ID таблицы из URL:
https://docs.google.com/spreadsheets/d/<SPREADSHEET_ID>/edit. - Нажмите Поделиться → вставьте email сервисного аккаунта (из JSON-ключа, поле
client_email, видаdinary@project-id.iam.gserviceaccount.com) → роль Редактор → Отправить.
6. Настройка dinary-server
Установите переменные окружения (в .env или в настройках хостинга):
| Переменная | Значение |
|---|---|
DINARY_GOOGLE_SHEETS_CREDENTIALS_PATH |
путь к service_account.json (по умолчанию: ~/.config/gspread/service_account.json) |
DINARY_IMPORT_SOURCES_JSON |
JSON-массив с описанием годовых исходных таблиц для bootstrap import |
Источники для bootstrap import
DINARY_IMPORT_SOURCES_JSON используется историческим import-флоу (inv import-config, inv import-catalog, inv import-budget, inv import-budget-all, inv verify-bootstrap-import, inv verify-bootstrap-import-all).
Пример:
DINARY_IMPORT_SOURCES_JSON=[{"year":2026,"spreadsheet_id":"1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms","worksheet_name":"Sheet1","layout_key":"default"}]
Каждый объект описывает один исходный spreadsheet для одного года. Основные поля:
year— бюджетный годspreadsheet_id— ID Google Sheets spreadsheetworksheet_name— имя листа для import расходовlayout_key— парсер формата листа (default,rub_6colи т.д.)income_worksheet_name— необязательное имя листа для import доходовincome_layout_key— необязательный парсер для import доходов
7. Логгинг в таблицу (опционально)
Sheet logging автоматически добавляет каждый новый расход в Google Sheets в реальном времени. Это удобно, если вы хотите строить сводные таблицы или графики в Google Sheets параллельно со встроенной аналитикой Dinary.
Как это работает
- Каждый
POST /api/expensesдобавляет строку в первый лист указанной таблицы. - 3D-категория/событие/теги проецируются в 2D
(sheet_category, sheet_group)через таблицуlogging_mapping. Если маппинг для категории не найден, название категории используется как fallback. - Один и тот же лист может хранить несколько лет одновременно. Строки сортируются по
(год, месяц, sheet_category, sheet_group), новые блоки(год, месяц)уходят наверх. Год берётся из реального значения колонки A (Google показывает её как, например,Apr-1, но хранит2026-04-01), поэтому январь 2026 и январь 2027 не путаются между собой. - В колонке J каждой добавленной строки лежит непрозрачный маркер
[exp:<expense_id>]. Если предыдущая попытка добавления для того же расхода уже дошла до Google, но ответ был потерян (тайм-аут), следующая попытка увидит маркер и пропустит дублирующую запись. - Если запись не удалась (ошибка сети, квота), задание остаётся в очереди и повторяется автоматически встроенным периодическим drain'ом (раз в
DINARY_SHEET_LOGGING_DRAIN_INTERVAL_SECсекунд, по умолчанию 300;0отключает периодический drain).
Включение
Установите DINARY_SHEET_LOGGING_SPREADSHEET — ID таблицы или полный URL из браузера:
# Просто ID таблицы:
DINARY_SHEET_LOGGING_SPREADSHEET=1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms
# Или полный URL (ID извлекается автоматически):
DINARY_SHEET_LOGGING_SPREADSHEET=https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms/edit
Таблица должна быть расшарена с сервисным аккаунтом из шага 4 (роль «Редактор»).
Отключение
Оставьте DINARY_SHEET_LOGGING_SPREADSHEET пустой или не задавайте. Расходы по-прежнему сохраняются в DuckDB; пропускается только запись в Google Sheets.
Повторы автоматические
Отложенные задания обрабатываются автоматически встроенным периодическим drain-таском внутри процесса FastAPI. При запуске сервера все накопленные задания обрабатываются сразу; после этого drain повторяется каждые DINARY_SHEET_LOGGING_DRAIN_INTERVAL_SEC секунд (по умолчанию 300). Внешней CLI-команды для запуска нет — восстановление полностью автоматическое.
Ограничение скорости. За одну итерацию drain обрабатывает не более DINARY_SHEET_LOGGING_DRAIN_MAX_ATTEMPTS_PER_ITERATION строк (по умолчанию 15) с паузой DINARY_SHEET_LOGGING_DRAIN_INTER_ROW_DELAY_SEC секунд между попытками (по умолчанию 1.0). Одна попытка делает 1-3 вызова Google Sheets API (чтение маркера идемпотентности, при необходимости append, при необходимости очистка дубликата), поэтому установившийся темп обращений — от ~3 до ~9 вызовов в минуту, что комфортно укладывается в квоту 60/min на пользователя. Очередь из 60 записей после рестарта разбирается примерно за 20 минут; 1000 записей — несколько часов. Повышайте cap только если уверены в запасе по квотам Sheets API.
TTL. Записи для расходов старше DINARY_SHEET_LOGGING_DRAIN_MAX_AGE_DAYS дней (по умолчанию 90) молча пропускаются и остаются в sheet_logging_jobs. Чтобы записать старый расход в таблицу вручную, пересоздайте его или удалите соответствующую строку из очереди через DuckDB CLI при остановленном сервере.
Warning
Внешней CLI-команды для обработки очереди при работающем сервере нет. DuckDB разрешает только одного писателя на файл между процессами, поэтому внешний drainer пришлось бы координировать с остановкой сервера. Lifespan-таск — единственный поддерживаемый recovery-путь.