Как создать удобный интерфейс командной строки из функций Pure Python

Как создать удобный интерфейс командной строки из функций Pure Python

12 марта 2022 г.

*DynaCLI (Dynamic CLI) — это облачная библиотека с открытым исходным кодом для преобразования чистых функций Python в команды Linux Shell. В этой статье объясняется, как DynaCLI делает написание интерфейсов командной строки на Python простым и эффективным, используя в качестве примера функцию для создания QR-кода, который записывает статус вакцинации человека. *


Это продолжение статьи [Как писать удобные интерфейсы командной строки в Python] (https://towardsdatascience.com/how-to-write-user-friendly-command-line-interfaces-in-python-cc3a6444af8e ), в котором описывается, как использовать различные библиотеки Python, такие как argparse, Click, [Typer] (https://typer.tiangolo.com/), [docopt] (https://github.com/docopt/docopt) и [Fire] (https://github. com/google/python-fire) для создания приложений CLI. Чтобы понять мотивы и варианты использования DynaCLI, прочитайте интервью для среды. Чтобы узнать о различиях между DynaCLI и альтернативами, см. раздел [DynaCLI и альтернативы] (https://bstlabs.github.io/py-dynacli/advanced/why/).


Мотивация


Основная идея DynaCLI состоит в том, чтобы максимально ускорить и автоматизировать процесс создания приложений CLI, сосредоточившись исключительно на коде Python. Аргументы функций преобразуются в команды CLI, а DynaCLI генерирует справочные сообщения из строк документации функций Python.


Мы продемонстрируем этот подход, сгенерировав QR-код, который указывает статус вакцинации человека.


Звучит интересно? Приступаем к изучению…


Обычный процесс построения интерфейса командной строки


Построение CLI, как правило, представляет собой двухэтапный процесс: во-первых, написание основного кода, а во-вторых, разработка предварительно определенного CLI в виде набора аргументов, как показано ниже.


Давайте начнем с просмотра исходного кода, ссылка статья).


В этом фрагменте они сначала пишут методы для реализации основного кода.


```питон


из классов данных импортировать класс данных, поле, asdict


запросы на импорт


импортный шутил


из даты и времени импортировать дату и время


класс ValidationException (Исключение):


проходить


@dataclass


класс вакцинации:


производитель: ул.


дата: ул.


деф post_init(сам):


пытаться:


date = datetime.strptime(self.date, "%Y-%m-%d")


кроме исключения:


поднять ValidationException("Дата прививки должна быть в формате ГГГГ-ММ-ДД.")


если дата> datetime.today ():


поднять ValidationException("Дата прививки не должна быть датой в будущем.")


если self.manufacturer.lower() отсутствует в ["pfizer","moderna","astrazeneca","janssen","sinovac"]:


поднять ValidationException("Ваш производитель вакцины не одобрен."


@dataclass


класс QRCode:


название: ул.


рождение: ул.


вакцина: список[Вакцинация] = поле(default_factory=список)


def generate_qr_code (qr_code):


код = asdict(qr_code)


res = request.get(f"http://api.qrserver.com/v1/create-qr-code/?data={code}", stream=True)


если res.status_code == 200:


с open("qr_code.png", "wb") как f:


res.raw.decode_content = Истина


Shutil.copyfileobj(res.raw, f)


вернуть «QR-код сгенерирован».


еще:


поднять исключение («QR-код не может быть сгенерирован с помощью API генератора QR-кода».)


Далее следует код, необходимый для создания CLI. В этом примере используется библиотека argparse (фрагмент кода из указанной статьи):


```питон


деф основной():


parser = argparse.ArgumentParser(description="Создайте QR-код вакцинации.")


parser.add_argument("-n", "--name", type=str, help="Ваше имя", required=True)


parser.add_argument("-b", "--birth", type=str, help="Ваш день рождения в формате ГГГГ-ММ-ДД", required=True)


parser.add_argument("-m", "--manufacturer", type=str, nargs="+", help="Производитель вакцины", required=True, selections=[


"пфайзер", "модерна", "астразенека", "янссен", "синовак"])


parser.add_argument("-d", "--date", type=str, nargs="+", help="Дата прививки", required=True)


аргументы = парсер.parse_args()


если len(args.manufacturer) != len(args.date):


регистрация.ошибка(


«Количество производителей вакцин не соответствует количеству дат вакцин».


выход(1)


qr_code = QRCode(имя=args.name, рождение=args.birth, вакцина=[Вакцинация(args.manufacturer[i], args.date[i]) для i в диапазоне(len(args.date))])


generate_qr_code (qr_code)


если name == "main":


пытаться:


основной()


кроме ValidationException как e:


регистрация.ошибка(е)


кроме Исключения как e:


logging.exception(e)


Проектирование CLI с DynaCLI


С DynaCLI мы можем пропустить вторую часть, разработав наши функции так, чтобы они были совместимы с CLI. Функционально основная логика та же. Чтобы продемонстрировать различия, мы обновим исходный код, как показано ниже.


Перед этим просто быстро установите DynaCLI, чтобы подготовиться:
pip3 install dynacli


Реструктуризация


Прежде всего, мы хотели бы реструктурировать код. Думая о дизайне CLI, должен быть ./qr-code, затем набор функций green-badge (фактический пакет Python), который предназначен для хранения всех команд, за которым следует generate для вывода фактических QR-кодов. :


```javascript


$ дерево green_badge -I pycache


```javascript


green_badge


├── generate.py


└── init.py


Из исходного кода мы знаем, что производители вакцин — это ограниченный набор компаний; такая информация хорошо подходит для типа Enum.


```питон


из enum импортировать Enum


производитель класса (перечисление):


пфайзер = 1


модерна = 2


астразенека = 3


Янссен = 4


синовак = 5


В качестве альтернативы мы можем использовать Literal. Я собираюсь использовать здесь Enum вместо Literal. Выбрав этот подход, мы добавим некоторый защитный контроль над входными данными, поскольку Enum будет автоматически проверять данные без какого-либо избыточного пользовательского валидатора.


Поэтому нам больше не нужна проверка производителя внутри __post_init__, и мы можем ее удалить. Идя дальше, вся цель @dataclass здесь заключается в проверке, поэтому мы можем заменить его на TypedDict.


Наш обновленный код файла generate.py выглядит следующим образом:


```питон


класс Вакцинация (TypedDict):


дата: дата


производитель: Производитель


класс QRCode (TypedDict):


название: ул.


рождение: ул.


прививки: список[Прививки]


Здание командной строки


Второй большой шаг — спроектировать фактическую функцию generate таким образом, чтобы она принимала всю необходимую информацию в качестве аргументов:


```питон


def generate(first_name: str, last_name: str, Birthday_date: str, **прививки: Производитель) -> Нет:


Сгенерируйте QR-код вакцинации.


Аргументы:


first_name (str): имя привитого человека


last_name (str): фамилия вакцинированного человека


birth_date(str): день рождения привитого в формате ГГГГ-ММ-ДД


**прививки (производитель): информация о прививках как дата=производитель


Возврат: Нет


TODO: обрабатывать повторяющиеся или неправильные даты.


qr_code = QRCode(


имя=f"{имя} {фамилия}",


рождение=дата_рождения,


прививки=[


Вакцинация(производитель=производитель, дата=дата-время.strptime(дата, "%Y-%m-%d"))


на дату производитель в вакцинациях.items()


разрешение = запросы. получить (


f"http://api.qrserver.com/v1/create-qr-code/?data={qr_code}", stream=True


если res.status_code != 200:


поднять исключение («QR-код не может быть сгенерирован с помощью API генератора QR-кода».)


с open("qr_code.png", "wb") как f:


res.raw.decode_content = Истина


Shutil.copyfileobj(res.raw, f)


print("QR-код сгенерирован.")


В идеале он должен выводить красивое сообщение об ошибке, если одна из пар прививок содержит неправильную или дублирующую дату , но для простоты мы просто пропустим этот шаг.


Основное изменение заключается в том, что мы собираемся принимать дату и имя производителя в виде пар ключ-значение. Это довольно интуитивно, не так ли?



Вызов CLI будет выглядеть примерно так:
./qr-code green-badge generate John Doe 1989-10-24 2021-01-01=pfizer 2021-06-01=pfizer


Следующее важное отличие состоит в том, что DynaCLI заполняет справочные сообщения из строк документации в методах, содержащих объяснение аргументов. …Вы правы, это вполне по-питоновски; если объяснения уже добавлены один раз, почему бы не использовать ту же информацию для создания справочных сообщений в CLI.


Остальная часть кода такая же — функционально логика кода не изменилась.


Точка входа CLI


Теперь, в качестве последнего шага, мы создаем точку входа CLI с помощью DynaCLI.


Мы уже предоставили скрипт начальной загрузки, все, что вам нужно, это указать путь для команды dynacli:


```javascript


$ dynacli init qr-code path=.


```javascript


Успешно создан qr-код точки входа CLI в /home/ssm-user/OSS/medium-articles/how_to_convert_python_functions/code


Он заполнит скрипт qr-кода некоторым шаблонным стартовым кодом, вам нужно просто изменить закомментированные части, чтобы получить настраиваемую версию вашего CLI:


```питон


!/usr/bin/env python3


Сценарий начальной загрузки DynaCLI # Измени меня


импорт ОС


импорт системы


от ввода импорта Final


из основного импорта dynacli


cwd = os.path.dirname(os.path.realpath(file))


version: Final[str] = "0.0.0" # Измените меня, чтобы определить свою собственную версию


search_path = [cwd] # Измените меня, если у вас другой путь; вы можете добавить несколько путей поиска


sys.path.extend (путь_поиска)


root_packages = ['cli.dev', 'cli.admin'] # Измените меня, если у вас есть предопределенное имя корневого пакета


main(search_path, root_packages) # Раскомментируйте, если у вас определены root_packages


основной (путь_поиска)


Измените закомментированные разделы, удалите лишние комментарии, и вы готовы к работе:


```питон


!/usr/bin/env python3


Образец QR-генератора


импорт ОС


импорт системы


из основного импорта dynacli


cwd = os.path.dirname(os.path.realpath(file))


версия = '1.0'


search_path = [cwd]


sys.path.extend (путь_поиска)


основной (путь_поиска)


Как вы, должно быть, уже заметили, нет предварительной обработки CLI, добавления аргументов, обратного вызова версии и т. д. Все предельно просто Python. DynaCLI получает версию из version и имя CLI из строки документации `qr_code`.


Вот и все — изменения внесены, и мы готовы запустить CLI.


Получение справки:


```javascript


$ ./qr-код -h


использование: qr-код [-h] [-v] {зеленый значок} ...


Образец QR-генератора


позиционные аргументы:


{зеленый значок}


green-badge Создать зеленый значок


необязательные аргументы:


-h, --help показать это справочное сообщение и выйти


-v, --version показать номер версии программы и выйти


Справочное сообщение green-badge исходит из файла пакета __init__.py:


```javascript


$ кошка green_badge/init.py


```javascript


Создать зеленый значок


```javascript


версия = "2.0"


Получение версии самого CLI:


```javascript


$ ./qr-код --версия


```javascript


qr-код - v1.0


Теперь давайте подумаем о другой ситуации, когда вы портируете уже разработанный пакет для предоставления через интерфейс командной строки, и у него установлена ​​собственная версия v2.0. Должен ли он связываться с версией CLI? Не идеально иметь одну версию для пакета и интерфейса командной строки.


С DynaCLI вы можете управлять версиями своих пакетов и даже модулей.


Опять же, это довольно Pythonic: просто добавьте __version__ в пакет __init__.py и в сам модуль generate.py.


```javascript


$ ./qr-код зеленый значок --версия


```javascript


зеленый значок qr-кода - v2.0


``` ударить


$ ./qr-code green-badge generate --version


```javascript


Создание зеленого значка qr-кода - v3.0


DynaCLI обнаруживает «generate.py» и функцию «generate» в нем. Интерфейс командной строки предоставляет только общедоступные имена, а функция docstring используется для регистрации справочного сообщения.


Здесь мы хотели бы подчеркнуть, что с DynaCLI нет необходимости начинать писать что-то с нуля и переделывать весь код. Все, что вам нужно, это импортировать уже существующую функциональность в промежуточное представление, как мы это сделали в generate.py, и зарегистрировать ее в CLI.


Это эффективно соответствует [принципу открытости/закрытости] (https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle), согласно которому исходный код закрыт для модификации, но открыт для расширения с помощью интерфейса командной строки.


Наша окончательная версия generate.py выглядит так:


```питон


импортный шутил


запросы на импорт


с даты импорта даты и времени, дата и время


от ввода импорта Literal, TypedDict


из enum импортировать Enum


версия = "3.0"


производитель класса (перечисление):


пфайзер = 1


модерна = 2


астразенека = 3


Янссен = 4


синовак = 5


класс Вакцинация (TypedDict):


дата: дата


производитель: Производитель


класс QRCode (TypedDict):


название: ул.


рождение: ул.


прививки: список[Прививки]


def generate(first_name: str, last_name: str, Birthday_date: str, **прививки: Производитель) -> Нет:


Сгенерируйте QR-код вакцинации.


Аргументы:


first_name (str): имя привитого человека


last_name (str): фамилия вакцинированного человека


birth_date(str): день рождения привитого в формате ГГГГ-ММ-ДД


**прививки (производитель): информация о прививках как дата=производитель


Возврат: Нет


TODO: обрабатывать повторяющиеся или неправильные даты.


qr_code = QRCode(


имя=f"{имя} {фамилия}",


рождение=дата_рождения,


прививки=[


Вакцинация(производитель=производитель, дата=дата-время.strptime(дата, "%Y-%m-%d"))


на дату производитель в вакцинациях.items()


разрешение = запросы. получить (


f"http://api.qrserver.com/v1/create-qr-code/?data={qr_code}", stream=True


если res.status_code != 200:


поднять исключение («QR-код не может быть сгенерирован с помощью API генератора QR-кода».)


с open("qr_code.png", "wb") как f:


res.raw.decode_content = Истина


Shutil.copyfileobj(res.raw, f)


print("QR-код сгенерирован.")


Вы можете получить справку о команде (на самом деле это наша функция Python), используя:


``` ударить


$ ./qr-код сгенерировать зеленый значок -h


использование: qr-код green-badge generate [-h] [-v] имя_имя_фамилия_дата_рождения [прививки <имя>=<значение> ...]


позиционные аргументы:


first_name имя привитого человека


last_name фамилия вакцинированного лица


рождения_дата день рождения привитого в формате ГГГГ-ММ-ДД


прививки <имя>=<значение>


информация о вакцинации как дата=производитель ['pfizer', 'moderna', 'astrazeneca', 'janssen', 'sinovac']


необязательные аргументы:


-h, --help показать это справочное сообщение и выйти


-v, --version показать номер версии программы и выйти


Таким образом, основные моменты использования DynaCLI в этом образце включают:


  • Мы не добавляли и не регистрировали никаких справочных сообщений — они были взяты из строк документации функций.

  • DynaCLI обнаруживает **kwargs и регистрирует их как пару <name>=<value>.

  • Никакой специальной предварительной обработки CLI, добавления аргументов или обратных вызовов версии не требовалось.

Теперь пришло время запустить команду и сгенерировать QR-код с предоставленными аргументами:


``` ударить


$ ./qr-code green-badge generate Шако Рзаев 1989-10-24 2021-01-01=pfizer 2021-06-01=pfizer


QR-код сгенерирован.



Вот и все. Мы сосредоточились только на основных функциях, упростили основные функции приложения CLI и изменили его, чтобы сделать его более удобным для пользователя. Вот какими должны быть CLI!


Исходный код: how_to_convert_python_functions/code


DynaCLI — это предложение с открытым исходным кодом от BST LABS. Наша цель — помочь организациям реализовать весь потенциал облачных вычислений с помощью ряда предложений с открытым исходным кодом и коммерческих предложений. Мы наиболее известны благодаря CAIOS, облачной операционной системе искусственного интеллекта, платформе разработки с технологией Infrastructure-from-Code. BST LABS является подразделением разработки программного обеспечения BlackSwan Technologies.



Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE