AWS APIGateway＋Lambda＋FastAPIにてCSVファイルの処理APIを作ってみた（忘備録）

# AWS APIGateway＋Lambda＋FastAPIにてCSVファイルの処理APIを作ってみた

### はじめに
今回はLambda＋FastAPI＋PandasでのCSVファイル800行の処理速度を検証するためにAPIを作成してみた。
これは、私が作っているポートフォリオ用のアプリのための検証である。

### 検証手順
1. Lambda＋FastAPI　をZipファイルでuploadしてLambdaが動くか検証
2. LambdaをCSVアップロードの方式に書き換えて、実際にリクエストを送る

### 1．Lambda＋FastAPI　をZipファイルでuploadしてLambdaが動くか検証
**lumbda_function.py**
```python
from fastapi import FastAPI, Request
from mangum import Mangum
import pandas as pd
from pydantic import BaseModel
from typing import List, Dict

app = FastAPI()

# 入力の定義（例：リストの辞書形式）
class InputData(BaseModel):
    data: List[Dict[str, float]]  # 例: [{"a": 1, "b": 2}, {"a": 3, "b": 4}]

@app.post("/describe")
async def describe_data(input: InputData):
    try:
        # DataFrameに変換
        df = pd.DataFrame(input.data)
        
        # describe()をJSONに変換
        result = df.describe().to_dict()
        return {"summary": result}
    
    except Exception as e:
        return {"error": str(e)}

# Lambdaハンドラー
handler = Mangum(app)
```
リクエストBodyから辞書型をリストとして格納して、describe()メソッドでサマリーを表示するようになっています。
Mangumとは、LambdaからAPIGatewayに渡される特殊なリクエスト形式をFastAPIが処理できるASGIに変換するlibraryです。

**build.ps1最終形態**
```posh
# build.ps1
Write-Host "▶ Cleaning up..."
Remove-Item -Recurse -Force package, function.zip -ErrorAction SilentlyContinue

Write-Host "▶ Installing dependencies with Docker (Amazon Linux 2023)..."
docker run --rm -v "${PWD}:/app" -w /app public.ecr.aws/amazonlinux/amazonlinux:2023 `
    bash -c "
        yum update -y && \
        yum install -y python3 python3-pip python3-devel gcc zip git make && \
        alternatives --install /usr/bin/python python /usr/bin/python3 1 && \
        alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 && \
        python -m pip install --upgrade pip && \
        mkdir -p /app/package && \
        pip install -r /app/requirements.txt -t /app/package && \
        cd /app/package && \
        find . -name '*.pyc' -delete && \
        find . -name '__pycache__' -type d -exec rm -r {} +
    "

Write-Host "▶ Creating ZIP file..."
Copy-Item -Path .\lambda_function.py -Destination .\package\
Compress-Archive -Path .\package\* -DestinationPath function.zip

Write-Host "✅ 完了しました！ → function.zip を Lambda にアップロードできます。"
```

**requirements.txt最終形態**
```
fastapi
mangum
numpy
pandas
pydantic==1.10.13
```
上記のBuild.ps1ファイルを実行して、Zipファイルを作成し、Lambdaにアップロードしました。
テストコードを実行です！
### が・・・・




ここで少し嵌りました。何度テストしても、このZipファイルがLambda上でエラーになり、pydantic_coreが見つからないと怒られます。調べたらWindows上でDockerなしでBuildしたファイルとUbuntu上でBuildしたファイルでは、セットが異なるようで、Docker（AmazonLinux2023）上でBuildして、中身をZipファイルにつめないといけないとのことで、以下の文を追加しました。
```posh
       mkdir -p /app/package && \
        pip install -r /app/requirements.txt -t /app/package && \
        cd /app/package && \
        find . -name '*.pyc' -delete && \
        find . -name '__pycache__' -type d -exec rm -r {} +

```
成功！と思いきや、またエラー。
嵌りました。
Pythonの実行VersionがLambda上とローカル（Docker上）でずれていたため、以下のエラーと格闘しました。

```
{
  "errorMessage": "Unable to import module 'lambda_function': Unable to import required dependencies:\nnumpy: Error importing numpy: you should not try to import numpy from\n        its source directory; please exit the numpy source tree, and relaunch\n        your python interpreter from there.",
  "errorType": "Runtime.ImportModuleError",
  "requestId": "",
  "stackTrace": []
}
```
.soファイルというnumpyのbuild済みのファイルが含まれているか確認しにいきました。
あるのになぜ？？と思ったら、LambdaのランタイムのVersionとずれていました。
pydantic_coreのバージョンを指定したり、いろいろやったのですが、結局バージョンの違いがポイントだったようです。

### 上記エラーが解消！今度こそ！！と思ったのですが・・・・
またもや、エラーです。
リクエストの形式がHTTP形式になっていないからInternalSeverErrorが出ていたようで、sourceIPを追記することで、テストが正しく実行できました！

**テストコード**
```json
{
  "version": "2.0",
  "routeKey": "POST /describe",
  "rawPath": "/describe",
  "rawQueryString": "",
  "requestContext": {
    "http": {
      "method": "POST",
      "path": "/describe",
      "sourceIp": "127.0.0.1",
      "userAgent": "test-client"
    }
  },
  "headers": {
    "content-type": "application/json"
  },
  "isBase64Encoded": false,
  "body": "{\"data\": [{\"a\": 1, \"b\": 2}, {\"a\": 3, \"b\": 4}]}"
}
```
やった～！！！


### 2. LambdaをCSVアップロードの方式に書き換えて、実際にリクエストを送る
```python
from fastapi import FastAPI, UploadFile, File
from mangum import Mangum
import pandas as pd

app = FastAPI()

@app.post("/describe")
async def describe_csv(file: UploadFile = File(...)):
    try:
        # CSVファイルを読み込み
        contents = await file.read()
        df = pd.read_csv(pd.io.common.BytesIO(contents))
        
        # describe()をJSONに変換
        result = df.describe().to_dict()
        return {"summary": result}
    
    except Exception as e:
        return {"error": str(e)}

# Lambdaハンドラー
handler = Mangum(app)

```

これは、テストがLambda単体では、できない形式となっているので、APIGateway経由で実際にリクエストしてテストします！！
/describeでリクエストを受ける仕様なので、APIGatewayの/describeにPOSTメソッドで、プロキシ統合をOnにして、Lambdaをトリガーに設定する。
**プロキシ統合をOn**にしないと、受け取ったリクエストが、Mangumの期待する生のデータでなくなるようです。
APIをデプロイで新しいステージにデプロイする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967486/f7ff0293-9a1f-4756-8614-c8dc1e7aca33.png)

curlコマンドで、実際にCSVを渡してみて、反応をみた。

```bash
curl -X POST https://youraddress.execute-api.ap-northeast-1.amazonaws.com/dev/describe \
-H "Content-Type: multipart/form-data" \
-F "file=@./simulated_data.csv"
```
失敗・・・

libraryの中にpython-multipartモジュールが足りないようなので、
追記して、zipファイルを再度アップロード

**requirements.txt**
```
fastapi
mangum
numpy
pandas
pydantic==1.10.13
python-multipart
```
無事成功！！
良かった！！

Lambdaがコールドスタートのせいで、最初の処理がやや遅いです。
でも、できた！！！