Промт для создания CLI-утилит на Python с argparse и логированием

Разница между скриптом и утилитой — в поведении на границах. Скрипт печатает результат и падает с трейсбеком, когда что-то не так. Утилита возвращает осмысленный код завершения, пишет ошибки в stderr, а данные в stdout, понимает --help и не молчит в ответ на неправильные аргументы. Второе стоит немногим дороже первого, если задать требования сразу.
Промт
Напиши CLI-утилиту на Python [версия] с argparse. НАЗНАЧЕНИЕ: [что делает, в одной фразе] АРГУМЕНТЫ: [позиционные и опции, что обязательно, значения по умолчанию] ВХОД: [файл / stdin / каталог] ВЫХОД: [файл / stdout / формат] ТРЕБОВАНИЯ: 1. Данные — только в stdout. Всё служебное: логи, прогресс, предупреждения — в stderr. 2. Коды возврата: 0 успех, 1 ошибка выполнения, 2 неверные аргументы. Другие коды описать отдельно. 3. Уровень логирования флагами: -v подробнее, -q только ошибки. По умолчанию — предупреждения и выше. 4. Ошибки пользователю — одной понятной строкой без трейсбека. Полный трейсбек только при --debug. 5. Флаг --dry-run для операций, изменяющих файлы: показать, что будет сделано, ничего не делая. 6. Корректная обработка Ctrl+C: без трейсбека, с кодом 130. 7. Проверка аргументов до начала работы: не начинать долгую операцию, чтобы упасть на середине. 8. Чтение из stdin, если вместо имени файла передан дефис. 9. Работа с путями через pathlib, кодировка при чтении и записи указана явно. СТРУКТУРА: main() возвращает код, функция run() содержит логику и не знает про argparse и sys.exit. Точка входа под if __name__ == '__main__'.
Пункт про разделение main и run окупается на следующий день: логику, не связанную с разбором аргументов, можно вызвать из тестов и из другого кода.
Почему потоки важнее, чем кажется
Смешивание данных и логов в stdout ломает главное свойство утилиты — возможность поставить её в конвейер. Если в поток данных попадает строка «Обработка началась...», следующая программа получит мусор. Проверяется одной командой: перенаправьте stdout в файл, и на экране должны остаться только служебные сообщения.
Логирование
Настрой логирование: - через модуль logging, без print для служебных сообщений; - обработчик в stderr, формат с уровнем и временем при -v, лаконичный по умолчанию; - уровень задаётся флагами, а не константой в коде; - сторонние библиотеки не должны заливать вывод своими DEBUG-сообщениями — приглуши их явно; - при --log-file дублировать в файл с ротацией. Не логируй содержимое обрабатываемых данных: там могут быть чувствительные значения.
Последний пункт стоит держать по умолчанию. Утилиты часто обрабатывают выгрузки с персональными данными, а лог живёт дольше, чем предполагалось.
Проверка готовой утилиты
| Проверка | Ожидаемое поведение |
|---|---|
| Запуск без аргументов | Краткая справка, код 2 |
| --help | Понятное описание, код 0 |
| Несуществующий файл | Одна строка в stderr, код 1, без трейсбека |
| Вывод в конвейер | В stdout только данные |
| Ctrl+C на середине | Тихий выход, файлы не испорчены |
| --dry-run | Ничего не изменилось на диске |
Когда argparse перестаёт хватать
Если у утилиты появляются подкоманды со своими наборами опций, argparse становится громоздким. Здесь уместно попросить модель переписать на click или typer — но именно попросить переписать, а не начинать с них: базовая версия на стандартной библиотеке запускается везде и не тянет зависимостей, что для служебных скриптов часто решающий довод.
Перепиши утилиту на [click / typer], сохранив поведение: те же флаги, те же коды возврата, то же разделение потоков. Покажи, что изменилось в интерфейсе, и что придётся поправить в скриптах, которые её вызывают.
Мелочь, которая экономит время
Попросите добавить в справку раздел с примерами: три-четыре реальные команды с пояснением. Через полгода вы сами будете читать --help вместо исходников, и примеры окажутся полезнее, чем описания флагов.