python src布局for AWS lambdas
#aws #python #lambda #poetry

在过去的几年中,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_lambda11
    • 您可以在服务下有更多的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 layoutnamesrc下的目录名称相同,该目录是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

src layout上的参考