Статьи

Sigma IDE теперь поддерживает Python-серверные функции Lambda!

Думай без сервера, иди Pythonic — все в вашем браузере!

(Хорошо, эта новость устарела на несколько недель, но все же…)

Если вам нравится вся эта бессерверная «штука» , вы, возможно, заметили нас, печально известную группу в SLAppForge , болтающую о «бессерверной IDE». Да, мы работаем с Sigma IDE — первой в своем роде — уже довольно давно, получая неоднозначные отзывы от пользователей со всего мира.

В нашей стандартной форме обратной связи был задан вопрос: «Какой язык вы предпочитаете для разработки безсерверных приложений?» ; с параметрами Node , Java , Go , C # и блоком предложений. Удивительно (но, возможно, нет), ящик для предложений был самым популярным вариантом; и кроме двух, все другие «альтернативные» варианты были одним — Python .

Пользователь король; Python это так!

У нас даже было несколько пользователей, которые хотели отменить свою новую подписку, потому что Sigma не поддерживала Python, как они ожидали.

Итак, на одном из наших совещаний по дорожной карте была раскрыта вся история Python; и мы решили дать ему шанс.

Перед историей, некоторые кредиты в порядке.

Хасанги , один из наших бывших разработчиков, изначально отвечал за оценку возможности поддержки Python в Sigma. После того, как она ушла, я вступил во владение. Теперь, в этот триумфальный момент, я хотел бы поблагодарить вас, Хасанги, за то, что вы возглавили весь ход Python. ?

Чатура , еще один из наших бывших мастеров, занимался всей частью анализа среды NodeJS в среде IDE, используя Babel . Хотя у меня были некоторые уроки по абстрактным синтаксическим деревьям (AST) в моих лекциях по теории компилятора, именно после прохождения его кода я действительно «почувствовал» мощь AST. Так что это вам, Чатура, за то, что вы дали жизнь ядру нашей IDE — и сделали наше путешествие на Python намного, намного быстрее! ?

И спасибо, Мэтт, за filbert.js !

Работа Чатуры была потрясающей; все же, это было как, скажем, «вода в воде» (чёрт, что это за аналогия?). Другими словами, мы в основном анализировали (Node) JS-код внутри приложения ReactJS (да, JS).

Итак, естественно, наш первый вопрос — и вопрос на миллион долларов тогда — был: можем ли мы проанализировать Python внутри нашего приложения JS? И все ли у нас получается: рендеринг хороших всплывающих окон для вызовов API, автоматическое определение использования ресурсов, автоматическое создание разрешений IAM и т. Д.?

Хасанги уже filbert.js , производную от filbert.js , которая могла разобрать Python. К сожалению, вскоре мы с ней узнали, что он не может понять стандартный (и самый популярный) формат вызовов API AWS SDK, а именно именованные параметры :

1
2
3
4
5
s3.put_object(
  Bucket="foo",
  Key="bar",
  Body=our_data
)

Если бы мы вместо этого переключились на «свободный» формат:

1
2
3
4
boto.connect_s3() \
  .get_bucket("foo") \
  .new_key("bar") \
  .set_contents_from_string(our_data)

мы должны были бы переписать целую логику синтаксического анализа AST; возможно, совершенно новый интерпретатор AST для кода пользователя на основе Python. Мы не хотели так много приключений — по крайней мере, пока.

Доктор Ватсон, давай! (ОНО РАБОТАЕТ!!)

В один прекрасный вечер я продолжил играть с filbert.js . Взглянув на путь разбора, я заметил:

1
2
3
4
5
6
7
8
9
...
    } else if (!noCalls && eat(_parenL)) {
      if (scope.isUserFunction(base.name)) {
        // Unpack parameters into JavaScript-friendly parameters, further processed at runtime
        var pl = parseParamsList();
...
        node.arguments = args;
      } else node.arguments = parseExprList(_parenR, false);
...

Подожди … они намеренно пропускают названные параманы?

Что если я закомментирую эту проверку состояния?

1
2
3
4
5
6
7
8
9
...
    } else if (!noCalls && eat(_parenL)) {
//    if (scope.isUserFunction(base.name)) {
        // Unpack parameters into JavaScript-friendly parameters, further processed at runtime
        var pl = parseParamsList();
...
        node.arguments = args;
//    } else node.arguments = parseExprList(_parenR, false);
...

А потом … ну, я просто не мог поверить своим глазам.

Две строки закомментированы, и это уже начало работать!

Это был мой момент истины. Я собираюсь привести Питона в Сигму. Не важно что.

Я просто не могу сдаться. Не после того, что я только что увидел.

Великий Рефактор

Когда мы родили Sigma , предполагалось, что это скорее PoC — чтобы доказать, что мы можем выполнять разработку без сервера без локальной настройки dev, панели мониторинга и документации, а также множества конфигураций.

В результате, расширяемость и настраиваемость тогда были не совсем в нашей тарелке. Все было в значительной степени связано с AWS и NodeJS. (И подумать, что мы все еще называем их «JavaScript» файлами… ?)

Итак, начиная с парсера, мой нетерпеливый палец ожидал грузовик рефакторинга. Начав с абстракции Language , я постепенно прошел путь через редактор и рендеринг всплывающих окон, генерацию фрагментов кода, создание артефактов, развертывание и так далее.

(Я сталкивался с подобной проблемой, когда привносил поддержку Google Cloud в Sigma, поэтому у меня была небольшая идея о том, как подойти ко всему этому.)

Тестовая среда

С тех пор, как Chathura — наш бывший мастер Adroit — реализовал его в одиночку, тестовая среда стала первостепенной среди возможностей Sigma. Если бы Python оказал влияние, нам также понадобилась бы тестовая среда для Python.

Здесь все становится немного веселее; благодаря своей несколько неловкой истории , Python имеет два разных вкуса: 2.7 и 3.x. Таким образом, по сути, нам нужно поддерживать две разные среды — по одной для каждой версии — и вызывать правильную среду, основанную на настройке времени выполнения текущей функции.

(Ну, на самом деле, на самом деле у нас есть такая же проблема и для NodeJS (6.x, 8.x, 10.x,…), но, очевидно, мы не думали об этом, и это не вызвало каких-либо также большие проблемы! ?)

pip install

Нам также потребовалась новая концепция для обработки зависимостей Python ( pip ). К счастью, pip уже был доступен в контейнере Lambda, поэтому установка не была главной проблемой; реальная проблема заключалась в том, что их нужно было извлекать прямо в корневой каталог проекта в тестовой среде. (В отличие от npm , где все идет в хороший и управляемый каталог node_modules — так что мы можем извлечь и очистить вещи за один раз.) К счастью, немного (надеюсь стабильного!) Кода помогло нам пройти.

Жизнь без __init__.py

Все шло гладко, пока …

1
from subdirectory.util_file import util_func
1
2
3
File "/tmp/pypy/ding.py", line 1, in <module>
    from subdirectory.util_file import util_func
ImportError: No module named subdirectory.util_file

Произошло только в Python 2.7, поэтому его было легко понять — нам нужен был __init__.py чтобы пометить его как импортируемый модуль .

Вместо того, чтобы полагаться на пользователя, чтобы создать его, мы решили сделать это сами; всякий раз, когда создается файл Python, мы теперь гарантируем, что __init__.py также существует в его родительском каталоге; создание пустого файла, если он отсутствует.

Черт возьми, бревна — они неисправны!

SigmaTrail — еще одна жемчужина нашей Sigma IDE. При написании Lambda по частям, это действительно помогает иметь панель журналов рядом с окном вашего кода. Кроме того, что хорошего в тестовой среде, если вы не видите журналы того, что вы только что запустили?

Еще раз, Чатура была вдохновителем SigmaTrail. (Да, в конце концов, он написал более половины IDE!) Его код скромно анализировал журналы CloudWatch и объединял их с LogResult возвращаемыми LogResult Lambda ; поэтому я подумал, что могу просто подключить его к среде исполнения Python, бездельничать и наслаждаться видом.

Я был ужасно неправ.

Поднимите руку, те, кто использует logging в Python!

В Node единственный (очевидный) способ получить что-то в консоли (или технически stdout ) — это один из этих вызовов console.{level}() .

Но Python дает вам варианты — скажем встроенная print , против модуля logging .

Если вы идете с logging , вы должны:

  1. import logging ,
  2. создайте Logger и установите уровень его обработчика — если вы хотите создавать журналы отладки и т. д.
  3. вызовите соответствующий метод logger.{level} или logging.{level} , когда дело доходит до этого

Да, на лямбде ты тоже мог

1
context.log("your log message\n")

если ваш context лежит в стороне — все равно, вам понадобится этот дополнительный \n в конце, чтобы он заносил материал в свою собственную строку.

Но гораздо проще просто print("your log message") — черт, если вы пользуетесь 2.x, вам даже не нужны эти скобки!

Повезло тебе.

Но это создает серьезную проблему для SigmaTrail.

Все эти печатные строки, в одной куче текста. Тьфу.

Для console.log в Node Lambda автоматически добавляет в каждый журнал текущую временную метку и идентификатор запроса ( context.awsRequestId ). Чатура использовал эти данные, чтобы отделить строки журнала и отобразить их как хороший след в SigmaTrail.

Но теперь с print префиксов не было. Ничего не было подобрано.

Исправить это, пожалуй, самая сложная часть работы. Я потратил около недели, пытаясь понять код (благодаря рабочему шаблону); а затем еще одну неделю, пытаясь исправить это, не нарушая поток NodeJS.

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

«Реальное» время выполнения: возиться с PYTHONPATH

После того, как тестовая среда ожила, я подумал, что все мои неприятности прошли. « Устаревшая » сборка ( CodeBuild -driven) и развертывание были довольно просты для рефакторинга, поэтому я был рад — и даже собирался поднять зеленый флаг для первоначального выпуска.

Но я делал серьезную ошибку.

Я не осознавал этого, пока не вызвал развернутую Lambda через триггер API-шлюза.

1
{"errorMessage": "Unable to import module 'project-name/func'"}

Что за…

1
Unable to import module 'project-name/func': No module named 'subdirectory'

Где находится модуль?

Тесты работают отлично! Так почему не производство?

После нескольких случайных экспериментов и проверки пакетов Python, сгенерированных другими платформами, я понял, что причиной является структура нашего архива развертывания (zipfile).

Все остальные пакеты имеют функции на верхнем уровне, но у нас они есть в каталоге (наш «корень проекта»). До сих пор это не было проблемой для NodeJS; но теперь, независимо от того, как я определяю путь обработчика, среда выполнения Python AWS не может его найти!

Изменение структуры проекта было бы катастрофой; слишком большой риск взлома, ну почти все остальное. Более безопасной идеей было бы переопределить один из доступных параметров — например, переменную среды, специфичную для Python, — чтобы каким-то образом подключить наш корневой каталог к PYTHONPATH .

Простой взлом

Да, ответ прямо здесь, PYTHONPATH ; но я не хотел отвергать передачу от Богов AWS, просто так.

Поэтому я начал копаться во время выполнения Lambda ( да, опять же ), чтобы найти, что я мог бы использовать:

1
2
3
4
import os
 
def handler(event, context):
    print(os.environ)

дает:

01
02
03
04
05
06
07
08
09
10
{'PATH': '/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin',
'LD_LIBRARY_PATH': '/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib',
...
'LAMBDA_TASK_ROOT': '/var/task',
'LAMBDA_RUNTIME_DIR': '/var/runtime',
...
'AWS_EXECUTION_ENV': 'AWS_Lambda_python3.6', '_HANDLER': 'runner_python36.handler',
...
'PYTHONPATH': '/var/runtime',
'SIGMA_AWS_ACC_ID': 'nnnnnnnnnnnn'}

LAMBDA_RUNTIME_DIR выглядел как многообещающая альтернатива; но, к сожалению, AWS отверг это. Каждое развертывание завершилось неудачно с длинной средней ошибкой:

1
2
3
Lambda was unable to configure your environment variables because the environment variables
you have provided contains reserved keys that are currently not supported for modification.
Reserved keys used in this request: LAMBDA_RUNTIME_DIR

Тем не менее, это расследование выявило нечто важное: PYTHONPATH в Lambda не был таким сложным или многолюдным, как я себе представлял.

1
'PYTHONPATH': '/var/runtime'

И, очевидно, внутренние агенты Лямбды не слишком возятся с этим. Просто вытащите и прочитайте /var/runtime/awslambda/bootstrap.py и убедитесь сами. ?

PYTHONPATH работает. Уф.

В итоге я переопределил PYTHONPATH , чтобы включить корневой каталог проекта, /var/task/project-name (в дополнение к /var/runtime ). Если вы хотите, чтобы там появилось что-то еще, не стесняйтесь изменять переменную окружения — но оставьте наш фрагмент позади!

С другой стороны, это должно означать, что мои функции должны работать и на других платформах — поскольку PYTHONPATH должен быть кроссплатформенным.

Google Cloud для Python — скоро!

С несколькими настройками мы могли бы заставить Python работать и над облачными функциями Google. Это уже в нашей постановочной среде; и как только он выйдет в эфир, вам, ребята из GCP, повезет! ?

Еще долгий путь … Но Python уже жив и здоров!

Вы можете наслаждаться написанием функций Python в нашей текущей версии IDE. Просто нажмите кнопку со знаком «плюс» (+) в правом верхнем углу панели «Проекты» , выберите « Новый файл функций Python» (или « Новый файл Python» ) и позвольте магии начаться!

И, конечно же, давайте — и весь мир — узнаем, как это происходит!

Смотрите оригинальную статью здесь: Sigma IDE теперь поддерживает Python-функции без сервера!

Мнения, высказанные участниками Java Code Geeks, являются их собственными.