FastAPI等のPydanticV2を含むLambdaレイヤーの作成には注意が必要という話

# はじめに
AWS LambdaのLambdaレイヤーにPydanticV2を含むライブラリ(FastAPI等)をアップロードすると、レイヤーの作成環境によっては下記エラーでLambda関数が正常に動作しません。
>[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'pydantic_core._pydantic_core'

特にLambdaレイヤーを作業PC等で用意したZIPファイルから作成したりすると、このエラーに遭遇する場合が多いと思います。この問題の原因と対処方法を紹介します。

# 事象の確認
まずは事象を確認します。
手元の作業PC(windows機)でfastapiをインストールしたzipファイルを作成します。
```shell
>pip install fastapi -t python --no-user
～省略～
Successfully installed annotated-types-0.7.0 anyio-4.8.0 fastapi-0.115.8 idna-3.10 pydantic-2.10.6 pydantic-core-2.27.2 sniffio-1.3.1 starlette-0.45.3 typing-extensions-4.12.2
>powershell Compress-Archive -Path "python" -DestinationPath "python.zip"
```

マネジメントコンソールにてzipファイルからLambdaレイヤーを作成します。
レイヤー名は`bad_layer`としました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3870591/44fc7a54-515d-4da1-9397-3b507a22943e.png)

Lambdaレイヤー：`bad_layer`を使用し、`fastapi`をインポートするだけのコードを記述したLambda関数をテスト実行すると、次のようなエラーとなり正常に動作しません。
```python:lambda_function.py
import fastapi

def lambda_handler(event, context):
    print("success!")
```
>[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'pydantic_core._pydantic_core'
Traceback (most recent call last):
INIT_REPORT Init Duration: 310.61 ms	Phase: init	Status: error	Error Type: Runtime.ImportModuleError
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'pydantic_core._pydantic_core'
Traceback (most recent call last):

# 原因
Lambda関数のコードを次のように書き換えて切り分けします。
```python:lambda_function.py
import sysconfig
print("Lambda関数の互換性のあるコンパイル済みライブラリのバージョン:", sysconfig.get_config_var("EXT_SUFFIX"))

from importlib.metadata import files
print("インストールされたpydantic-core:", [file for file in files('pydantic-core') if file.name.startswith('_pydantic_core')])

def lambda_handler(event, context):
    print("success!")
```
>Lambda関数の互換性のあるコンパイル済みライブラリのバージョン: .cpython-312-x86_64-linux-gnu.so
インストールされたpydantic-core: [PackagePath('pydantic_core/_pydantic_core.cp312-win_amd64.pyd'), PackagePath('pydantic_core/_pydantic_core.pyi')]

上記の例ではLambda関数はサフィックスが`.cpython-312-x86_64-linux-gnu.so`であるファイルを必要としますが、bad_layerに追加した_pydantic_coreはサフィックスが`.cp312-win_amd64.pyd` `.pyi`であるためエラーが発生しています。

[切り分けの仕方はPydantic公式ドキュメントも参照ください](https://docs.pydantic.dev/2.10/integrations/aws_lambda/#troubleshooting "Troubleshooting")

# 対処方法
いくつか対処方法がありますので3つご紹介します。
## 1. AWSレイヤー：AWSLambdaPowertoolsを追加する
最速で対処できる方法です。
AWS公式のレイヤーであるAWSLambdaPowerToolsにはParserとしてPydanticが使用されています。
このレイヤーをbad_layerの後に追加することで正常に動作させることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3870591/a984931d-ef7e-4065-b925-7dedd8e872f7.png)

実行結果：
>Lambda関数の互換性のあるコンパイル済みライブラリのバージョン: .cpython-312-x86_64-linux-gnu.so
インストールされたpydantic-core: [PackagePath('pydantic_core/_pydantic_core.cpython-312-x86_64-linux-gnu.so'), PackagePath('pydantic_core/_pydantic_core.pyi')]
START RequestId: 113cb194-ec8c-4ed7-b22f-1b15e357ead1 Version: $LATEST
success!
END RequestId: 113cb194-ec8c-4ed7-b22f-1b15e357ead1

## 2. Lambdaレイヤー用のzipファイルを作り直す
作業PCでLambda関数が要求するpydanticを含むzipファイルを作り直す方法もあります。
```shell
>pip install fastapi -t python --no-user
～省略～
Successfully installed annotated-types-0.7.0 anyio-4.8.0 fastapi-0.115.8 idna-3.10 pydantic-2.10.6 pydantic-core-2.27.2 sniffio-1.3.1 starlette-0.45.3 typing-extensions-4.12.2
>pip install \
    --platform manylinux2014_x86_64 \
    --target=python \
    --implementation cp \
    --python-version 3.12 \
    --only-binary=:all: \
    --upgrade pydantic
～省略～
Successfully installed annotated-types-0.7.0 pydantic-2.10.6 pydantic-core-2.27.2 typing-extensions-4.12.2
>powershell Compress-Archive -Path "python" -DestinationPath "python.zip"
```
作成し直したzipファイルでLambdaレイヤーを作成し実行すると正常に動作させることができます。

[インストール方法はPydantic公式ドキュメントも参照ください](https://docs.pydantic.dev/2.10/integrations/aws_lambda/#installing-pydantic-for-aws-lambda-functions "Installing pydantic for AWS Lambda functions")

## 3. Lambdaレイヤーを使用せずDockerイメージ化してしまう
PydanticV2の問題に限らず様々な依存関係の問題を解決することができる方法です。
次のようなDockerfileからビルドしたイメージでLambda関数を作成することでも正常に動作させることができます。
```dockerfile:Dockerfile
FROM public.ecr.aws/lambda/python:3.12

RUN pip install fastapi

COPY lambda_function.py ${LAMBDA_TASK_ROOT}

CMD [ "lambda_function.lambda_handler" ]
```

# さいごに
この記事が誰かの役に立てば幸いです。