Rain Lag

От крошечной 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 .

Что это делает:

  1. Устанавливает logcleaner в текущее окружение.
  2. Создаёт команду logcleaner, используя точку входа из [project.scripts].

Теперь можно запускать:

logcleaner /tmp/logs --dry-run

Любые изменения в src/logcleaner сразу отражаются в поведении команды, пока вы работаете в том же окружении. Это стандартный рабочий процесс для разработки Python‑CLI.


5. Версионирование и лучшие практики упаковки

Если вы когда‑нибудь захотите опубликовать свою утилиту — на PyPI или во внутренний репозиторий — стоит с самого начала придерживаться пары простых правил.

Выберите схему версионирования

Используйте предсказуемую схему, например Semantic Versioning:

  • MAJOR.MINOR.PATCH1.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 или частный индекс)

Когда вы довольны своей утилитой:

  1. Создайте README.md, в котором опишите:
    • что делает инструмент;
    • как его установить (pip install logcleaner);
    • базовое использование и примеры.
  2. По желанию добавьте файл LICENSE (MIT, Apache‑2.0 и др.).
  3. Проверьте, что метаданные в 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‑режиме в ходе разработки;
  • примете базовые практики версионирования и упаковки,

…то превратите разовый скрипт в отполированную, устанавливаемую утилиту, которую можно переиспользовать, делиться ею и даже публиковать.

Выберите сегодня один скрипт из своей папки с «инструментами». Оберните его в пакет, добавьте команду — и выпустите что‑то реальное, пусть даже очень маленькое.

От крошечной CLI-утилиты до отполированного Python‑инструмента: практическое руководство для новичков | Rain Lag