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

Доброї доби всім.

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

Щоб спростити життя девопсам, створено та продовжує створюватися багато корисних бібліотек та утиліт на Python. Ця стаття описує відразу дві нові бібліотеки, створені автором цього посту. smart-env и оболонка python — і покликані позбавити девопса необхідності приділяти багато уваги тонкощам роботи з Python, залишаючи простір для цікавіших завдань. Сфера діяльності бібліотек - змінні оточення та запуск зовнішніх утиліт.

Кого зацікавило, прошу під кат.

Нові "велосипеди"?

Здавалося б, навіщо створювати нові пакети для звичайних операцій? Що заважає використовувати безпосередньо os.environ та subprocess.<метод чи клас на ваш смак>?

Докази на користь кожної бібліотеки наведу окремо.

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

Перед тим, як писати власне дітище, корисно полізти до Інтернету та пошукати готові рішення. Звичайно, є ризик не знайти те, що потрібно, але це, скоріше, «страховий випадок». Як правило, підхід спрацьовує та економить купу часу та сил.

За результатами пошуку було виявлено таке:

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

    або ось ще одне рішення, яке, однак, не підтримує нині опальний Python 2 (на якому, незважаючи на офіційний RIP, все ще залишаються гори написаного коду та цілі екосистеми);

  • є шкільно-студентські вироби, які взагалі незрозуміло навіщо опинилися в апстрімному 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-невалідним ідентифікатором, виправлення багів.

Джерело: habr.com

Додати коментар або відгук