От крошечной CLI-утилиты до отполированного Python‑инструмента: практическое руководство для новичков
Узнайте, как превратить быстрый Python‑скрипт в полноценную, устанавливаемую командную утилиту с помощью современного пакета с pyproject.toml, entry points и лучших практик публикации.
От крошечной CLI-утилиты до отполированного Python‑инструмента: практическое руководство для новичков
У большинства разработчиков есть папка с маленькими Python‑скриптами, которые решают реальные задачи — но только для них самих. Это может быть очистка логов, форматирование CSV или небольшой скрипт для бэкапа. Всё работает… пока вы помните точную команду для запуска, путь к файлу и то, в каком виртуальном окружении он живёт.
Хорошая новость: из такого «чернового» скрипта легко сделать настоящий, устанавливаемый инструмент командной строки — такой, который запускается просто как logcleaner из любого места в системе. В этом руководстве мы пройдём путь от «маленького скрипта» до «отполированной Python‑утилиты» с использованием современных практик упаковки.
Мы сосредоточимся на:
- использовании
pyproject.tomlи современных инструментов упаковки - добавлении аргументов и опций командной строки
- экспонировании функций как команд через
[project.scripts] - установке в editable‑режиме для удобной разработки
- подготовке к публикации на PyPI или во внутренний (частный) репозиторий
1. Начните с простого Python‑скрипта
Представим, что у вас есть небольшой скрипт для очистки лог‑файлов:
# logcleaner.py import os from pathlib import Path LOG_DIR = Path("/var/log/myapp") for log_file in LOG_DIR.glob("*.log"): if log_file.stat().st_size == 0: log_file.unlink() print(f"Deleted empty log: {log_file}")
Он работает, но:
- вам нужно помнить путь к
logcleaner.py; - вы не можете просто так передать опции (например, другой каталог или
--dry-run); - вы не можете удобно установить этот инструмент другим людям.
Наша цель — превратить его во что‑то, что можно установить и запускать так:
logcleaner --path /var/log/myapp --dry-run
2. Сделайте полноценный интерфейс командной строки
Чтобы относиться к скрипту как к «настоящему» CLI‑инструменту, нужен интерфейс, принимающий аргументы и опции. Для начала отлично подходит стандартная библиотека argparse.
Перепишем код в виде небольшого пакета и сделаем модуль входной точки main.py.
Структура проекта:
logcleaner/ ├─ src/ │ └─ logcleaner/ │ ├─ __init__.py │ └─ main.py └─ pyproject.toml # добавим его чуть позже
src/logcleaner/main.py:
from __future__ import annotations import argparse from pathlib import Path def clean(path: str | Path, dry_run: bool = False) -> None: log_dir = Path(path) if not log_dir.exists() or not log_dir.is_dir(): raise SystemExit(f"Error: {log_dir} is not a valid directory") for log_file in log_dir.glob("*.log"): if log_file.stat().st_size == 0: if dry_run: print(f"[DRY RUN] Would delete: {log_file}") else: log_file.unlink() print(f"Deleted empty log: {log_file}") def main() -> None: parser = argparse.ArgumentParser(description="Clean up empty log files.") parser.add_argument( "path", nargs="?", default="/var/log/myapp", help="Path to the log directory (default: /var/log/myapp)", ) parser.add_argument( "--dry-run", action="store_true", help="Show what would be deleted without removing anything", ) args = parser.parse_args() clean(path=args.path, dry_run=args.dry_run) if __name__ == "__main__": # всё ещё можно запускать как `python -m logcleaner.main` main()
Теперь у нас есть:
- переиспользуемая функция
clean(), которую легко тестировать напрямую; - функция
main(), отвечающая за разбор аргументов командной строки; - структура, пригодная для упаковки в пакет.
Вы также можете использовать библиотеки вроде Click, Typer или сочетание Argparse + Rich для более удобных CLI, но argparse вполне достаточно для начала.
3. Используйте pyproject.toml и современную упаковку
Современная упаковка Python строится вокруг pyproject.toml, который заменяет (или дополняет) устаревший setup.py. Этот файл описывает метаданные проекта, зависимости и то, как его собирать.
Создайте pyproject.toml в корне проекта:
[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "logcleaner" version = "0.1.0" description = "A tiny CLI tool to clean up empty log files." readme = "README.md" requires-python = ">=3.9" authors = [ { name = "Your Name", email = "you@example.com" } ] license = { text = "MIT" } keywords = ["logs", "cli", "utilities"] # Если у вас есть зависимости, перечислите их здесь # dependencies = [ # "rich>=13.0.0", # ] [project.urls] Homepage = "https://github.com/yourname/logcleaner" [project.scripts] logcleaner = "logcleaner.main:main"
Критически важная часть для CLI — секция [project.scripts]:
[project.scripts] logcleaner = "logcleaner.main:main"
Она говорит системе упаковки:
При установке этого проекта создай исполняемый скрипт
logcleaner, который вызывает функциюmainизlogcleaner/main.py.
Можно сопоставить несколько команд:
[project.scripts] logcleaner = "logcleaner.main:main" logcleaner-clean = "logcleaner.main:clean" # вызывает функцию напрямую
Так обычные Python‑функции превращаются в полноценные команды командной строки.
4. Установите пакет в editable‑режиме (для разработки)
Во время разработки не хочется переустанавливать пакет после каждого изменения кода. Editable‑режим решает эту проблему, «связывая» установленный пакет с исходным кодом.
Из корня проекта выполните:
python -m pip install -e .
Что это делает:
- Устанавливает
logcleanerв текущее окружение. - Создаёт команду
logcleaner, используя точку входа из[project.scripts].
Теперь можно запускать:
logcleaner /tmp/logs --dry-run
Любые изменения в src/logcleaner сразу отражаются в поведении команды, пока вы работаете в том же окружении. Это стандартный рабочий процесс для разработки Python‑CLI.
5. Версионирование и лучшие практики упаковки
Если вы когда‑нибудь захотите опубликовать свою утилиту — на PyPI или во внутренний репозиторий — стоит с самого начала придерживаться пары простых правил.
Выберите схему версионирования
Используйте предсказуемую схему, например Semantic Versioning:
MAJOR.MINOR.PATCH→1.4.2- Увеличивайте:
- PATCH для исправления багов
- MINOR для новых функций, не ломающих совместимость
- MAJOR для изменений, ломающих старый интерфейс
Держите версию в одном месте — обычно в pyproject.toml в поле [project] version.
Соберите артефакты дистрибутива
Используйте пакет build, чтобы создать распространяемые файлы:
python -m pip install build python -m build
Будут сгенерированы:
- исходный дистрибутив (
.tar.gz), - wheel (
.whl)
в директории dist/. Именно эти файлы вы будете загружать или распространять.
Протестируйте установку в «чистом» окружении
Перед публикацией всегда тестируйте установку так, как это сделал бы конечный пользователь:
python -m venv .venv-test source .venv-test/bin/activate # или .venv-test\Scripts\activate в Windows python -m pip install dist/logcleaner-0.1.0-py3-none-any.whl logcleaner --help
Если всё работает без проблем, вы на правильном пути.
6. Подготовка к публикации (PyPI или частный индекс)
Когда вы довольны своей утилитой:
- Создайте
README.md, в котором опишите:- что делает инструмент;
- как его установить (
pip install logcleaner); - базовое использование и примеры.
- По желанию добавьте файл
LICENSE(MIT, Apache‑2.0 и др.). - Проверьте, что метаданные в
pyproject.tomlактуальны (авторы, ссылки, описание).
Чтобы опубликовать пакет на публичном PyPI:
python -m pip install twine python -m twine upload dist/*
Для частного репозитория (например, внутренний Artifactory, Nexus или простой индекс) организация обычно выдаёт вам URL для загрузки. После этого установка будет выглядеть так:
python -m pip install --index-url https://your-private-index/simple logcleaner
Так как вы придерживались современных стандартов, ваш пакет будет легко размещать где угодно.
7. Используйте зрелые библиотеки и паттерны для CLI
Хотя argparse — отличный инструмент, многие «боевые» CLI‑утилиты используют устоявшиеся библиотеки:
- Click — декларативные декораторы, удобен для вложенных команд.
- Typer — интерфейс, ориентированный на type hints, очень удобен, построен поверх Click.
- Rich-Argparse — более красивые сообщения помощи и об ошибках.
Эти библиотеки реализуют распространённые паттерны:
- чёткое разделение бизнес‑логики и разбора аргументов CLI;
- консистентный вывод
--help; - поддержка подкоманд (
git commit,git pushи т. п.).
Вы можете начать с небольшого скрипта на argparse, а позже перейти на Click или Typer, не меняя модель упаковки. Секция [project.scripts] по‑прежнему будет указывать на одну функцию входа.
8. Наведите «глянец»
Чтобы перейти от «оно у меня работает» к «выглядит как законченный инструмент», добавьте пару штрихов:
- информативный вывод
--helpс примерами использования; - понятные сообщения об ошибках, используя
SystemExitс человекочитаемым текстом; - режимы логирования или подробности (
--verbose,--quiet); - базовые тесты основной функциональности;
- минимальный CHANGELOG, чтобы фиксировать изменения в версиях.
Необязательно доводить всё до идеала, но даже небольшая полировка делает инструмент более надёжным и приятным для пользователей.
Вывод: выпустите что‑то настоящее
Чтобы освоить «боевую» упаковку Python‑проектов, не нужен гигантский проект. Достаточно скрипта строк на 50.
Если вы:
- оформите код как пакет;
- добавите простой CLI‑интерфейс с аргументами и опциями;
- используете
pyproject.tomlи[project.scripts]для описания точек входа; - будете ставить пакет в editable‑режиме в ходе разработки;
- примете базовые практики версионирования и упаковки,
…то превратите разовый скрипт в отполированную, устанавливаемую утилиту, которую можно переиспользовать, делиться ею и даже публиковать.
Выберите сегодня один скрипт из своей папки с «инструментами». Оберните его в пакет, добавьте команду — и выпустите что‑то реальное, пусть даже очень маленькое.