Сдруживаем Python и Bash: библиотеки smart-env и python-shell

в 15:28, , рубрики: bash, devops, environment variables, python, python2, python3

Доброго времени суток всем.

На сегодняшний день Python является одним из наиболее используемых языков в сфере создания не только непосредственно программных продуктов, но также обеспечения их инфраструктуры. Вследствие этого многим девопсам, по их воле или против оной, пришлось учить новый язык для последующего использования в качестве дополнения к старым добрым Bash-скриптам. Однако Bash и Python исповедуют различные подходы к написанию кода и имеют определенные особенности, в виду чего портирование Bash-скриптов на «змеиный язык» иногда оказывается ёмкой и далеко не тривиальной задачей.

Чтобы упростить жизнь девопсам, создано и продолжает создаваться много полезных библиотек и утилит на Python. Данная статья описывает сразу две новых библиотеки, созданные автором сего поста — smart-env и python-shell — и призванные избавить девопса от необходимости уделять много внимания тонкостям работы с Python, оставляя простор для более интересных задач. Сфера деятельности библиотек — переменные окружения и запуск внешних утилит.

Кого заинтересовало, прошу под кат.

Новые «велосипеды»?

Казалось бы, зачем создавать новые пакеты для достаточно обыденных операций? Что мешает использовать напрямую os.environ и subprocess.<метод или класс на ваш вкус>?

Доказательства в пользу каждой из библиотек приведу отдельно.

Библиотека smart-env

Перед тем, как писать собственное детище, полезно полезть в Интернет и поискать готовые решения. Конечно, есть риск не найти то, что нужно, но это, скорее, «страховой случай». Как правило, подход срабатывает и экономит кучу времени и сил.

По результатам поиска было выявлено следующее:

  • есть пакеты, действительно оборачивающие вызовы к os.environ, однако при этом требующие кучу отвлекающих действий (создание экземпляра класса, спец-параметры в вызовах и пр.);
  • есть неплохие пакеты, которые, однако, жестко завязаны на определенную экосистему (в основном, на веб-фреймворки вроде Django) и потому без напильника совсем не универсальные;
  • есть редкие попытки сделать что-то новое. Например, добавить типизацию и явно парсить значения переменных путем вызова методов вида
    get_<typename>(var_name)

    Или вот еще одно решение, которое, однако, не поддерживает ныне опальный Python 2 (на котором, несмотря на официальный R.I.P., все еще остаются горы написанного кода и целые экосистемы);

  • есть школьно-студенческие поделки, вообще непонятно зачем оказавшиеся в апстримном PyPI и только создающие проблемы с именованием новых пакетов (в частности, название «smart-env» — вынужденная мера).

И этот список можно продолжать долго. Однако и приведенных выше пунктов хватило, чтобы загореться идеей сделать нечто удобное и универсальное.

Требования, которые ставились перед написанием smart-env:

  • Максимально простая схема использования
  • Легко конфигурируемая поддержка типизации данных
  • Совместимость с Python 2.7
  • Хорошее покрытие кода тестами

В конечном итоге, все это удалось реализовать. Вот пример использования:

from smart_env import ENV

print(ENV.HOME)  # Equals print(os.environ['HOME'])

# assuming you set env variable MYVAR to "True"

ENV.enable_automatic_type_cast()

my_var = ENV.MY_VAR  # Equals boolean True

ENV.NEW_VAR = 100  # Sets a new environment variable

Как видно из примера, для работы с новым классом достаточно его импортировать (создавать экземпляр не нужно — минус лишнее действие). Доступ к любой переменной окружения достигается путем обращения к ней как к переменной класса ENV, что, фактически, делает этот класс интуитивно понятной обёрткой нативного системного окружения, параллельно превращая его в возможный вариант объекта конфигурации практически любой системы (похожий подход, например, достигается в Django, только там конфигурационным объектом выступает непосредственно модуль/пакет settings).

Включение/выключение режима поддержки автоматической типизации достигается использованием двух методов — enable_automatic_type_cast() и disable_automatic_type_cast(). Это может быть удобно, если в переменной окружения лежит сериализованный JSON-подобный объект или даже просто булевая константа (явное прописывание переменной DEBUG в Django путем сравнения переменной окружения с «допустимыми» строками — один из часто встречающихся случаев). Но теперь нет нужды явно конвертировать строки — большая часть необходимых действий уже заложена в недрах библиотеки и только ждет сигнала к действию. :) В целом же типизация работает прозрачно и поддерживает почти все имеющиеся встроенные типы данных (не тестировались frozenset, complex и bytes).

Требование поддержки Python 2 было реализовано практически без жертв (отказ от typing и некоторых «сахарных конфеток» последних версий Python 3), в частности, благодаря вездесущему six (для решения проблем использования метаклассов).

Но есть и немного ограничений:

  • Поддержка Python 3 подразумевает версию 3.5 и выше (их наличие в вашем проекте — результат либо лени, либо отсутствия необходимости в улучшениях, т.к. сложно придумать объективную причину, почему вы до сих пор сидите на 3.4);
  • В Python 2.7 библиотека не поддерживает десериализацию литералов множеств. Описание тут. Но, если кто-нибудь захочет реализовать — welcome:);

Библиотека также исповедует механизм исключений на случай ошибок парсинга. Если строку не удалось распознать ни одним из имеющихся анализаторов, значение остается строковым (скорее, из соображений удобства и обратной совместимости с привычной логикой работы переменных в Bash).

Библиотека python-shell

Теперь расскажу о второй библиотеке (описание недостатков имеющихся аналогов опущу — оно похоже на описанное для smart-env. Аналоги — тут и тут).

В целом, идея реализации и требования к ней аналогичны описанным для smart-env, что видно из примера:

from python_shell import Shell

Shell.ls('-l', '$HOME')  # Equals "ls -l $HOME"

command = Shell.whoami()  # Equals "whoami"
print(command.output)  # prints your current user name

print(command.command)  # prints "whoami"
print(command.return_code)  # prints "0"
print(command.arguments)  # prints ""

Shell.mkdir('-p', '/tmp/new_folder')  # makes a new folder

Идея такова:

  1. Единый класс, олицетворяющий Bash в мире Python;
  2. Каждая Bash-команда вызывается как функция класса Shell;
  3. Параметры вызова каждой функции далее пробрасываются в вызов соответствующей команды Bash;
  4. Каждая команда выполняется «здесь и сейчас» в момент ее вызова, т.е. работает синхронный подход;
  5. есть возможность получить доступ к выхлопу команды в stdout, а также код ее возврата;
  6. Если команда отсутствует в системе — бросается исключение.

Как и в случае со smart-env, обеспечена поддержка Python 2 (правда, жертвенной крови потребовалось немного больше) и отсутствует поддержка Python 3.0-3.4.

Планы по развитию библиотек

Использовать библиотеки можно уже сейчас: обе выложены на официальный PyPI. Исходники доступны на Github (см. ниже).

Обе библиотеки будут развиваться с учетом фидбека, собранного от заинтересовавшихся. И, если в smart-env, может, и сложно придумать разнообразие новых фич, то в python-shell точно есть еще что добавить:

  • поддержка неблокирующих вызовов;
  • возможность интерактивного общения с командой (работа с stdin);
  • добавление новых свойств (например, property для получения выхлопа из stderr);
  • реализация каталога доступных команд (для использования с функцией dir());
  • и т.д.

Ссылки

  1. Библиотека smart-env: Github и PyPI
  2. Библиотека python-shell: Github и PyPI
  3. Телеграм-канал обновлений библиотек

UPD 23.02.2020:
* Репозитории перенесены, соответствующие ссылки обновлены
* Версия python-shell==1.0.1 готовится к выходу 29.02.2020. Среди изменений — поддержка автокомплита команд и команды dir(Shell), запуск команд с Python-невалидным идентификатором, исправление багов.

Автор: Alex Sokolov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js