概要
repo-root ├ functions │ ├ func_a │ │ ├ main.py │ │ └ serverless.yml │ └ func_b │ ├ main.py │ └ serverless.yml ├ libs │ └ mod_c │ └ foo.py └ tox.ini
とあるServerlessのLamdaを管理するリポジトリがあります。
このリポジトリは(歴史的背景により)、それぞれ serverless.yml
を持つ複数のFunction(func_a, func_b)を持っています。
という条件の中で、func_a, func_bで共通の自作モジュール mod_c/foo.py
を読み込みたくていろいろやったメモです。
なにゆえ?
普通にこのままデプロイをかけると、 serverless.yml
があるディレクトリの外側にある libs
はzipされません=Lambdaが libs
を持たない状態になります。
そのため、Lambdaは
Unable to import module 'main': No module named 'libs'
というエラーを出すことになります。
本稿の目的はこれを回避することです。
本来なら /repo-root
に serverless.yml
を置くのがたぶん最適解です。
が、歴史的背景が色々あってその変更を掛けることができませんでした。
というお断りをしておきます…
説明すること
- Serverlessデプロイするまで
- PyCharm上で読み込めるようにする方法
- tox/pytestでテストできるようにする方法
1. Serverlessデプロイするまで
serverless-python-requirementsというプラグインを使用します。
serverless.yml
があるディレクトリで↓を実行してインストール。
$ sls plugin install -n serverless-python-requirements
インストール後、 serverless.yml
の plugins
内に vendor
オプションを追記することで libs
を含めることが可能になります。
service: xxxx-yyyy-zzzz frameworkVersion: "^1.xx.x" provider: ...(省略)... plugins: - serverless-python-requirements custom: pythonRequirements: vendor: ../../libs ...(省略)... functions: ...(省略)...
ただ、デプロイ後、 vendor
に指定したディレクトリ階層が1階層浅くなるトラップがあります。
デプロイ後、Lambdaの中は↓のようになります。
func_a ├ /bin ├ /redis ├ /requests ├ __init__.py ├ main.py ├ /mod_c └ ...
serverlessルートディレクトリ直下にimportしているライブラリが展開されます。
それによって何が起きるかというと、func_aおよびfunc_bの main.py
で
from libs.mod_c.foo import bar
のようにimportしている場合、
libs
が見つからないことによってLambdaの中で結局 Unable to import module
が出ます。
ソースコード上での解決方法は簡単で、 libs
を除けば動作します。
from mod_c.foo import bar
ただこの状態だとローカルでテストが動作しなくなったり、toxが回らなくなったりするので、
importに合わせて各種設定を変更することが必要になります。次節へ。
2. PyCharm上で読み込めるようにする方法
1の手順に沿ってimportを書き換えるとPyCharmが警告を出すので、Project Structureから libs
をSource Foldersとして登録します。
- PyCharmのProject Structure画面を開きます。
- [File] -> [Settings] -> [Project] -> [Project Structure]
- [Mark as:] から [Sources] をクリックしてから、
/libs
をクリックします。 /libs
のディレクトリが青色になったら [Apply] -> [OK]
これでPyCharm上では正しくimportができる状態になります。
3. tox/pytestでテストできるようにする方法
toxを使っている場合、まだimportが失敗するので修正する必要があります。
(本稿ではCircleCI+tox+pytestの使用を前提としています)
原因は libs
にpathが通っていないことなので、
テスト実行時の環境変数に PYTHONPATH
を追記します。
tox.ini
[testenv:py36] deps = pipenv commands = - pipenv install --dev pipenv run pytest setenv = PYTHONPATH = {toxinidir}/functions{:}{toxinidir}/libs ...
この例では、 /functions
と /libs
の2つのディレクトリにパスを通しています。
setenvについての詳細はこちら。名前の通り環境変数をセットするだけです。
{toxinidir}
はtox.iniがあるディレクトリを指す変数、
{:}
は複数変数の区切り文字(環境依存)を環境に合わせて変更してくれるものです。
toxの置換変数の詳細はこちらを参照。
{:}
でちょっとハマりました。
Windowsローカル環境で作業しているときに ;
で書いて、
それをCircleCI(つまりLinux)に出したら動かなくなって、区切り文字が環境依存であることに気づくまでに数十分かけてしまった…
直接 :
, ;
とか書かずにちゃんと {:}
を使いましょう。