Перейти к контенту

Kawai-Focus 2.1: переезд на новый стек

Kawai-Focus Eugene Kaddo 39

Данная статья посвящена:

  • Причинам ухода с Kivy;
  • Переезду проекта на новый стек: FastApi + Vue.js + Tauri + Ionic;
  • Сборке приложения под Linux в AppImage.

Kawai-Focus 2.1: переезд на новый стек
Kawai-Focus Eugene Kaddo 39

Вступление

Всем доброго дня! В одной из предыдущих статей Вайбкодинг с нейросетью 1: проверяю сборку Flutter-приложения в AppImage:

  1. Сравнил Flutter и Kivy;
  2. Написал Hello World на Dart для Flutter;
  3. Использовал нейросети Perplexity и Giga Chat в качестве инструментов вайб-кодинга;
  4. Собрал приложение для Linux в AppImage.

Предыдущая статья была написана для конкурса «Властелин алгоритмов: сезон „ИИ в разработке“ на Хабре», где основной темой было применение ИИ в разработке. Это был мой первый опыт написания статьи про вайб-кодинг, и мне не очень зашла данная тематика, так как я не знаю Flutter. Ориентироваться только на то, что пишет нейросеть, довольно тяжело, потому что непонятно, к какому результату это может привести и насколько этот результат будет хорошим.

Статья была отражением того факта, что я начал сомневаться в стеке Kivy + Python. Когда я писал на Kivy + KivyMD 2.0.0 и собирал приложение под Linux, я сталкивался с рядом проблем, о которых речь пойдёт ниже в данной статье.

Что же касается Flutter и Dart, то мне понравились удобство и скорость приложения, которые они предоставляют. Однако этот стек нужно учить с нуля. Это несколько затрудняет переписывание приложения Kawai-Focus прямо сейчас, а на освоение Dart и Flutter может уйти много времени. Поэтому я решил попробовать перевести проект на более знакомый мне стек, а Dart и Flutter оставить запасным вариантом и не бросаться в них прямо сейчас.

В данной статье я попытаюсь переписать часть приложения Kawai-Focus на стек: FastAPI + Vue.js + Tauri + Ionic. Также я соберу приложение только с экраном «Таймеры» под Linux в AppImage, которое подойдёт для большинства Linux-дистрибутивов. Реализация одного экрана не будет слишком долгим процессом, а финальная сборка приложения позволит оценить сложность и удобство полного цикла разработки.

Данная статья не будет подробным гайдом, так как размер переписанного кода и его описание слишком велики для одной статьи. Однако я покажу часть кода проекта и расскажу о выбранном стеке, а также о трудностях и преимуществах, с которыми я столкнулся в процессе переписывания проекта. Я буду использовать часть кода старого проекта, который не касается Kivy и KivyMD, поэтому его душа не умрёт, а обновится.

Заваривайте чай, доставайте вкусняшки — пора «старый стек превращать в удобрение для новых помидор»! 🍅


Почему мне не подошёл Kivy?

Фреймворк Kivy я впервые увидел несколько лет назад в видеоролике одного блогера. Он рассказывал, как написать на нём простую игру «пинг-понг» и собрать её под Android с помощью Buildozer. Мне это показалось очень занятным, и я вручную переписал его гайд в коде, а затем собрал приложение под Android. Когда я запустил игру на телефоне, для меня это было чем-то волшебным, и мне это очень понравилось. Тогда у меня и возникла мысль попробовать сделать на нём что-то своё. Так, пару лет спустя, и появился прототип приложения Kawai-Focus.

Мне удалось реализовать цепочку таймеров и сохранение пользовательских таймеров в базу данных sqlite3. Однако стандартный дизайн Kivy был квадратным и топорным, и его явно нужно было менять. Каким-то чудом я нашёл форк KivyMD 2.0.0, который не заброшен и предоставляет современный Material Design. Я переписал дизайн, и он понравился пользователям в комментариях к статье. Мне казалось, что я почти справился, и я сел за сборку приложения под Linux.

Я ждал чуда, но вместо него получил разрушающее стек озарение…

На самом деле ещё до события, которое поставило на стеке Kivy крест, уже были тревожные звоночки:

  • Лаги при работе с интерфейсом — особенно это чувствовалось при разворачивании информации о таймере на экране «Таймеры». Блок раскрывается с такими фризами, что это уже стыдно показывать пользователю. Работать с таким интерфейсом неприятно, и, скорее всего, человек просто не захочет пользоваться приложением;
  • Вёрстка экранов — этот процесс оказался куда более муторным и непонятным, чем вёрстка в HTML. Логи часто сообщают о том, что используемый способ уже deprecated. Можно легко наткнуться на нерабочие варианты позиционирования элементов и другие странности;
  • Звук, ломающий запуск приложения — мне пришлось перейти с audio_sdl2 на ffpyplayer, так как audio_sdl2 вызывал падение приложения на этапе инициализации. При запуске Kivy не удавалось корректно инициализировать SDL-аудиодрайвер (отсутствие или конфликт системных аудиобиблиотек), из-за чего приложение завершалось ещё до отображения интерфейса;
  • Работа с версией KivyMD — разработка ведётся с форком, где рекомендуется либо клонировать репозиторий с главной веткой, либо использовать ссылку на zip-архив этой ветки. Это крайне неудобно в работе, например, с Poetry, поскольку зависимость невозможно зафиксировать как версионированный пакет: отсутствуют релизы и теги, а значит, нельзя гарантировать воспроизводимую сборку и стабильность окружения.

Окончательно добил проект на Kivy процесс сборки приложения под Linux. Приложение требует наличия древней и устаревшей библиотеки для сенсорных экранов внутри Flatpak-пакета. Всё это в итоге сильно увеличивает размер сборки.

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


Новый стек

Наступил тот момент, когда Kawai-Focus должен перейти на новый стек. Что же это может быть? Как я писал во вступлении к этой статье, я уже успел попробовать Flutter + Dart и пришёл к выводу, что это хороший стек, но переход на него займёт слишком много времени. Некий Сергей уже вовсю ждёт возможности опробовать мой Kawai-Focus, поэтому уход в кардинально новый стек — это заставить его ждать условный месяц. Да и сам я хочу получить результат побыстрее, поэтому начал подыскивать более удобные альтернативы на Python.

Одной из таких альтернатив мог оказаться фреймворк Flet. Казалось бы, хороший вариант: написан на Python, использует Flutter под капотом, позволяет писать код быстрее и проще, да и устаревших библиотек не требует. Однако верстать внешний вид на Python оказалось бы крайне неудобно. В качестве прототипа он подошёл бы хорошо, но довести интерфейс до уровня полноценного продукта было бы очень непросто.

В итоге я решил взять на пробу универсальный стек, который больше подойдёт веб-разработчику, решившему попробовать себя в кроссплатформенной разработке, не теряя при этом своей «веб-идентичности».

На самом деле изначально я обучался именно на веб-разработчика с использованием Python, а использование Kivy немного увело меня в сторону от этого направления. Работать сразу в двух стэках — это серьёзное распыление внимания в профессиональной карьере. Поэтому я решил сократить это распыление и немного подкорректировать используемый стек.

Основной стек:

  • FastAPI — вся логика приложения и бэкенд;
  • Vue.js — отвечает за внешний вид (фронтенд);
  • Ionic — нативный UI и мобильная версия;
  • Tauri — движок для десктопной версии приложения.

Остановимся немного на каждой технологии.

FastApi

FastAPI — современный асинхронный Python-фреймворк для создания API, ориентированный на высокую производительность, строгую типизацию и удобство разработки. В десктопном приложении FastAPI обычно выступает в роли локального бэкенда, обрабатывающего бизнес-логику, работу с данными и взаимодействие с внешними сервисами.

Благодаря ASGI, асинхронности и использованию Starlette и Pydantic, FastAPI показывает высокую скорость как в веб-приложениях, так и в десктопных сценариях, где фронтенд общается с ним через HTTP или WebSocket. При этом асинхронный подход больше подходит для веб-приложений с большим количеством пользователей и запросов. Для локального приложения с одним пользователем и базой данных sqlite3 вполне достаточно обычного синхронного подхода. Однако я не исключаю, что в будущем могу реализовать более сложную веб-версию, где асинхронность будет действительно необходима.

С этим фреймворком я уже знаком, но опробовал далеко не все его возможности. В основном я использовал FastAPI для работы с WebSocket и раздачи HTML. Это для меня комфортнее, чем Dart, так как здесь у меня есть пусть и небольшой, но реальный опыт, который хочется расширить и углубить.

Vue.js

Vue.js — прогрессивный JavaScript-фреймворк для создания пользовательских интерфейсов с упором на реактивность и компонентный подход. В десктопном приложении Vue.js отвечает за внешний вид и пользовательское взаимодействие, выступая фронтендом, работающим поверх веб-движка (например, Tauri). Благодаря лёгкости, виртуальному DOM и эффективной реактивной системе Vue.js обеспечивает высокую отзывчивость интерфейса как в вебе, так и в десктопных приложениях.

С этим фреймворком я знаком слабо, но у меня был небольшой опыт работы с ним примерно два года назад. Честно говоря, у меня больше практики с React и обычным JavaScript. Vue.js придётся освежить в памяти, но так как с фронтендом я всё же сталкивался, процесс пойдёт заметно быстрее, чем изучение Dart и Flutter с нуля.

Ionic

Ionic — фреймворк для создания кроссплатформенных приложений с использованием веб-технологий (HTML, CSS и JavaScript). В десктопных и мобильных приложениях Ionic отвечает за нативный внешний вид интерфейса и доступ к возможностям устройства, работая поверх фронтенда (например, Vue.js). За счёт оптимизированных UI-компонентов и интеграции с нативными API Ionic обеспечивает приемлемую производительность как на мобильных устройствах, так и в десктопных оболочках.

Как раз раньше я пробовал проходить гайды по Ionic в паре с Vue.js, что внушает надежду на более быстрое освоение этого фреймворка по сравнению с Flutter.

Tauri

Tauri — фреймворк для создания кроссплатформенных десктоп-приложений на базе веб-технологий. В десктопном приложении Tauri выступает в роли оболочки и движка, а его бэкенд написан на Rust, что даёт высокую производительность, безопасность памяти и существенно меньший размер сборки по сравнению с Electron-подобными решениями. Благодаря тесной интеграции с нативной системой и минимальному оверхеду Tauri обеспечивает быструю работу интерфейса и хорошую отзывчивость на всех поддерживаемых платформах.

Схема десктоп-приложения на Tauri:

Vue
 ↓
Tauri
 ↓
System WebView
 ↓
Native Window
  • Vue — отвечает за пользовательский интерфейс и логику фронтенда, формируя HTML, CSS и JavaScript-приложение;
  • Tauri — связующий слой между веб-интерфейсом и операционной системой, управляющий окнами, событиями и безопасным доступом к нативным возможностям;
  • System WebView — системный веб-движок, в котором отрисовывается Vue-приложение без встроенного браузера;
  • Native Window — нативное окно операционной системы, с которым взаимодействует пользователь.

Tauri позволяет работать с системными функциями напрямую из приложения:

  • Файловая система — чтение, запись и управление файлами с контролируемыми правами доступа;
  • Автозапуск — запуск приложения вместе с системой;
  • Системный трей — иконка и управление из области уведомлений;
  • Нотификации — показ системных уведомлений;
  • Системные хоткеи — регистрация глобальных сочетаний клавиш.

Tauri изначально спроектирован с упором на строгую безопасность: доступ к нативным API ограничен, разрешения настраиваются явно, а фронтенд изолирован от системы, что существенно снижает риск уязвимостей.

Всё это звучит хорошо, но раз речь идёт о веб-приложении, возникает закономерный вопрос: а как обстоят дела с потреблением оперативной памяти и размером итоговой сборки?

Одно из ключевых преимуществ Tauri — минимальный размер приложения и низкое потребление ресурсов. Так как Tauri использует системный WebView, а не встраивает собственный браузер, размер сборки обычно составляет несколько мегабайт, а не сотни, как у Electron-подобных решений. За счёт бэкенда на Rust и отсутствия тяжёлого runtime Tauri потребляет значительно меньше оперативной памяти, что особенно важно для фоновых утилит и постоянно работающих приложений.

Дополнительно я планирую скомпилировать Python-код с помощью Nuitka в C-код — это позволит ускорить выполнение и уменьшить занимаемый объём по сравнению с обычным Python.

В итоге получился довольно интересный стек как для веб-, так и для десктоп-разработки, который мне уже не терпится опробовать в деле. Кроме того, если убрать Tauri, можно сделать веб- и мобильную версии приложения, практически не меняя код. Звучит многообещающе, но как это покажет себя в реальном проекте — узнаете в следующем разделе статьи.


Процесс переезда

Теперь, когда обозначен новый стек приложения, пора заняться процессом «переезда» Kawai-Focus на него. Так совпало, что этот процесс происходит перед Новым годом, и получается, что вместе с обновлением года и календаря обновится и код приложения.

Лично я очень жду новогоднего чуда и хочу, чтобы моё приложение в новом году стало ещё лучше, а у читателей сбылись их надежды и мечты в наступающем 2026 году ⛄

Основа проекта

Часть кода из приложения Kawai-Focus я перенесу в новый проект, улучшая его в процессе переноса. Идея заключается в том, чтобы брать старый код и по возможности адаптировать и улучшать его под новый стек. Таким образом я сохраню основную идею приложения, но при этом сразу сделаю его лучше.

Например, CRUD-операции для работы с базой данных будут отлично работать и на новом стеке. В старом коде уже есть решения, которые вполне можно доработать и улучшить. О процессе рефакторинга я расскажу в следующих статьях, а сейчас сосредоточусь именно на описании переезда и общих идеях проекта.

Я решил не трогать старый репозиторий, а создать новый. В старом репозитории я укажу, что проект переехал на новый стек, и добавлю ссылку на него. Таким образом код старого проекта останется на память и будет использоваться в качестве примеров для статей на сайте «Код на салфетке».

Также изменениям подвергнется структура проекта, менеджер зависимостей (замена poetry на uv), а ещё будет добавлен линтер ruff, работающий под управлением pre-commit. По этой теме недавно у Ивана proDreams вышла отличная статья «ИИ бот-модератор 1: Начало проекта», где подробно рассказывается, как настроить uv и ruff под управлением pre-commit. Поэтому, в целях экономии времени, я воспользуюсь этим материалом для настройки базовой инфраструктуры проекта.

Менеджер пакетов uv написан на Rust и работает быстрее poetry, а ruff — это линтер, который проверяет код, тогда как pre-commit — фреймворк, автоматически запускающий набор проверок. Часто я сосредотачиваюсь на создании прототипа, игнорируя современные инструменты разработки. Поэтому в новом 2026 году я хочу использовать актуальные инструменты и сделать стек более свежим.

Теперь я займусь структурой проекта. Ниже я покажу примерную структуру, которая выступает в роли наброска первоначальной идеи. Итоговый вариант, скорее всего, будет отличаться от этой заготовки — ровно так же, как набросок художника отличается от готовой картины.

Базовая структура проекта:

kawai-focus-v2/
├── src/                             # src-layout от uv (ВАЖНО)
│   └── kawai_focus_v2/              # python package
│       ├── database/                # всё, связанное с БД
│       │   ├── __init__.py
│       │   ├── db.py                # engine, SessionLocal
│       │   ├── models.py            # Base + импорт моделей
│       │   └── mixins.py
│       │
│       ├── tests/
│       │
│       ├── alembic/                 # Alembic как часть приложения
│       │   ├── __init__.py
│       │   ├── README.md
│       │   ├── env.py
│       │   ├── script.py.mako
│       │   └── versions/
│       │
│       ├── __init__.py
│       ├── main.py                  # FastAPI entrypoint
│       ├── api/                     # routers
│       ├── core/                    # config, settings, paths
│       │   ├── config.py
│       │   └── settings.py
│       ├── schemas/                 # Pydantic схемы
│       └── services/                # бизнес-логика
│
├── client/                          # Ionic + Vue (frontend)
│   ├── src/
│   │   ├── main.js
│   │   ├── App.vue
│   │   ├── theme/
│   │   ├── views/
│   │   ├── services/
│   │   │   └── api.js
│   │   └── router/
│   │       └── index.js
│   │
│   ├── tests/
│   ├── public/
│   ├── capacitor.config.json
│   ├── ionic.config.json
│   ├── package.json
│   └── vite.config.js
│
├── desktop/                         # Tauri
│   └── src-tauri/
│       ├── tauri.conf.json
│       └── src/
│           └── main.rs
│
├── .gitignore
├── .pre-commit-config.yaml
├── alembic.ini
├── .python-version
├── pyproject.toml
├── uv.lock
├── README.md
└── LICENSE

Описание основных элементов структуры:

  • src/kawai_focus_v2/ — основной Python-пакет приложения, оформленный по src-layout, в котором размещены бизнес-логика, API и инфраструктурный код;
  • client/ — фронтенд-приложение на Ionic + Vue.js, отвечающее за пользовательский интерфейс и взаимодействие с бекендом;
  • desktop/ — конфигурация и исходный код десктопной оболочки на Tauri, связывающей фронтенд с нативным окружением ОС;
  • .pre-commit-config.yaml — конфигурация хуков pre-commit для автоматической проверки кода перед коммитами;
  • .python-version — файл с указанием версии Python, используемой в проекте, для синхронизации окружений разработки;
  • pyproject.toml — основной файл конфигурации проекта, описывающий зависимости, метаданные и настройки инструментов Python;
  • uv.lock — lock-файл менеджера зависимостей uv, фиксирующий точные версии пакетов для воспроизводимых сборок;
  • LICENSE — файл с лицензией проекта, определяющий условия использования и распространения кода.

Я использую следующие команды для начала работы в проекте:

  1. git clone git@github.com:Arduinum/kawai-focus-v2.git — клонирует репозиторий проекта из GitHub в локальную рабочую директорию;
  2. Установка uv (любым удобным способом);
  3. uv init --package kawai-focus-v2 — инициализирует Python-проект с использованием uv, создавая базовую структуру и конфигурационные файлы;
  4. cd kawai-focus-v2 — переходит в директорию инициализированного проекта;
  5. uv add --dev pre-commit — добавляет pre-commit в dev-зависимости проекта для автоматической проверки кода;
  6. uv run pre-commit install — устанавливает Git-хуки pre-commit, чтобы проверки запускались автоматически перед каждым коммитом.

После инициализации проекта у меня получилась следующая структура.

Мой пример дополняет статью Ивана тем, что в моём случае репозиторий и такие файлы, как README.md, .gitignore и LICENSE, создаются в GitHub, а не на локальном ПК. Приятный момент заключается в том, что команда uv init --package kawai-focus-v2, выполненная рядом с каталогом репозитория, а не внутри него, не пересоздаёт и не перезатирает содержимое README.md и .gitignore.

Далее я проверяю работу pre-commit, выполнив следующую команду. Я впервые использую pre-commit, поэтому было особенно интересно посмотреть, какой результат она выдаст.

uv run pre-commit run --all

Данная команда запускает все настроенные pre-commit хуки вручную для всех файлов проекта, а не только для тех, которые были изменены или добавлены в индекс Git.

Вывод команды:

trim trailing whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook
Fixing .gitignore
check yaml...........................................(no files to check)Skipped
check for case conflicts.................................................Passed
check for merge conflicts................................................Passed
fix end of files.........................................................Passed
pyupgrade............................................(no files to check)Skipped
ruff check...........................................(no files to check)Skipped
ty check.................................................................Passed

Приятно видеть, что инструмент сразу начал приносить пользу. Хук trim trailing whitespace нашёл лишние пробелы и удалил их, о чём говорит сообщение files were modified by this hook.

После этого я добавляю все файлы из корня проекта и делаю первый коммит.

git add . && git commit -m "feat: init project"

Вывод команды:

trim trailing whitespace.................................................Passed
check yaml...............................................................Passed
check for case conflicts.................................................Passed
check for merge conflicts................................................Passed
fix end of files.........................................................Passed
pyupgrade................................................................Passed
ruff check...............................................................Passed
ty check.................................................................Passed
[master 4608b1a] feat: init project
 7 files changed, 214 insertions(+), 2 deletions(-)
 create mode 100644 .pre-commit-config.yaml
 create mode 100644 .python-version
 create mode 100644 .vscode/settings.json
 create mode 100644 pyproject.toml
 create mode 100644 src/kawai_focus_v2/__init__.py
 create mode 100644 uv.lock

Все хуки отработали корректно, о чём свидетельствует статус Passed у каждого из них, и коммит успешно применился.

Чуть позже выяснилось, что я использую более старую версию ty-pre-commit, поэтому я выполнил команду обновления. Заодно обновился и ruff-pre-commit, который тоже был устаревшим.

pre-commit autoupdate

Вывод команды:

[https://github.com/pre-commit/pre-commit-hooks] already up to date!
[https://github.com/asottile/pyupgrade] already up to date!
[https://github.com/astral-sh/ruff-pre-commit] updating v0.13.0 -> v0.14.10
[https://foundry.fsky.io/vel/ty-pre-commit] updating 0.0.3 -> 0.0.8

Бекенд

Обновлять и писать новый код я начну с бекенда так как основная логика приложения у меня будет и именно там. Начну с установки dev зависимостей для python.

uv add --dev pytest ruff
  • pytest — библиотека для тестов;
  • ruff — линтер для проверки кода на ошибки (его использует alembic у меня в проекте).

Я откажусь от библиотеки ffpyplayer для воспроизведения звука, так как она может вызывать проблемы при установке на Windows и в целом больше ориентирована на Linux. В данной ситуации самым простым и надёжным решением будет использование фронтенда для воспроизведения звука — через Web Audio API или HTML5 Audio. Это позволит одинаково просто воспроизводить звук на разных платформах, не задумываясь о поддержке и установке аудиобиблиотек под различные операционные системы.

Установка основных зависимостей:

uv add fastapi sqlalchemy pydantic-settings alembic uvicorn
  • FastAPI — веб-фреймворк для создания API и бэкенд-логики приложения;
  • SQLAlchemy — ORM и инструментарий для работы с базой данных и SQL;
  • pydantic-settings — управление конфигурацией приложения через настройки и переменные окружения;
  • Alembic — инструмент для управления миграциями схемы базы данных;
  • Uvicorn — ASGI-сервер для запуска FastAPI-приложения.

Инициализация alembic:

uv run alembic init src/kawai_focus_v2/database/alembic

Настройки alembic я так же взял из старого проекта и поменял лишь импорты.

Я понимаю, что FastAPI хорош для асинхронной работы, но у меня десктоп-приложение с SQLite3, который не поддерживает асинхронность. Если я напишу веб-версию в будущем, которой потребуется асинхронность, то скорее всего создам её в отдельном репозитории ибо так будет гораздо удобнее.

Файлы models.py и mixins.py я просто скопирую из старого проекта и поменяю пути импорта если это будет необходимо. Файл settings.py теперь будет лежать в отдельной папке (core/) — это признак явного разделения инфраструктуры и бизнес-кода: настройки становятся частью слоя конфигурации, а не «глобальной помойкой» проекта.

Создание миграции для таблицы Timer в бд:

uv run alembic revision --autogenerate -m "Create Timer"

Вывод команды:

INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'timer'
  Generating /media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/src/kawai_focus_v2/database/alembic/versions/2025_12_25_1504-503b50886979_create_timer.py ...  done
  Running post write hook 'ruff' ...
1 file reformatted
  done

Кстати я заметил, что у меня теперь два ruff один в pre-commit используется, а другой в dev зависимостях и настроен для alembic и миграций. Есть два варинта: оставить как есть два ruffa или же оставить один ruff и дёргать его из .venv окружения c pre-commit для проверки перед коммитом. Для этого нужно будет поменять конфиг .pre-commit-config.yaml.

Напишите в комметариях какой вариант грамотнее на ваш взгляд. Мне будет очень интересно узнать ваше мнение. Я пока оставлю оба варианта и дождусь ваших ответов.

В .env поменяю название бд на NAME_DB = "timer.sqlite3" так будет более явнее чем timer.db.

Применение миграций:

uv run alembic upgrade head

Вывод команды:

INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 503b50886979, Create Timer

Отлично у меня теперь есть база данных с полями, с которой уже можно работать. В следующей части статьи я расскажу более подробно о самом коде бекенда, а пока я перейду к процессу создания бинарника.

Компиляция в бинарный файл

Nuitka — это компилятор для Python, который превращает Python-код в нативный исполняемый файл, компилируя его через C и убирая зависимость от интерпретатора на целевой машине.

Бинарник — это готовый исполняемый файл в машинном коде (например, ELF в Linux), который запускается напрямую операционной системой без shell-скриптов и интерпретаторов. Он ускоряет работу Python-кода, потому что компилирует его в машинный код, устраняя интерпретацию на лету и уменьшая накладные расходы на запуск и импорт модулей.

Процесс компиляции будет происходить на операционной системе Linux Debian!

Установка зависимостей для компиляции:

uv add --dev nuitka patchelf

Для переиспользования кэша при компиляции можно установить следующую библиотеку.

sudo apt install ccache

ccache при работе с Nuitka кэширует результат компиляции C/C++ файлов, которые Nuitka генерирует из Python-кода, чтобы при повторной сборке те же исходники не компилировались заново.

Теперь можно запустить сам процесс компиляции.

uv run nuitka \
  --standalone \
  --follow-imports \
  --include-module=uvicorn \
  --include-package=fastapi \
  --include-package=sqlalchemy \
  --output-filename=backend-x86_64-unknown-linux-gnu.bin \
  ./src/kawai_focus_v2/main.py

Разбор команды:

  • uv run nuitka — запускает Nuitka через твой CLI (uv), чтобы скомпилировать Python-код;
  • --standalone — собирает полностью независимый бинарник со всеми зависимостями, не требующий установленного Python;
  • --follow-imports — включает в сборку все импортированные модули автоматически;
  • --include-module=uvicorn — явно добавляет модуль uvicorn в бинарник, даже если он используется динамически;
  • --include-package=fastapi — включает пакет fastapi целиком в сборку;
  • --include-package=sqlalchemy — включает пакет sqlalchemy целиком в сборку;
  • --output-filename=backend-x86_64-unknown-linux-gnu.bin — задаёт имя и путь для сгенерированного бинарника;
  • ./src/kawai_focus_v2/main.py — указывает основной Python-файл, который будет точкой входа для компиляции.

В конце компиляции появится примерно следующая информация.

Nuitka: Successfully created 'main.dist/backend-x86_64-unknown-linux-gnu.bin'.

Строка говорит о том, что бинарный файл успешно создался. Протестировать работу бинарника можно, запустив его из терминала.

./main.dist/backend-x86_64-unknown-linux-gnu.bin

Вывод команды:

INFO: Started server process [151235]
    INFO: Waiting for application startup.
    INFO: Application startup complete.
    INFO: Uvicorn running on http://127.0.0.1:8090 (Press CTRL+C to quit)
    INFO: 127.0.0.1:32958 - "GET /docs HTTP/1.1" 200 OK
    INFO: 127.0.0.1:32958 - "GET /openapi.json HTTP/1.1" 200 OK

Судя по логам, Uvicorn успешно поднял бекенд на FastAPI по локальному адресу http://127.0.0.1:8090.

Фронтенд

После того как я успешно создал, настроил и протестировал бинарник бекенда, я перехожу к фронтенду.

Установка npm:

  • curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - — добавляет репозиторий Node.js 24.x и настраивает его для установки через apt;
  • sudo apt install -y nodejs — устанавливает Node.js и npm из настроенного репозитория;
  • node -v — проверяет версию установленного Node.js;
  • v24.12.0 — пример вывода команды node -v;
  • npm -v — проверяет версию установленного npm;
  • 11.6.2 — пример вывода команды npm -v;
  • cd client — переходит в директорию фронтенда проекта;
  • npm run build — собирает фронтенд-проект в production-режиме, создавая готовые к раздаче файлы.

Установка ionic:

  • sudo npm install -g @ionic/cli — устанавливает глобально CLI фреймворка Ionic;
  • ionic --version — проверяет установленную версию Ionic;
  • 7.2.1 — пример вывода команды ionic --version;
  • ionic serve — запускает локальный dev-сервер для проекта Ionic и открывает его в браузере.

Я написал код для фронтенда, который получает от бекенда список таймеров. Так как описание этого кода не помещается в текущую статью, я перенесу рассказ о нём на одну из следующих частей.

Мне понадобятся пару плагинов для взаимодействия tauri и фронтенда.

npm install @tauri-apps/plugin-shell @tauri-apps/api

Разбор команды:

  • @tauri-apps/plugin-shell — плагин для Tauri, который позволяет фронтенду запускать сторонние процессы и команды на локальной машине через безопасный API;
  • @tauri-apps/api — набор официальных API Tauri для работы с файловой системой, окнами, уведомлениями, диалогами и другими возможностями нативного приложения.

Для проверки запускаю фронтенд через ionic.

ionic serve

Вывод команды:

> vite --host=localhost --port=8100
[vite]   ➜  Local:   http://localhost:8100/
[vite]   ➜  press h + enter to show help
[INFO] Development server running!

Фронтенд успешно поднялся на локальном адресе http://localhost:8100/.

Если в консоли браузера появляется ошибка, связанная с CORS, и данные не отображаются, это означает, что необходимо разрешить CORS на бекенде.

После того как я разрешил URL фронтенда в CORS бекенда, запрос успешно выполнился, а данные отобразились на фронтенде.

Пока единственное действие, которое можно выполнить на странице таймеров, — развернуть информацию о таймере. Интересный момент: если развернуть другой таймер, первый будет сворачиваться автоматически. Это позволяет сократить путь пользователя и не держать большое количество раскрытых таймеров одновременно.


Desktop на Tauri

Теперь я перейду к одной из самых интересных частей проекта — настройке desktop-версии приложения.

Установка зависимостей

sudo apt install -y \
  build-essential \
  curl \
  wget \
  file \
  libssl-dev \
  libgtk-3-dev \
  libayatana-appindicator3-dev \
  librsvg2-dev \
  pkg-config \
  libwebkit2gtk-4.1-dev

Разбор зависимостей:

  • build-essential — устанавливает набор инструментов для компиляции C/C++ (gcc, g++, make и др.);
  • curl — утилита для скачивания файлов и работы с URL через командную строку;
  • wget — ещё одна утилита для загрузки файлов из интернета;
  • file — определяет тип файла (например, бинарник, текстовый или скрипт);
  • libssl-dev — библиотеки и заголовочные файлы OpenSSL для работы с криптографией и HTTPS;
  • libgtk-3-dev — библиотеки и заголовки GTK3 для создания графического интерфейса;
  • libayatana-appindicator3-dev — библиотеки для создания системных индикаторов/иконок в панели задач;
  • librsvg2-dev — библиотеки для работы с SVG-файлами (отрисовка и конвертация в растровые изображения);
  • pkg-config — утилита для получения информации о библиотечных файлах и их флагах компиляции;
  • libwebkit2gtk-4.1-dev — библиотеки WebKitGTK для интеграции веб-контента в GTK-приложения.

Установка Rust:

Я не смог скачать установщик с официального сайта (курсор зависал и пакеты не скачивались), поэтому пришлось использовать зеркала:

export RUSTUP_DIST_SERVER=https://rsproxy.cn 
export RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustup
  • curl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh — скачивает и запускает скрипт установки Rust через rustup;
  • Rust is installed now. Great! — пример вывода скрипта, подтверждающий успешную установку Rust;
  • source ~/.cargo/env — подгружает переменные окружения Rust в текущую сессию терминала;
  • cargo --version — проверяет версию установленного пакетного менеджера и сборщика проектов Cargo;
  • cargo 1.92.0 (344c4567c 2025-10-21) — пример вывода команды cargo --version;
  • rustc --version — проверяет версию установленного компилятора Rust;
  • rustc 1.92.0 (ded5c06cf 2025-12-08) — пример вывода команды rustc --version.

Установка Tauri:

  • cargo install tauri-cli — устанавливает CLI-инструменты Tauri через Cargo для работы с проектами Tauri;
  • cargo tauri --version — проверяет установленную версию Tauri CLI;
  • tauri-cli 2.9.6 — пример вывода команды, показывающий текущую версию Tauri CLI.

Инициализация проекта:

cargo tauri init

Необходимо будет ответить на несколько вопросов в интерактивном режиме: имя приложения, URL сервера, команды сборки фронтенда и т.д.

✔ What is your app name? · Kawai-Focus
✔ What should the window title be? · Kawai-Focus
✔ Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created? · ../client/dist
✔ What is the url of your dev server? · http://localhost:8100
✔ What is your frontend dev command? · cd ../client && ionic serve
✔ What is your frontend build command? · cd ../client && npm run build

Рекомендую добавить команду, которая подключает плагин для запуска сторонних процессов и команд из Tauri-приложения.

cargo add tauri-plugin-shell

В данной статье я не поместил информацию о настройке конфигурации Tauri — об этом я обязательно расскажу в одной из следующих частей. Задача этой статьи — показать идею, установку, первичную настройку, запуск приложения и процесс сборки.

Поговорим о смене иконок приложения на свои. Приложение использует несколько размеров иконок, но позволяет из одного изображения (например, 512×512) сгенерировать все необходимые размеры автоматически.

Дефолтные icons:

Команда для создания иконок:

cargo tauri icon path_your_icon

После выполнения команды в папке icons дефолтные иконки заменились на мои иконки с помидорками.

Запуск в режиме разработки:

cargo tauri dev

Режим разработчика удобен тем, что позволяет запускать консоль разработчика, похожую на консоль браузера: анализировать ошибки, смотреть HTML и многое другое. В данном случае консоль показала, что приложение не может найти таблицу taimer в базе данных.

Работоспособный экран приложения выглядит так:


Сборка под Linux в AppImage

Приступим к последнему этапу — сборке приложения AppImage под Linux. AppImage — это формат пакета для Linux, который позволяет распространять приложение вместе со всеми его зависимостями в одном исполняемом файле, который можно запускать напрямую, без установки и изменения системы. Такое приложение удобно запускать, оно не требует установки, поэтому я решил начать с него.

⚠️ Важно: сборка AppImage возможна только на Linux, и для неё должны быть установлены системные зависимости (libwebkit2gtk, libgtk-3, patchelf и др.).

Команда сборки:

cargo tauri build

Вывод команды:

Finished 3 bundles at:
        /media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/desktop/src-tauri/target/release/bundle/deb/Kawai-Focus_0.1.0_amd64.deb
        /media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/desktop/src-tauri/target/release/bundle/rpm/Kawai-Focus-0.1.0-1.x86_64.rpm
        /media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/desktop/src-tauri/target/release/bundle/appimage/Kawai-Focus_0.1.0_amd64.AppImage

Последние строки вывода показывают, что команда успешно собрала приложение сразу в трёх форматах: .deb, .rpm и .AppImage.


Запуск приложения

Для запуска приложения достаточно нажать на файл Kawai-Focus_0.1.0_amd64.AppImage, который находится по пути desktop/src-tauri/target/release/bundle/appimage.

Если запустить его из терминала, то в логи приложения, отображаемые Tauri, будет видно, что происходит в процессе. Для этого нужно написать специальный код на языке Rust. Подробнее об этом я обязательно расскажу в следующей части статьи. А пока что я успешно запустил приложение на Debian 12 с X11.

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


Также мне удалось успешно запустить приложение на своём ноутбуке с Debian 13 и Wayland.

Но не обошлось и без ложки дёгтя. Приложение не смогло отобразить окно в Arch Linux на ПК Сергея (того самого человека, который больше всего ждал моё приложение). Возможно, проблема связана с webkit2gtk, который предоставляет компонент для рендеринга веб-страниц внутри приложений, использующих GTK (графическую библиотеку).

Так же на ПК Ивана в Arch запустилось отображение, но возникла проблема с undefined symbol: rl_print_keybinding при запуске бекенда, которая связана с динамическими зависимостями, которые bash подхватывает внутри AppImage.

Будет очень интересно решить эти проблемы и запустить Kawai-Focus на Arch Linux. Такие ситуации только подстёгивают мой интерес к продолжению работы над приложением.


Анонс на следующие статьи

Сегодня я собрал первый урожай новых помидоров, которые вырастил, опираясь на предыдущий опыт выращивания 🙂

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

Также я постараюсь исправить критические проблемы запуска бэкенда на Arch и успешно запустить на нём своё приложение. Для этого, скорее всего, я уберу sh-скрипт и создам бинарник другим способом. Я пытался с помощью Nuitka собрать один файл бинарника, но Tauri, так сказать, немного испортил его в процессе сборки. Что с ним произошло, узнаете в следующей статье.

Кроме того, осталось много недосказанной информации по коду проекта, которая не поместилась в текущую статью. Например, я не показал код фронтенда, настройку конфигов для Tauri и многое другое. Всё это ждёт вас в следующих частях.

Код приложения я решил пока не выкладывать, так как ещё не показал его в статье. Я выложу сам AppImage, но запуск бэкенда внутри через sh-скрипт у вас может не сработать, как это произошло у Ивана под Arch!

Если у вас есть мысли о том, как можно улучшить проект, пишите в комментариях — с удовольствием ознакомлюсь с вашими предложениями!

Читайте продолжение — не пропустите!


Поздравления с НГ

Дорогие подписчики, гости канала и команда «Код на салфетке» — поздравляю всех с наступившим Новым 2026 годом и Рождеством! Желаю каждому, чтобы в 2026-м исполнились самые заветные мечты, а также чтобы год принёс рост, интересные задачи и успехи в IT-карьере.


Заключение

  1. Обсудили, почему Kivy не подошёл для моего проекта;
  2. Проект был переведён на новый стек технологий;
  3. Собрано приложение для Linux в формате AppImage.

Ссылки к статье

Аватар автора

Автор

Eugene Kaddo

Программист фрилансер. Пишу боты, парсеры и скрипты на Python3. По вопросам фриланс заказа программы пишите на почту, указанную в профиле. Автор статей по программированию. Увлекаюсь lego, робототехникой на arduino, рок музыкой, программированием на Python3 и C.

Комментарии