labunix's blog

labunixのラボUnix

python-lambda-local を試してみる

■python-lambda-local を試してみる
 余分な工程を減らすことが目的
 後でgit管理出来るよう、PEP 8に準拠したソースをローカルに保持しておく

 コードを書く←ここ
 Zipでひとつにまとめてアップロード
 サンプルイベントをインプットにして Lambda 関数をテスト実行
 CloudWatch Logs でログを確認してデバッグ

■まずは仮想環境(venv)に入る

$ mkdir lambda-local && cd lambda-local
$ python3 -m venv venv
$ source venv/bin/activate

$ pip --version
pip 23.0.1 from /home/bookworm/lambda-local/venv/lib/python3.11/site-packages/pip (python 3.11)

■python-lambda-local のインストール

$ pip install python-lambda-local
$ python-lambda-local -h | lsec usage
usage: python-lambda-local [-h] [-l LIBRARY_PATH] [-f HANDLER_FUNCTION]
                           [-t TIMEOUT] [-a ARN_STRING] [-v VERSION_NAME]
                           [-e ENVIRONMENT_VARIABLES] [--version]
                           FILE EVENT

$ pip freeze > requirements.txt

■debin-bookwormでのrequirements.txt
 CloudShellと比べてurllib3のバージョンが新しい

$ cat requirements.txt 
boto3==1.34.70
botocore==1.34.70
jmespath==1.0.1
python-dateutil==2.9.0.post0
python-lambda-local==0.1.13
s3transfer==0.10.1
six==1.16.0
urllib3==2.2.1

■aws CloudShellでのrewuirements.txt
 debianと比べてurllib3のバージョンが古い

$ cat requirements.txt 
boto3==1.34.70
botocore==1.34.70
jmespath==1.0.1
python-dateutil==2.9.0.post0
python-lambda-local==0.1.13
s3transfer==0.10.1
six==1.16.0
urllib3==1.26.18

■どちらでもインストール出来るように requirements.txt を書き換える
 これで現バージョン相当をどこにでも作れるようになった

$ cat requirements.txt 
boto3==1.34.70
botocore==1.34.70
jmespath==1.0.1
python-dateutil==2.9.0.post0
python-lambda-local==0.1.13
s3transfer==0.10.1
six==1.16.0
urllib3>=1.26.18

$ pip install -r requirements.txt

■lambdaでは「ファイル名.ハンドラ名」でハンドラが作成される
 HelloWorldアプリを作成して実行
 Duration: 0.36 msなので

$ tree HelloWorld/
HelloWorld/
├── app
│   └── lambda_function.py
└── test
    └── event.json

$ find HelloWorld/ -type f \! -name "*.pyc" -exec awk '{print FILENAME,$0}' {} +
HelloWorld/test/event.json {}
HelloWorld/app/lambda_function.py def lambda_handler(event, context):
HelloWorld/app/lambda_function.py     return "Hello world"

$ cd HelloWorld/;python-lambda-local -f lambda_handler app/lambda_function.py test/event.json;cd ../
[root - INFO - 2024-03-26 23:05:08,173] Event: {}
[root - INFO - 2024-03-26 23:05:08,173] START RequestId: 9ba20fcc-3b02-4dc6-838a-40bf2216d9c2 Version: 
[root - INFO - 2024-03-26 23:05:08,185] END RequestId: 9ba20fcc-3b02-4dc6-838a-40bf2216d9c2
[root - INFO - 2024-03-26 23:05:08,186] REPORT RequestId: 9ba20fcc-3b02-4dc6-838a-40bf2216d9c2	Duration: 0.36 ms
[root - INFO - 2024-03-26 23:05:08,186] RESULT:
Hello world

■jsonの値を受け取って表示する、バージョン情報とタイムアウトを1秒にして実行

$ find HelloWorld_0.1/ -type f \! -name "*.pyc" -exec awk '{print FILENAME":"$0}' {} +
HelloWorld_0.1/event.json:{
HelloWorld_0.1/event.json:    "id": "sample_001",
HelloWorld_0.1/event.json:    "name": "sample name",
HelloWorld_0.1/event.json:    "description":"sample description"
HelloWorld_0.1/event.json:}
HelloWorld_0.1/lambda_function.py:import json
HelloWorld_0.1/lambda_function.py:
HelloWorld_0.1/lambda_function.py:print('Loading function')
HelloWorld_0.1/lambda_function.py:
HelloWorld_0.1/lambda_function.py:
HelloWorld_0.1/lambda_function.py:def lambda_handler(event, context):
HelloWorld_0.1/lambda_function.py:    print("id = " + event['id'])
HelloWorld_0.1/lambda_function.py:    print("name " + event['name'])
HelloWorld_0.1/lambda_function.py:    print("description = " + event['description'])
HelloWorld_0.1/lambda_function.py:    return event['id']  # Echo back the first key value
HelloWorld_0.1/lambda_function.py:    #raise Exception('Something went wrong')

■構文チェックのfalke8を入れる

$ pip install flake8
$ pip freeze > requirements.txt.flake8
$ sdiff -s requirements.txt*
							      >	flake8==7.0.0
							      >	mccabe==0.7.0
							      >	pycodestyle==2.11.1
							      >	pyflakes==3.2.0

■flake8でPEP 8の定義と照らす

$ flake8 lambda_function.py 
lambda_function.py:1:1: F401 'json' imported but unused
lambda_function.py:11:5: E265 block comment should start with '# '

$ pip install black
$ pip freeze > requirements.txt.black

■「black」を入れてPEP 8を強制する
 ダブルクォーテーションとシングルクォーテーションの取り扱いが変わる

$ sdiff -s requirements.txt.{flake8,black}
							      >	black==24.3.0
							      >	click==8.1.7
							      >	mypy-extensions==1.0.0
							      >	packaging==24.0
							      >	pathspec==0.12.1
							      >	platformdirs==4.2.0

■ところでディレクトリ階層は不要なので「app」や「test」をやめて、blackコマンドを適用する

$ cp lambda_function.py{,.backup} 
$ black lambda_function.py
reformatted lambda_function.py

All done! ✨ 🍰 ✨
1 file reformatted.

$ flake8 lambda_function.py 
lambda_function.py:1:1: F401 'json' imported but unused

$ sdiff -s lambda_function.py{,.backup}
print("Loading function")				      |	print('Loading function')
    print("id = " + event["id"])			      |	    print("id = " + event['id'])
    print("name " + event["name"])			      |	    print("name " + event['name'])
    print("description = " + event["description"])	      |	    print("description = " + event['description'])
    return event["id"]  # Echo back the first key value	      |	    return event['id']  # Echo back the first key value
    # raise Exception('Something went wrong')		      |	    #raise Exception('Something went wrong')

■変更前に確認するなら「--diff」オプションで

$ black --help | lsec -sep "^  -" --diff
  --color / --no-color            Show (or do not show) colored diff. Only
                                  applies when --diff is given.
  --diff                          Don't write the files back, just output a
                                  diff to indicate what changes Black would've
                                  made. They are printed to stdout so
                                  capturing them is simple.

■isortでimport文を修正する
 blackはimport文をチェックしない、isortは順序しかチェックしない

$ pip install isort
$ pip freeze > requirements.txt.isort
$ sdiff -s requirements.txt{.black,.isort}
							      >	isort==5.13.2

$ isort --diff lambda_function.py 

■元のソースとの差異を確認
 lambdaのバージョン指定は整数値の様子。

$ sdiff -s lambda_function.py{,.backup}
print("Loading function")				      |	import json
							      >
							      >	print('Loading function')
    print("id = " + event["id"])			      |	    print("id = " + event['id'])
    print("name " + event["name"])			      |	    print("name " + event['name'])
    print("description = " + event["description"])	      |	    print("description = " + event['description'])
    return event["id"]  # Echo back the first key value	      |	    return event['id']  # Echo back the first key value
    # raise Exception('Something went wrong')		      |	    #raise Exception('Something went wrong')

$ python-lambda-local -f lambda_handler lambda_function.py event.json -t 1 -v 1
[root - INFO - 2024-03-30 23:25:39,422] Event: {'id': 'sample_001', 'name': 'sample name', 'description': 'sample description'}
[root - INFO - 2024-03-30 23:25:39,422] START RequestId: 35478441-19b5-4297-823e-f43acf65bc1f Version: 1
Loading function
id = sample_001
name sample name
description = sample description
[root - INFO - 2024-03-30 23:25:39,435] END RequestId: 35478441-19b5-4297-823e-f43acf65bc1f
[root - INFO - 2024-03-30 23:25:39,435] REPORT RequestId: 35478441-19b5-4297-823e-f43acf65bc1f	Duration: 0.63 ms
[root - INFO - 2024-03-30 23:25:39,435] RESULT:
sample_001

$ zip IdNameDesc.zip lambda_function.py event.json 
  adding: lambda_function.py (deflated 39%)
  adding: event.json (deflated 38%)

■lambdaで実行
 バージョンを指定すると、「$LATEST」箇所が整数値に変わる

Loading function
START RequestId: XXXXXXXX-0063-4c8c-b39c-3c049d7ed9c5 Version: $LATEST
id = sample_001
name sample name
description = sample description
END RequestId: XXXXXXXX-0063-4c8c-b39c-3c049d7ed9c5
REPORT RequestId: XXXXXXXX-0063-4c8c-b39c-3c049d7ed9c5	Duration: 2.35 ms	Billed Duration: 3 ms	Memory Size: 128 MB	Max Memory Used: 34 MB	Init Duration: 83.05 ms	

■同様に

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/with-sns-create-package.html#with-sns-example-deployment-pkg-python

$ flake8 lambda_handler.py 
lambda_handler.py:2:1: F401 'json' imported but unused
lambda_handler.py:5:1: E302 expected 2 blank lines, found 1
lambda_handler.py:6:5: E265 block comment should start with '# '

$ cp lambda_handler.py{,.backup}
$ black lambda_handler.py
reformatted lambda_handler.py

All done! ✨ 🍰 ✨
1 file reformatted.

■ちなみにコードを zip 化してアップロードできるlambda-uploaderは更新されていないので使わないことにした。

$ pip_search lambda-uploader | grep lambda-uploader
                                                 🐍 https://pypi.org/search/?q=lambda-uploader 🐍                                                 
│ 📂 lambda-uploader      │ 1.3.0     │ 14-05-2018 │ AWS Python Lambda Packager   

■ところで、解説用のドキュメントファイルがあるならまだしも、
 動くコードしかないのであれば別のイライラポイントの発生源となるので、
 「Javadoc コメント」のような共通テンプレートをはやめに用意するべき
 ※Javaコーディング規約参照

https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html
Javadoc コメントには、少なくとも author と version(クラス)、param と return と exception(メソッド)を記述する

今後もバージョンアップのリリースが予定されているソースでは、上記に加えて since(バージョン)を記述する
@Overrideのあるメソッドでは、上記に加えて{@Inherit}を記述する