консольное приложение на python
Пишем инструменты командной строки на Python с помощью Click
Авторизуйтесь
Пишем инструменты командной строки на Python с помощью Click
Python — невероятно гибкий язык программирования, который хорошо интегрируется с существующими программами. Немало Python-кода написано в виде скриптов и интерфейсов командной строки (CLI).
Инструменты и интерфейсы командной строки — эффективная вещь, так как они позволяют автоматизировать практически всё что угодно. Как следствие, эти интерфейсы с течением времени могут стать довольно сложными.
Обычно всё начинается с простого скрипта на Python, который что-то делает. Например, получает доступ к веб-API и выводит результат в консоль:
Как и было сказано, довольно простой скрипт.
Но что делать, когда подобная программа растёт и становится всё более сложной?
Решением этого вопроса мы сегодня и займёмся. Вы узнаете об основах написания интерфейсов командной строки на Python и о том, как click позволяет упростить этот процесс.
Используя эти знания, мы шаг за шагом перейдём от простого скрипта к интерфейсу командной строки с аргументами, опциями и полезными инструкциями по использованию. Всё это мы сделаем с помощью библиотеки click.
К концу этой статьи вы будете знать:
Вы увидите, как сделать всё это с минимальным количеством шаблонного кода.
Примечание переводчика Код в данной статье написан на Python 3.6, работоспособность на более ранних версиях не гарантируется.
Зачем вам писать скрипты и инструменты для командной строки на Python?
Код выше – всего лишь пример, не очень полезный в реальной жизни. На самом деле скрипты бывают куда более сложные. Возможно, вы имели опыт с ними и знаете, что они могут быть важной частью нашей повседневной работы: некоторые скрипты остаются на протяжении всего времени жизни проекта, для которого они были написаны. Некоторые начинают приносить пользу другим командам или проектам. У них даже может расширяться функционал.
В этих случаях важно сделать скрипты более гибкими и настраиваемыми с помощью параметров командной строки. Они позволяют указать имя сервера, учётные данные или любую другую информацию скрипту.
Здесь приходят на выручку такие модули, как optparse и argparse, которые делают нашу жизнь на порядок проще. Но прежде чем мы с ними познакомимся, давайте разберёмся с терминологией.
Основы интерфейса командной строки
Интерфейс командной строки (CLI) начинается с имени исполняемого файла. Вы вводите имя в консоль и получаете доступ к главной точке входа скрипта, такого как pip.
В зависимости от сложности CLI обычно есть определённые параметры, которые вы можете передавать скрипту:
С более сложными CLI, такими как pip или Heroku CLI, вы получаете доступ к набору функций, которые собраны под главной точкой входа. Они обычно называются командами или подкомандами.
Пакеты для работы с командной строкой, доступные в стандартной библиотеке Python 3.x
Добавление команд и параметров в ваши скрипты может сделать их значительно лучше, но парсить командную строку не так просто, как может показаться. Однако вместо того, чтобы пытаться самостоятельно решить эту проблему, лучше воспользоваться одним из многих пакетов, которые сделали это за вас.
Два наиболее известных пакета для этого — optparse и argparse. Они являются частью стандартной библиотеки Python и добавлены туда по принципу «всё включено».
По большей части они делают одно и то же и работают схожим образом. Главное отличие заключается в том, что optparse не используется начиная с Python 3.2, и argparse считается стандартом для создания CLI в Python.
Вы можете узнать о них больше в документации Python, но, чтобы иметь представление, как выглядит скрипт с argparse, посмотрите на пример ниже:
click против argparse: лучшая альтернатива?
Вероятно, вы смотрите на этот код и думаете: «Что это всё значит?» И это является одной из проблем argparse: код с ним неинтуитивен и сложночитаем.
Поэтому вам может понравиться click.
Click решает ту же проблему, что и optparse и argparse, но немного иначе. Он использует декораторы, поэтому ваши команды должны быть функциями, которые можно обернуть этими декораторами.
С click легко создавать многофункциональный CLI с небольшим количеством кода. И этот код будет легко читаться, даже когда ваш CLI вырастет и станет более сложным.
Пишем простой CLI на Python с помощью click
Вдоволь поговорив о CLI и библиотеках, давайте взглянем на пример, чтобы понять, как написать простой CLI с click. Как и в первом примере, мы создаём простой CLI, который выводит результат в консоль. Это несложно:
Не пугайтесь последних двух строк: это то, как Python запускает функцию main при исполнении файла как скрипта.
Более реалистичный пример CLI на Python с использованием click
Теперь, когда вы знаете, как click упрощает написание CLI, давайте взглянем на более реалистичный пример. Мы напишем программу, которая позволяет нам взаимодействовать с веб-API.
API, который мы дальше будем использовать, — OpenWeatherMap API. Он предоставляет информацию о текущей погоде, а также прогноз на пять дней для определённого местоположения. Мы начнём с тестового API, который возвращает текущую погоду для места.
Прежде чем мы начнём писать код, давайте познакомимся с API. Для этого можно использовать сервис HTTPie, включая онлайн-терминал.
Давайте посмотрим, что случится, когда мы обратимся к API с Лондоном в качестве местоположения:
Если вы смущены наличием API-ключа в примере сверху, не переживайте, это тестовый API-ключ, предоставляемый сервисом.
Более важное наблюдение заключается в том, что мы отправляем два параметра (обозначаемые == при использовании HTTPie), чтобы узнать текущую погоду:
Это позволяет нам создать простую реализацию на Python с использованием библиотеки requests (опустим обработку ошибок и неудачных запросов для простоты):
Эта функция делает простой запрос к API, используя два параметра. В качестве обязательного аргумента она принимает location (местоположение), которое должно быть строкой. Также мы можем указать API-ключ, передавая параметр api_key при вызове функции. Это необязательно, так как по умолчанию используется тестовый ключ.
И вот мы видим текущую погоду в Python REPL:
Парсим обязательные параметры с click
Простая функция current_weather позволяет нам создать CLI с местоположением, указанным пользователем. Это должно работать примерно так:
Как вы, возможно, догадались, местоположение — это аргумент, поскольку оно является обязательным параметром для нашего погодного CLI.
Давайте возьмём наш предыдущий пример и слегка изменим его, добавив аргумент location :
Если этот print выглядит для вас странно, не волнуйтесь — это новый способ форматирования строк в Python 3.6+, который называется f-форматированием.
Как вы видите, всё, что нам нужно сделать, это добавить дополнительный декоратор к нашей функции main и дать ему имя. Click использует имя в качестве имени аргумента, переданного обёрнутой функции.
Примечание переводчика Имя аргумента, переданное click, должно совпадать с именем аргумента в объявлении функции.
Реализация main просто использует нашу функцию current_weather для получения погоды в указанном месте. И затем мы с помощью print выводим полученную информацию.
Парсим опциональные параметры с click
Как вы, возможно, догадались, тестовый API ограничивает нас в возможностях. Поэтому, прежде чем мы продолжим, зарегистрируйтесь и получите настоящий API-ключ.
Первое, что нам нужно изменить, — URL, откуда берутся данные о текущей погоде. Это можно сделать, изменив значение переменной url в функции current_weather на URL, указанный в документации OpenWeatherMap:
Проще простого. Посмотрим, как добавить опцию к нашей существующей команде:
Мы добавили возможность указывать свой собственный ключ и проверять погоду в любом месте:
Добавляем автоматически генерируемые инструкции по использованию
Можете себя похвалить, вы создали отличный небольшой CLI почти без шаблонного кода. Однако прежде чем вы решите отдохнуть, давайте убедимся, что новый пользователь будет знать, как пользоваться нашим CLI, путём добавления документации. Не бойтесь, всё будет просто.
Первое, что нужно исправить, это добавить описание для нашей опции с API-ключом. Всё, что нам для этого нужно сделать, – добавить справочный текст в декоратор @click.option :
Сложив всё вместе, мы получаем хороший вывод для нашего инструмента:
Подводим итоги
Итак, в этом уроке мы рассмотрели много всего. Можете гордиться собой, вы написали свой собственный CLI, и всё это с минимальным количеством шаблонного кода! Исходный код ниже доказывает это. Не стесняйтесь использовать его для собственных экспериментов:
Сравнение популярных CLI-библиотек для Python: click, cement, fire и другие
Python — отличный язык для консольных приложений, и это подчёркивает большое количество библиотек для этих задач. Но какие вообще библиотеки существуют? А какую лучше взять? В этом материале сравниваются популярные и не очень инструменты для консольного мира и дана попытка ответить на второй вопрос.
Для удобства чтения обзор разделён на два поста: в первом сравнивается шесть самых популярных библиотек, во втором — менее популярные и более специфичные, но всё же заслуживающие внимания.
В каждом из примеров будет написана на Python 3.7 консольная утилита к библиотеке todolib, с которой можно создавать, просматривать, помечать и удалять задачи. Остальное будет дописано при условии простоты реализации на том или ином фреймворке. Сами задачи хранятся в json-файле, который будет сохраняться отдельным вызовом — дополнительное условие к примерам.
Вдобавок к этому, для каждой реализации будет написан тривиальный тест. За фреймворк для тестирования взят pytest со следующими фикстурами:
В принципе, для демонстрации библиотек всего перечисленного хватит. Полный исходный код доступен в этом репозитории.
argparse
У argparse есть неоспоримое преимущество — он есть в стандартной библиотеке и его API нетрудно выучить: есть парсер, есть аргументы, у аргументов есть type, action, dest, default и help. И есть subparser — возможность выделять часть аргументов и логики в отдельные команды.
Парсер
На первый взгляд — ничего необычного, парсер как парсер. Но — на мой взгляд — читаемость не самая лучшая, если сравнивать с другими библиотеками, т.к. аргументы к разным командам описываются в одном месте.
И здесь то же самое — парсер кроме парсинга аргументов больше ничего не умеет, так что логику придётся писать самостоятельно и в одном месте. С одной стороны — жить можно, с другой — можно же лучше, только пока неясно, как именно.
UPD: Как подметил foldr, на самом деле сабпарсерам можно задать функции через set_defaults(func=foo), то есть argparse позволяет укоротить main до небольших размеров. Век живи — век учись.
Тестирование
Для проверки вывода утилиты используется фикстура capsys, которая даёт доступ к тексту из stdout и stderr.
Из плюсов — хороший набор возможностей для парсинга, наличие модуля в стандартной библиотеке.
Минусы — argparse занимается лишь парсингом аргументов, большую часть логики в main пришлось писать самому. И неясно, как в тестах проверять exit code.
docopt
docopt — это небольшой ( 6700 звёзд, он используется в минимум 22 тысячах других проектах. И это лишь у python-реализации! На странице проекта docopt есть множество вариантов под разные языки, от C и PHP до CoffeeScript и даже R. Такую кросплатформенность могу объяснить лишь компактностью и простотой кода.
Парсер
В сравнении с argparse, этот парсер — большой шаг вперёд.
В целом всё так же, как и с argparse, однако теперь у verbose может быть несколько значений (0-2), и ещё доступ к аргументам отличается: docopt возвращает не namespace с атрибутами, а просто словарь, где выбор той или иной команды обозначается через её boolean, что видно в if:
Тестирование
Аналогично тестированию argparse:
Из плюсов — гораздо меньше кода для парсера, простота описания и чтения команд и аргументов, встроенный version.
Минусы, во-первых, те же, что и у argparse — много логики в main, нельзя протестировать exit code. К тому же текущая версия (0.6.2) docopt ещё не стабильна и вряд ли когда-нибудь будет — проект активно развивался с 2012 по конец 2013 года, последний коммит был в декабре 17-го. А самое неприятное на данный момент — некоторые регулярки docopt’а провоцируют DeprecationWarning’и при выполнении тестов.
Click
Click принципиально отличается от argparse и docopt количеством функционала и подходом к описанию команд и параметров через декораторы, а саму логику предлагается выделять в отдельные функции вместо большого main. Авторы утверждают, что у Click много настроек, но стандартных параметров должно хватить. Среди фич подчёркиваются вложенные команды и их ленивая подгрузка.
Проект крайне популярен: кроме того, что у него >8100 звёзд и он используется в минимум 174 тысячах (!) проектах, он до сих пор развивается: версия 7.0 вышла осенью 2018 года, а новые коммиты и merge request’ы появляются и по сей день.
Парсер
На странице документации я нашёл декоратор confirmation_option, который запрашивает подтверждения у пользователя перед выполнением команды. Для его демонстрации была добавлена команда wipe, которая очищает весь список задач.
И тут мы встречаемся с главным преимеществом Click — благодаря тому, что логика команд разнесена по их функциям, в main почти ничего не остаётся. Также здесь продемонстрирована возможность библиотеки получать аргументы и параметры из переменных окружения.
Тестирование
В случае с Click в перехвате sys.stdout нет нужды, так как есть модуль click.testing с раннером для таких вещей. И мало того, что CliRunner сам перехватывает вывод, он ещё и позволяет проверить exit code, что тоже круто. Всё это позволяет тестировать click-утилиты без использования pytest и обходиться стандартным модулем unittest.
Это лишь малая часть того, что умеет Click. Из остального API — валидация значений, интеграция с терминалом (цвета, пейджер а-ля less, прогресс-бар и т.д.), result callback, автодополнение и многое другое. Можете посмотреть их примеры здесь.
Плюсы: много инструментов на любой случай, оригинальный, но при этом удобный подход к описанию команд, простота тестирования и активная жизнь проекта.
Минусы: Какие у «клика» минусы — это сложный вопрос. Может, он чего-то не умеет из того, на что способны следующие библиотеки?
Fire — это не просто молодая (появилась в 2017-м) библиотека для консольных интерфейсов от Google, это библиотека для генерации консольных интерфейсов из, дословно цитируя, абсолютно любого объекта Python.
Среди прочего заявляется, что fire помогает в разработке и отладке кода, помогает адаптировать существующий код в CLI, облегчает переход из баша в Python и обладает своим REPL для интерактивной работы. Посмотрим?
Парсер и main
fire.Fire действительно способен принимать любой объект: модуль, инстанс класса, словарь с именами команд и соответствующими функциями, и так далее.
Что для нас важно, так это то, что Fire допускает передачу объекта класса. Таким образом, конструктор класса принимает аргументы, общие для всех команд, а его методы и атрибуты являются отдельными командами. Этим мы и воспользуемся:
Встроенные флаги
У Fire есть собственные флаги с особым синтаксисом (их надо передавать после «—«), которые позволяют заглянуть под капот парсера и приложения в целом:
Тестирование
Тестирование main-функции аналогично тестированию argparse и docopt, поэтому приводить его здесь не вижу смысла.
В то же время стоит отметить, что из-за интроспективной натуры Fire можно с тем же успехом тестировать сразу класс Commands.
Fire — инструмент, интересный не меньше, чем click. Он не требует перечисления множества опций в парсере, конфигурация минимальна, есть свои опции для отладки, а сама библиотека живёт и развивается даже активнее, чем click (60 коммитов за это лето).
Минусы: умеет ощутимо меньше, чем click и другие парсеры; нестабильный API (текущая версия — 0.2.1).
Cement
Вообще-то, Cement не совсем CLI-библиотека, а фреймворк для консольных приложений, но утверждается, что он подходит и для скриптов, и для сложных приложений с различными интеграциями.
Парсер
Парсер в Cement выглядит необычно, но если приглядеться к параметрам, то нетрудно догадаться, что под капотом стоит знакомый argparse. Но, может, это и к лучшему — не надо учить новые параметры.
App и main
Cement, кроме всего остального, ещё оборачивает сигналы в исключения. Здесь это продемонстрировано на выходе с нулевым кодом при SIGINT/SIGTERM.
Если вчитаться в main, то можно заметить, что загрузку и сохранение todolib.TodoApp можно провести и в переопределённых __enter__/__exit__, но эти фазы в итоге были выделены в отдельные методы для того, чтобы продемонстрировать хуки Cement.
Тестирование
Для тестирования можно использовать тот же класс приложения:
Итоги
Плюсы: Набор API походит на набор швейцарского ножа, расширяемость через хуки и плагины, стабильный интерфейс и активная разработка.
Минусы: Местами пустая документация; небольшие скрипты на основе Cement могут показаться несколько сложноватыми.
Cleo далеко не такой популярный фреймворк, как другие перечисленные здесь (всего около 400 звёзд на GitHub), и всё же мне удалось познакомиться с ним, когда я изучал, каким образом Poetry осуществляет форматирование вывода.
Так вот, Cleo — это один из проектов автора уже упомянутого Poetry, инструмента для управления зависимостями, virtualenv’ами и сборками приложений. Про Poetry на хабре уже не раз писали, а про его консольную часть — нет.
Парсер
Cleo, как и Cement, построен на объектных принципах, т.е. определение команд происходит через класс Command и его docstring, доступ к параметрам осуществляется через метод option(), и так далее. Кроме того, метод line(), который используется для вывода текста, поддерживает стили (т.е. цвета) и фильтрацию вывода на основании количества verbose-флагов из коробки. А ещё у cleo есть вывод таблиц. А ещё прогресс-бары. А ещё… В общем, смотрите:
Всё, что надо, это создать объект cleo.Application и потом передать ему команды в add_commands. Чтобы не повторяться при тестировании, всё это было перенесено из main в конструктор:
Тестирование
Для тестирования команд в Cleo есть CommandTester, который, как и все взрослые дяди фреймворки, перехватывает I/O и exit code:
Плюсы: объектная структура с наличием type hints, что упрощает разработку (т.к. многие IDE и редакторы имеют хорошую поддержку ООП-кода и модуля typing); хороший объём функционала по работе не только с аргументами, но и I/O.
Плюс-минус: свой параметр verbosity, который совместим только с I/O Cleo/CliKit. И хотя можно написать кастомный handler для модуля logging, его может быть сложно поддерживать вместе с развитием cleo.
Минусы: явно — личное мнение — молодое API: фреймворку не хватает другого «крупного» пользователя, кроме Poetry, а так Cleo развивается параллельно с развитием и под нужды его одного; местами документация устаревшая (например, уровни логирования теперь лежат не в модуле clikit, а в clikit.api.io.flags), да и в целом она скудна и не отражает всего API.
Cleo, в сравнении с Cement, больше сфокусирован на CLI, и он единственный, кто задумался о форматировании (скрытии стектрейса по умолчанию) исключений в выводе по умолчанию. Но он — снова личное мнение — проигрывает Cement’у в своей юности и стабильности API.
Мелкая питонячая радость #9: консольные приложения с человеческим лицом
Философы говорят, что людей нужно оценивать не по тому, как высоко они могут забраться, а по тому, как низко они могут пасть.
В мире есть много прекрасных разработчиков, которые могут выдавать эффектные алгоритмы, изящные архитектуры и прекрасный код. Но эти же программеры берут и пишут весьма посредственно организованный код какого-нибудь маленького консольного скрипта для расчета аналитики или патча данных в базе. Никакой разбивки на классы и функции, корявая передача аргументов, примитивный вывод малопонятной информации с помощью print()
Сегодня мы поговорим о том, как сделать лучше программы, на которые большинству плевать — одноразовые консольные утилитки и скрипты.
python-nubia
Библиотека от второй по значимости в мире корпорации добра — Facebook. Тамошних инженеров тоже порядком утомил хаос в консольных приложениях, поэтому они решили снабдить их интерактивным интерфейсом. Конечно же, текстовым.
Termgraph
Для того, чтобы сделать отрисовку того, что происходит в программе, есть либа Termgraph
Она позволяет делать такие вот картинки в терминале
Или такие, посложнее
Используя простой бар, можно показывать прогресс выполнения программы в виде красивой полоски — и это уже будет круто и куда приятней вывода непонятных значений принтом.
Fabric
Скрипты для выполнения на удаленных машинах — отдельная боль. Конечно, мы живем в веке DevOps, когда кучи задач решаются с помощью Salt и Ansible. Но бывает и такое, что нужно регулярно логиниться на кластер удаленных тачек и выполнять там пачки команд. Для этого и есть fabric
Fabric построен вокруг Paramiko и вообще вы можете выполнять команды с помощью этой низкоуровневой библиотеки общения по SSH. Но Fabric дает необходимый уровень абстракции, который позволяет делать понятным и легкоиспользуемым.
python-prompt-toolkit
Эта либа превращает простой скрипт в реально мощное консольное приложение.
Например, можно добавить строку запроса команд в текстовый интерфейс.
А после этого можно добавить историю использованных команд, прямо как в вашем терминале.
А можно сделать автоподсказки строк из истории ввода.
И научить автоподсказки определенным предустановленным командам.
Либа крайне простая и дает возможность сделать свой классный и полностью кастомизируемый интерфейс.
Зачем это?
Внимательное отношение к, казалось бы, одноразовым программам позволит вам писать не код на выброс, а консольные инструменты, которые легко можно использовать повторно потом. Это экономит время и, конечно же, делает работу с вашими скриптами намного приятней.
Создание интерфейсов командной строки (CLI) с помощью Fire в Python
Интерфейс командной строки (CLI), представляет собой способ взаимодействия с компьютерами с помощью текстовых команд.
Эти библиотеки могут помочь нам в написании сценариев CLI, предоставляя такие услуги, как параметры синтаксического анализа и флаги, для гораздо более продвинутых функций CLI.
В этой статье обсуждается библиотека Python Fire, написанная Google Inc., полезный инструмент для создания интерфейса командной строки с минимальным кодом.
Общая форма приложений CLI
Вы можете изменить поведение или вывод программы командной строки, предоставив ей список токенов или параметров, более известных как флаги. Давайте попробуем флаги команды ls :
Разница между коротким и длинным вариантами:
Интерфейс командной строки предоставляет пользователю простой способ настройки и запуска приложения из командной строки. Библиотека Python Fire позволяет легко добавить компонент обработки интерфейса командной строки в любой существующий скрипт Python.
Давайте посмотрим, как создать приложение из командной строки с помощью Python Fire.
Установка
Давайте продолжим и установим библиотеку, используя pip :
Python Fire работает с любым объектом Python, т.е. функциями, классами, словарями, списками и т. д. Давайте попробуем понять использование библиотеки Python Fire на некоторых примерах.
Создание приложения CLI с помощью Python Fire
Скажем, создадим сценарий fire_cli.py и поместим в него функцию:
При запуске этой программы в оболочке Python вывод будет:
Мы можем легко превратить этот скрипт в приложение CLI, используя Python Fire:
Вызов fire.Fire() превращает модуль т.е. fire_cli.py в приложение Fire CLI. Более того, функция greet_mankind() автоматически отображается как команда.
Теперь мы можем сохранить и запустить приведенный выше сценарий как CLI следующим образом:
В качестве напоминания давайте разберемся с вызовом:
Передача аргументов команде
Давайте создадим другое приложение CLI, которое принимает имя в качестве параметра и отображает настраиваемое приветственное сообщение:
Вот как мы можем запустить эту команду из командной строки:
Установка функции в качестве точки входа
С небольшими изменениями мы можем контролировать отображение функции greetings() в командной строке и установить ее как точку входа по умолчанию:
Вот как мы сейчас запустим команду:
Разбор аргументов
Вот как мы генерируем последовательность с помощью этой утилиты командной строки:
Вот результат со значением смещения 2:
Аргументы конструктора всегда передаются с использованием синтаксиса флага, тогда как аргументы другим методам или функциям передаются позиционно или по имени:
Флаги Fire
Это очень полезно для тестирования команд:
Вывод
В этой статье мы рассмотрели, как установить Python Fire, а также сгенерировать простые интерфейсы командной строки.