Как создать удобный интерфейс командной строки из функций 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.
Оригинал