Знаете, в чём проблема большинства гайдов и курсов, которые обещают научить всему и сразу — да ещё и устроить на работу? Часто они учат примитиву, выдавая это за качественный контент. В итоге появляется много низкокачественного кода: на первый взгляд он работает, но в реальности трудно поддерживается.
Если в проекте нет структуры, он быстро превращается в кашу. Каждая доработка — это не отдельный продуманный модуль, а «приматывание новых кусков кода синей изолентой» с мыслью: «хоть бы не сломалось». Для новичка это особенно опасно: кажется, что всё нормально, пока проект маленький, но при росте даже простые изменения начинают занимать часы и ломать соседние части.
Вы наверняка задаётесь вопросом: «Почему рубрика называется “ИИ бот-модератор”, а автор тут рассказывает про качество кода?» На самом деле, всё связано.
Telegram-бот для группы — отличный пример проекта, который очень быстро обрастает фичами: команды, настройки, роли, интеграции, хранение данных, логирование, админка, модерация, ИИ и т.д. Если делать всё “в одном файле”, это почти гарантированно закончится болью. Поэтому в этой рубрике мы будем строить бота так, чтобы его можно было развивать: добавлять функциональность без постоянного страха «сломать всё».
В этом цикле статей:
Напишем многофункционального Telegram-бота для групп с ИИ.
Напишем микросервис с MCP-сервером на FastAPI (поясню, что это и зачем он нужен, когда дойдём до интеграции).
Познакомимся с архитектурой, близкой к DDD (Domain Driven Design): будем отделять “бизнес-логику” от инфраструктуры, чтобы код легче читался и расширялся.
Применим современные инструменты, вроде uv и dishka.
А ещё: Docker, PostgreSQL и многое другое.
Всё задуманное невозможно уместить в одной статье (ну или она будет на десятки тысяч символов). Подписывайтесь на наш Telegram-канал «Код на салфетке», чтобы следить за новыми частями и другими интересными статьями!
🤫А ещё! У нас скоро стартует новогодний розыгрыш!🤫
У меня уже есть базовый список функций, которые хочу реализовать в рамках цикла, но я буду рад вашим предложениям — особенно если вы уже сталкивались с модерацией в группах и знаете, что реально нужно.
Это не курс “от А до Я”, а цикл статей, где я шаг за шагом буду показывать процесс разработки, дополняя его небольшими вставками теории — ровно в тех местах, где она нужна для понимания решений.
Я не ставлю целью дать единственно верный подход: это мой взгляд на процесс разработки, и он может отличаться от вашего. Уверен, опытные специалисты найдут спорные места или ошибки — и это нормально. Но для новичков этот материал должен стать хорошей опорой, чтобы увидеть разработку не в формате “всё в одном main.py”, а в виде проекта, который можно поддерживать и развивать.
Я не буду глубоко уходить в теорию и буду держать фокус на практике. Поэтому если вы хотите, чтобы я отдельно раскрыл какую-то тему (например, DDD-подход, слои приложения, транзакции, тестирование, миграции, очереди, логирование) — пишите в комментариях, и я вынесу это в отдельную статью.
Контроль версий — одна из важнейших вещей в разработке. Он помогает не только «сохранять файлы», а управлять изменениями в проекте так, чтобы можно было безопасно экспериментировать и возвращаться к рабочему состоянию.
Система контроля версий позволяет:
Хранить историю изменений файлов (кто, что и когда поменял).
Откатывать неудачные изменения.
Работать с ветками для параллельной разработки разных фич.
Объединять (мержить) изменения между ветками.
Систем контроля версий существует много, но стандартом в индустрии считается git. Его создал Линус Торвальдс — автор ядра Linux. Важно не путать: Git — это инструмент, а GitHub/GitLab — сервисы, где часто хранят репозитории и ведут совместную работу.
Linux
Если вы используете Linux, то, скорее всего, git уже установлен. Проверить можно командой:
git --version
Если Git не установлен, проще всего поставить его через менеджер пакетов вашего дистрибутива (способы отличаются, поэтому ниже оставляю универсальную ссылку).
Windows
На Windows установить Git можно двумя основными способами:
Скачать и установить Git for Windows с официальной страницы: https://git-scm.com/download/win
Установить через winget. Для этого откройте PowerShell и выполните команду:
winget install --id Git.Git -e --source winget
MacOS
На macOS сначала проверьте, установлен ли git:
git --version
Если Git не установлен, поставьте одним из способов:
Через Xcode Command Line Tools: xcode-select --install
Через Homebrew: brew install git
Другое
Инструкции для всех ОС есть на официальной странице:
https://git-scm.com/install/
Первоначальная настройка
Чтобы Git мог «подписывать» ваши коммиты (указывать автора), нужно один раз указать имя и почту. Выполните в терминале:
# Установить имя пользователя git config --global user.name "Имя" # Установить электронную почту git config --global user.email "[email protected]"
Эти значения попадут в метаданные коммитов. Обычно почта должна совпадать с почтой аккаунта на GitHub/GitLab (если вы планируете пушить туда), но это не строго обязательно.
Работа с репозиторием
git init — инициализирует репозиторий в текущей директории (создаёт папку .git).
git clone <url> — копирует удалённый репозиторий (например, с GitHub) на ваш компьютер.
git status — показывает состояние репозитория: какие файлы изменены, какие подготовлены к коммиту и т.д.
Добавление и фиксация изменений
Тут полезно помнить про два «слоя»: рабочая директория (ваши файлы) и индекс/стейджинг (то, что пойдёт в следующий коммит).
git add <файл> — добавляет изменения указанного файла в индекс (подготовка к коммиту).
git add . — добавляет в индекс все изменённые и новые файлы в текущей директории.
git commit -m "сообщение" — создаёт коммит из того, что лежит в индексе.
Если хотите посмотреть, что именно уже попало в индекс, используйте git diff --staged (часто очень спасает от случайных коммитов).
Просмотр истории и различий
git log — показывает историю коммитов (автор, дата, сообщение, хеш).
git log --oneline --graph --all — компактный лог со «схемой» веток (удобно смотреть структуру).
git diff — показывает изменения, которые ещё не добавлены в индекс (строки до/после).
Работа с ветками
git branch — список локальных веток и текущая ветка.
git branch <имя> — создаёт новую ветку.
git checkout <ветка> — переключается на ветку (старый универсальный стиль).
git switch <ветка> — современная команда для переключения веток.
git switch -c <ветка> — создать новую ветку и сразу на неё перейти.
Обновление и отправка на удалённый репозиторий
git remote -v — показывает привязанные удалённые репозитории (обычно origin).
git pull — забирает изменения с сервера и пытается влить их в текущую ветку.
git push — отправляет локальные коммиты на сервер (по умолчанию в текущую ветку).
git push -u origin <ветка> — первый пуш ветки: создаёт ветку на сервере и связывает её с локальной.
Для понимания: pull = «забрать + слить». Иногда удобнее делать это в два шага через git fetch (забрать без слияния), но для простых сценариев pull достаточно.
Отмена и возврат изменений (простые случаи)
git restore <файл> — отменяет изменения в рабочей директории, возвращая файл к версии из последнего коммита.
git restore --staged <файл> — убирает файл из индекса, но оставляет изменения в рабочей директории.
git reset --hard HEAD — полностью откатывает рабочую директорию и индекс к последнему коммиту.
С reset --hard будьте осторожны: незакоммиченные изменения будут потеряны. Если сомневаетесь — лучше сначала посмотрите git status и git diff.
Далеко не все файлы и директории стоит добавлять в репозиторий. Обычно игнорируют:
Виртуальное окружение .venv — это локальные зависимости и бинарники, они у каждого могут отличаться и должны ставиться заново из pyproject.toml/requirements.txt.
Кэш Python pycache/ и файлы *.pyc — это автоматически генерируемые артефакты, в репозитории они не нужны.
Настройки IDE .idea/ или .vscode/ — это персональные настройки редактора (часто отличаются у разных людей).
Файлы окружения .env, логи, временные файлы, сборочные артефакты и т.п. — их либо генерирует приложение, либо они содержат локальные/секретные данные.
.gitignore — это текстовый файл со списком шаблонов, по которым Git решает, какие файлы и папки не отслеживать. Он начинается с точки (значит, файл скрытый) и не имеет расширения.
Важный нюанс: .gitignore не удаляет файлы, которые уже были добавлены в репозиторий ранее. Если файл уже отслеживается, его нужно отдельно «разотслеживать», например: git rm --cached <файл>.
Основные элементы синтаксиса
Одна строка — один шаблон (путь, маска или правило).
Пустые строки игнорируются.
Строки, начинающиеся с #, — комментарии.
* — «любой набор символов», ? — «один символ».
Завершающий / означает директорию: build/ — игнорировать папку build.
Ведущий / привязывает правило к корню репозитория: /config.json.
— любые подкаталоги: logs//debug.log.
! в начале строки отменяет игнорирование (исключение из правила):
*.log !keep.log
Любой проект начинается с выбора менеджера пакетов/проекта и создания виртуального окружения.
Вместе они отвечают за «быт» проекта:
Установка библиотек.
Разрешение зависимостей.
Запуск команд в окружении проекта.
Управление версией Python (когда это поддерживает выбранный инструмент).
Есть и другие возможности, но сейчас они не так важны.
В Python часть задач закрывают встроенные инструменты вроде pip и venv, а часть — отдельные утилиты, которые нужно ставить и подружить между собой. uv во многом снимает эту боль: это быстрый менеджер пакетов и проектов для Python, написанный на Rust.
uv можно воспринимать как «комбайн» для управления проектом:
Устанавливает и обновляет зависимости проекта.
Создаёт и управляет виртуальным окружением (по умолчанию часто используется папка .venv).
Умеет устанавливать и выбирать версии Python (например, через uv python install, а также через закрепление версии в .python-version).
Работает с lock-файлом uv.lock, чтобы окружение было воспроизводимым (особенно полезно в команде и на CI).
Может инициализировать VCS при создании проекта (например, Git через опции uv init).
Установить uv можно двумя способами.
Через standalone-инсталлятор (рекомендуемый вариант):
# Linux или MacOS - выполнить в терминале curl -LsSf https://astral.sh/uv/install.sh | sh # Windows - выполнить в PowerShell powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Глобально через pip (если так привычнее):
# Выплнить в терминале или PowerShell pip install uv
Работа с проектами
uv init — инициализирует новый Python‑проект в текущей папке (создаёт pyproject.toml и базовую структуру).
uv init my-project — создаёт директорию my-project и инициализирует проект внутри неё.
uv init --package my-project — создаёт «пакетный» проект и использует src-layout (часто удобный и более безопасный для импорта кода).
Управление зависимостями
uv add <пакет> — добавляет зависимость в проект (обновляет pyproject.toml и ставит пакет в окружение).
uv add --dev <пакет> — добавляет зависимость для разработки (например, линтеры/тесты).
uv remove <пакет> — удаляет пакет из зависимостей проекта и из окружения.
Запуск кода в окружении проекта
uv run <команда> — запускает команду в окружении проекта (и при необходимости синхронизирует зависимости/окружение).
- uv run python main.py
- uv run pytest
uv run -- <команда с аргументами> — позволяет явно отделить опции uv от аргументов запускаемой команды.
Синхронизация окружения
uv sync — приводит окружение проекта в соответствие с зависимостями из pyproject.toml и lock-файла uv.lock.
Сам uv
uv self update - обновляет uv до последней версии.
uv self version - показывает версию установленного uv.
Ещё один полезный класс инструментов в разработке — всевозможные линтеры и форматтеры.
Линтеры — это программы для статического анализа кода. Они проверяют исходники по набору правил: от стилистики и форматирования до поиска потенциальных ошибок и «подозрительных» мест. Важно: линтеры не запускают проект, а анализируют только код. Особенно полезно это в командной разработке: меньше шансов закоммитить проблемный код, и у всех участников получается единый стиль.
Линтеры можно ставить и запускать по отдельности, но удобнее пользоваться фреймворком pre-commit.
pre-commit — это фреймворк для управления Git‑хуками. Он подключается к git commit и автоматически запускает набор проверок (хуков), которые вы описываете в конфигурации. Если какая-то проверка не пройдена, коммит прерывается — пока вы не исправите ошибки (или не отключите/настроите правило). Это помогает не тащить в репозиторий «грязный» или явно ошибочный код.
Установите pre-commit как dev‑зависимость, находясь в директории проекта:
uv add --dev pre-commit
Дальше нужно сделать две вещи:
Создать конфиг .pre-commit-config.yaml в корне проекта — без него pre-commit просто не будет знать, что запускать.
Установить Git‑хук:
uv run pre-commit install
Обратите внимание! Для установки хуков должен быть инициализирован git‑репозиторий (git init), иначе pre-commit не сможет «подцепиться» к коммитам.
Помимо автоматического запуска при коммите есть и ручной запуск — удобно, чтобы прогнать проверки перед первым коммитом или после больших правок:
uv run pre-commit run --all
Обратите внимание! По умолчанию pre-commit проверяет файлы, которые попадают в коммит (обычно — проиндексированные, то есть добавленные через git add). Если файл не добавлен в индекс, он, как правило, не будет проверяться — сначала добавьте его в staging или запускайте проверки по всем файлам через --all-files.
Запускаемые проверки (хуки) для pre-commit описываются в файле .pre-commit-config.yaml, который лежит в корне проекта — обычно рядом с pyproject.toml.
.pre-commit-config.yaml — это YAML-файл, где на верхнем уровне используется список repos, а внутри каждого репозитория перечисляются hooks.
Структура верхнего уровня
repos: — список источников хуков (чаще всего это Git-репозитории).
Дополнительно встречаются опции вроде default_stages, но на старте достаточно понимать repos.
Простейший пример
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer
Поля внутри repos
У каждого элемента в repos обычно есть:
repo — URL (или путь) к репозиторию с хуками.
rev — версия/тег/коммит (почти всегда стоит фиксировать версию, а не main).
hooks — список хуков из этого репозитория.
Поля внутри hooks
Каждый хук задаётся набором параметров, самые полезные:
id — идентификатор хука (обязателен).
args — список аргументов, которые будут переданы хуку.
files / types — какие файлы проверять.
exclude — какие файлы/пути исключить.
stages — на каких стадиях Git-хуков запускать (обычно и так достаточно дефолта).
Ниже пример «базы», которую обычно хватает, чтобы сразу почувствовать пользу: мелкие проверки файлов + авто-апгрейд синтаксиса + линтинг/форматирование Python-кода.
Хуки из pre-commit-hooks
Подключён стандартный набор готовых хуков из pre-commit-hooks, который:
убирает лишние пробелы в конце строк (trailing-whitespace), следит за пустой строкой в конце файла (end-of-file-fixer) и предотвращает конфликты имён файлов на разных ОС (check-case-conflict);
проверяет YAML‑файлы на корректный синтаксис (check-yaml), а также не даёт случайно закоммитить маркеры незавершённых merge‑конфликтов (check-merge-conflict).
Хук pyupgrade
Репозиторий asottile/pyupgrade добавляет хук pyupgrade, который переписывает код на более современный синтаксис Python.
Флаг --py313-plus говорит, что код должен соответствовать возможностям Python 3.13 и новее, поэтому инструмент может смело применять самые свежие упрощения и оптимизации.
Хук ruff
Репозиторий astral-sh/ruff-pre-commit добавляет ruff-check — быстрый линтер и форматер для Python. Аргументы --fix и --line-length=120 включают автоматическое исправление найденных проблем и задают максимальную длину строки в 120 символов, чтобы код соответствовал выбранному стилю.
Хук ty
Репозиторий vel/ty-pre-commit подключает хук ty-check, который запускает ty — новый сверхбыстрый проверщик типов для Python. ty выполняет статическую проверку аннотаций типов, быстро находит несоответствия и потенциальные ошибки в типах, что повышает надёжность кода и делает работу с типизированным Python комфортнее даже в больших проектах.
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: check-yaml - id: check-case-conflict - id: check-merge-conflict - id: end-of-file-fixer - repo: https://github.com/asottile/pyupgrade rev: v3.21.2 hooks: - id: pyupgrade args: [ --py313-plus ] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.13.0 hooks: - id: ruff-check args: [ "--fix", "--line-length=120" ] - repo: https://foundry.fsky.io/vel/ty-pre-commit rev: 0.0.3 hooks: - id: ty-check
Последний в этой статье, но не по значению инструмент, который мы будем активно применять — это make.
make — консольная утилита для выполнения команд и их последовательностей, пришедшая из мира C/C++ (исторически — для сборки проектов). Сейчас make часто используют как универсальный task-runner: удобные «шорткаты» для запуска проекта, линтеров, миграций и других регулярных действий.
Проще говоря, make позволяет один раз описать нужные проекту команды в одном файле — и потом вызывать их коротко и одинаково у всех в команде.
Команды для make описываются в файле Makefile (без расширения).
Базовый синтаксис правила выглядит так:
target: <dependencies> <TAB>command 1 <TAB>command 2
target — цель: имя «задачи» (часто это виртуальная команда вроде test, lint, run).
dependencies — зависимости цели (другие цели или файлы).
Команды (recipe) — это обычные shell-команды; каждая строка рецепта должна начинаться с TAB, иначе make не распознает её как команду правила.
Ниже пример для проекта на uv. Обратите внимание: перед командами именно TAB (не пробелы).
install: uv sync run_dev: uv run app migrate: uv run alembic upgrade head run_prod: install migrate uv run app
В примере выше у нас четыре команды (цели): install, run_dev, migrate и run_prod. Каждая из них описывает один «шорткат» для частой операции в проекте.
install — выполняет uv sync — установка/обновление зависимостей проекта.
run_dev — запускает приложение в режиме разработки командой uv run app.
migrate — применяет миграции базы данных через uv run alembic upgrade head.
Цель с зависимостями
run_prod: install migrate — у цели run_prod есть зависимости install и migrate, поэтому при make run_prod сначала выполняются цели install и migrate (в указанном порядке), а затем команда uv run app. Так можно собрать цепочку действий («установи зависимости → примени миграции → запусти приложение») в одну удобную команду.
Команды запускаются просто: make <target>
Для примера выше запуск проекта во время разработки будет таким: make run_dev
С теоретической частью покончено — пора переходить к созданию проекта.
Прежде чем начнём, убедитесь, что у вас установлены git, uv и make. Быстро проверить можно так:
git --version uv --version make --version
Откройте терминал и перейдите в удобную директорию:
cd <путь_до_директории>
Теперь инициализируем проект через uv:
uv init --package ai_moderation_bot
Флаг --package создаст «пакетный» проект со структурой src/..., а также сгенерирует базовые файлы проекта вроде pyproject.toml и README.md.
Переходим в свежесозданную директорию:
cd ai_moderation_bot
Отлично, начало положено. Теперь папку проекта можно открыть в любой удобной IDE — я буду использовать PyCharm.
Как видно на скриншоте, при инициализации проекта uv создаёт .gitignore. Но базовый файл часто неполный: в нём могут отсутствовать правила под IDE, локальную конфигурацию и артефакты вроде логов.
В нашем случае добавим игнорирование:
настроек IDE (.idea/, .vscode/), чтобы не тащить в репозиторий личные рабочие файлы (при желании можно игнорировать не всё, а только персональные файлы IDE, но для старта проще целиком).
файла переменных окружения .env (его обычно не коммитят, потому что там могут оказаться токены/пароли).
директории логов logs/, потому что это генерируемые файлы.
Откройте .gitignore и добавьте в конец:
# IDE .idea/ .vscode/ # Config .env # Logs logs/
Получается вот так:
Выполняем то, что было описано ранее.
Сперва установим библиотеку:
uv add pre-commit
Затем, инициализируем:
uv run pre-commit install
Теперь создадим в корне файл .pre-commit-config.yaml со следующим содержимым:
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: check-yaml - id: check-case-conflict - id: check-merge-conflict - id: end-of-file-fixer - repo: https://github.com/asottile/pyupgrade rev: v3.21.2 hooks: - id: pyupgrade args: [ --py313-plus ] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.13.0 hooks: - id: ruff-check args: [ "--fix", "--line-length=120" ] - repo: https://foundry.fsky.io/vel/ty-pre-commit rev: 0.0.3 hooks: - id: ty-check
Прежде чем запустить и проверить как работает, необходимо добавить файлы в индекс git-репозитория.
Для этого выполним команду:
git add .
Теперь, когда файлы добавлены в отслеживание, запустим pre-commit:
uv run pre-commit run --all
Сперва идёт скачивание линтеров (хуков). Это происходит только один раз, впоследствии они будут запускаться сразу:
После чего идёт непосредственно проверка:
В двух файлах не было пустой строки в конце и хук end-of-file-fixer это исправил.
В корне создадим файл Makefile. У нас сейчас есть всего одна команда - запуск линтеров, её и добавим.
Создадим цель lint и пропишем команду запуска pre-commit:
lint: uv run pre-commit run --all
Теперь запустим:
make lint
Всё работает!
Остался последний шаг - зафиксировать изменения в git-репозитории.
Для этого выполним команду добавления в git, но уже не всех файлов, а только Makefile, т.к. он единственный новый файл:
git add Makefile
Теперь создаём коммит:
git commit -m "Init project"
В этой статье было совсем мало практики, но это только первая, отправная точка в большом пути проекта.
В процессе написания мы ещё не раз воспользуемся или нам помогут упомянутые выше инструменты. Не зря они так полюбились разработчикам, что считаются обязательными для каждого проекта.
Подписывайтесь на Telegram-канал «Код на салфетке» и следите за развитием или даже предлагайте свои идеи!
🤫А ещё! У нас скоро стартует новогодний розыгрыш!🤫
Благодарю за прочтение, увидимся в следующих статьях!
Источник


