在过去的几年中,Python社区一直朝着称为src layout
的包装布局格式迈进,是为Python封装组织目录和文件的推荐方法。
src layout
的主要收获是:
- 您的项目有一个
src
文件夹-
src
文件夹仅具有子文件夹[s]。每个子文件夹 - 是一个Python软件包
- 它的名称是软件包的名称
- 它包含一个
__init__.py
(这几乎是使它成为python包的原因) - 所有构成您的应用程序的PYTHON源代码(测试或构建实用程序除外)均为这些子文件夹[S]
-
- 现代Python工具期望 /与此布局最有效< / li>
- 使包装更容易,更容易出现错误
- “可编辑”安装用于开发和定期安装进行测试
- 通常不需要“导入欺骗”
- 本文末尾的参考
我们正在寻找使用Poetry在我们的MonorePo中组织Python AWS Lambda项目的标准和最佳实践,并一直访问src layout
,这是组织Python项目的推荐方法,这些项目具有多个模块。同时,几乎所有Python Lambda项目的示例都没有使用src layout
,实际上似乎希望将处理程序模块处于项目目录的顶级。 src layout
Poetry
和aws lambdas的这种组合并不多。
事实证明,这并不困难。主要的“技巧”是正确设置lambda handler
名称。
所有此示例代码均可在Informed/blogpost-python-src-layout
上获得
示例AWS lambda的SRC布局
这是一个适合AWS lambda的示例src layout
- 这是可能是常规或monorepo的部分,显示一个lambda项目:
my_lambda
11- 您可以在服务下有更多的lambda项目,每个项目都会是一个单独的子项目,具有相同的布局
- 如何实现这样的多项目MonorePo留给未来的博客文章
- 实际的lambda代码在
src/my_lambda
中 -
src/my_lambda/stuff
中其他非代码文件的示例可能由lambda或lambda layers(例如otel layer)使用,该示例将是已部署的ZIP映像的一部分。- 这是不需要的,只是显示了如何轻松捆绑其他非代码文件 的示例
- 您可以在此处像
utils.py
这样的其他Python模块,这些模块已导入到handler.py
模块 - 如果您的lambda应用程序更为复杂,则可以将本地软件包作为
src/my_lambda
的子目录
blogpost-python-src-layout
├── LICENSE
├── README.md
├── doc
│ └── python_src_layout_for_aws_lambdas.md
└── services
└── my_lambda
├── CHANGELOG.md
├── LICENSE
├── README.md
├── poetry.lock
├── pyproject.toml
├── src
│ └── my_lambda
│ ├── __init__.py
│ ├── handler.py
│ ├── stuff
│ │ ├── config.yml
│ │ └── data.csv
│ └── utils.py
└── tests
└── test_my_handler.py
诗诗Pyproject.toml
[tool.poetry]
name = "my_lambda"
version = "0.0.0"
description = "This is my lambda which is mine"
authors = ["Da Dev <daDev@example.com>"]
[tool.poetry.dependencies]
python = "^3.9"
pyyaml = "6.0"
[tool.poetry.group.dev.dependencies]
coverage = {extras = ["toml"], version = "^6.5.0"}
pytest = "^7.2.0"
flake8 = "^6.0.0"
python-lambda-local = "^0.1.13"
boto3 = "1.20.32"
[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
诗歌期望一个src layout
。 name
与src
下的目录名称相同,该目录是Python包(即它具有__init__.py
和源代码),这一事实意味着诗歌将在src/my_lambda
中将所有内容都视为要构建的软件包。 src/my_lambda
中的所有内容以及[tool.poetry.dependencies]
下的任何依赖项最终都将成为已部署的ZIP映像,该图像将成为Lambda函数图像。在此示例中,我们包含pyyaml
包。
[tool.poetry.group.dev.dependencies]
下的依赖项仅用于本地构建,而不包含在ZIP图像中。您可以将lambda运行时的依赖项放在[tool.poetry.group.dev.dependencies]
中,例如boto3或任何lambda层(例如otel或aws_powertools),如果您希望它们可用于本地测试。
还有选项(有些人建议)在[tool.poetry.dependencies]
中放置更新的Boto3和固定版本,因为AWS可以随时更改运行时使用的版本。这主要缺点是它使您上传的图像更大。
建议使用pytest进行测试的所有新软件包,建议使用'[tool.pytest.ini_options''部分。
将lambda处理程序设置为“两个点”格式
使用src layout
时配置lambdas的主要因素是必须按照
指定处理程序
<package_name>.<handler_module_name>.<handler_function_name>`
也称为2 dot solution,它像包装导入路径一样指定处理程序。
在我们的示例中是:
my_lambda.handler.handler
示例src/my_lambda/handler.py
这只是一个非常最小的lambda函数,证明了:
- 导入
PyYaml
软件包,我们指定为pyproject.toml
中的依赖项 - 打印处理程序中传递的
event
信息 - 从同一lambda软件包(
utils.my_util
)调用导入功能 - 获取和打印
src/my_lambda/stuff
中的非源文件之一
import json
import os
from . import utils
# We're not using pyyaml, just showing that it's installed
import yaml
print("Loading function")
def handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
print("value1 = " + event["key1"])
print("value2 = " + event["key2"])
print("value3 = " + event["key3"])
print(utils.my_util())
print(f"cwd: {os.getcwd()}")
with open("my_lambda/stuff/config.yml") as f:
lines = f.readlines()
print("File contents of my_lambda/stuff/config.yml:")
print(lines)
return event["key1"] # Return the first key value
建造和部署Lambda
这是使用诗歌构建和部署Lambda的基础知识。您可能有更复杂的CI/CD流程。
这些命令应在项目的顶级执行(即my_repo/services/my_lambda
)
建立分布
注意:如果需要编译作为构建过程的一部分,这些命令可能对您的实际应用程序不起作用,尤其是当您在与目标lambda的机器上构建应用程序上的应用程序中构建应用程序(即,在M1 Macintosh上构建并以X86_64 lambda为目标)。在这种情况下,您需要使用Docker来做您的建筑物,这超出了本文的范围。
这些示例仅在M1 Mac上进行测试,但应在任何现代Mac或 *Nix机器上使用,并且如果您不需要构建二进制依赖项,则将构建适用于Linux X86 lambdas的ZIP图像。
后来显示的pip install
使用参数--platform manylinux2014_x86_64
,该参数迫使包装仅包括为Linux X86_64构建的二进制轮毂。参数--only-binary :all:
确保它不会尝试编译任何源的依赖项,而是会发出错误,让您知道必须在本机目标环境中构建软件包(即使用Docker构建过程)。
1)首先,我们从诗歌中导出依赖项,以便稍后可以使用pip install
创建zip图像所需的文件。
poetry export -f requirements.txt --output requirements.txt --without-hashes
2)让诗歌构建所有轮子和这样的轮子
poetry build
这将导致dist
目录中的大量文件。
3)使用PIP创建软件包
poetry run pip install -r requirements.txt --upgrade --only-binary :all: --platform manylinux2014_x86_64 --target package dist/*.whl
这将生成package
目录中所有依赖项的所有车轮,适合于lambda映像。
4)拉链图像
cd package
mkdir -p out
zip -r -q out/my-lambda.zip . -x '*.pyc' out
cd ..
这将导致my_repo/services/my_lambda/package/out/my-lambda.zip
中的一个文件,适用于lambda函数图像上传到AWS。
创建lambda功能
您可以在AWS控制台中执行此操作或使用以下AWS CLI命令(如果使用AWS控制台,它将自动创建执行角色和信任策略)。
1)创建execution role and trust policy
aws iam create-role --role-name my-lambda-ex --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
- 您应该得到以下结果。
- 从此输出中复制ARN,您将其用于下一个命令
{
"Role": {
"Path": "/",
"RoleName": "my-lambda-ex",
"RoleId": "AROAWUWOOBVLDKY7ZE7P3",
"Arn": "arn:aws:iam::1234567890123:role/my-lambda-ex",
"CreateDate": "2023-01-12T06:08:22+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
2)创建lambda函数
- 您必须将
handler
设置为2个点样式my_lambda.handler.handler
-
将
role arn
中的假帐户ID(1234567890123
)更改为您的AWS帐户-ID
aws lambda create-function --function-name my-lambda \ --zip-file fileb://package/out/my-lambda.zip \ --handler my_lambda.handler.handler --runtime python3.9 \ --role arn:aws:iam::1234567890123:role/my-lambda-ex
如果您已经创建了lambda函数
如果您创建了lambda函数某种其他方式,例如通过控制台或要更新您之前创建的lambda函数,则可以使用以下命令将其更新。
这种机制从您早先上传的S3资产中加载lambda
aws lambda update-function-code --function-name my-lambda \
--zip-file fileb://package/out/my-lambda.zip \
--region us-west-2
测试Lambda
此时,您的Lambda应该准备好测试。您可以在AWS lambda控制台中使用默认测试输入:
{
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
您可以在AWS lambda控制台上运行默认的Test
,您将看到所有日志输出以及看起来像:
的结果
Test Event Name
basic
Response
"value1"
Function Logs
Loading function
START RequestId: 2f33f7b2-ddae-46df-a61b-1263ef404d6b Version: $LATEST
Received event: {
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
value1 = value1
value2 = value2
value3 = value3
Hello from my_util
cwd: /var/task
File contents of my_lambda/stuff/config.yml:
['thing:\n', ' hand: manicure\n']
END RequestId: 2f33f7b2-ddae-46df-a61b-1263ef404d6b
REPORT RequestId: 2f33f7b2-ddae-46df-a61b-1263ef404d6b Duration: 1.57 ms Billed Duration: 2 ms Memory Size: 128 MB Max Memory Used: 40 MB Init Duration: 172.83 ms
,也可以通过AWS CLI运行:
aws lambda invoke --cli-binary-format raw-in-base64-out \
--function-name my-lambda \
--payload '{ "key1": "value1", "key2": "value2", "key3": "value3"}' \
outputfile.txt --log-type Tail \
--query 'LogResult' --output text | base64 -d
这将打印日志输出(与在AWS控制台中运行测试的示例中所示的相同)
您可以在outputfile.txt
中看到处理程序的返回值
使用Pytest
进行测试
services/my_lambda/tests/test_my_handler.py
中还有一个简单的测试示例
import pytest
import os
from my_lambda.handler import handler
event = {"key1": "value1", "key2": "value2", "key3": "value3"}
context = {}
def test_my_handler():
# Emulate running in the same directory context as the lambda would
os.chdir("src")
assert handler(event, context) == "value1"
在您在此项目中首次运行pytest(或在进行需要在pyproject.toml
的[tool.poetry.group.dev.dependencies]
部分中需要任何包装的任何本地开发之前),您需要至少运行一次:
poetry install
这将安装您的主[tool.poetry.dependencies]
中列出的所有依赖项和Virtualenv中的[tool.poetry.group.dev.dependencies]
中列出的所有依赖项。
您可以从services/my_lambda
进行测试:
poetry run pytest