PynamoDB + FastAPIを用いたAPI開発方法 メモ

* PynamoDBとFastAPIを用いて簡単なCRUD APIを作成したのでメモとして残しておく。

## PynamoDB

* DynamoDB用Pythonインターフェイス
* DynamoDB APIを抽象化しており、比較的簡単にDynamoDB操作ができる。

## 事前準備

* [こちら](https://qiita.com/KWS_0901/items/300188b14b72533c2f99)の手順で動作確認用のDynamoDBコンテナを起動する。

  * テーブル名などは`test_user`として修正する。

* 依存ライブラリインストール

  ```
  pynamodb
  fastapi
  uvicorn
  ```

## コード

* `app.py`

```python
from pynamodb.models import Model
from pynamodb.attributes import UnicodeAttribute,NumberAttribute
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from pydantic import BaseModel
from typing import Optional
import uuid
import logging

# PynamoDB用データモデル
class UserModel(Model):
    # 接続先情報(クレデンシャルやテーブル情報)を定義
    class Meta:
        host = "http://localhost:8000"
        aws_access_key_id = 'dummy'
        aws_secret_access_key = 'dummy'
        region = 'ap-northeast-1'
        table_name = "test_user"
    # テーブル属性定義
    id = UnicodeAttribute(hash_key=True)
    email = UnicodeAttribute(null=True)
    
# FastAPIリクエストボディ用データモデル
class User(BaseModel):
    id: Optional[str] = None
    email: str
    
app = FastAPI()

# ユーザー登録API
@app.post("/users")
def regist_user(user: User):
    # ユーザーID生成
    user_id = str(uuid.uuid4()).replace("-","")
    regist_user = UserModel(user_id)
    regist_user.email = user.email
    # ユーザー登録
    regist_user.save()
    
    user.id = user_id
    return user

# ユーザー取得API
@app.get("/users/{user_id}")
def get_user(user_id: str):
    try:
        # ユーザー取得
        user = UserModel.get(user_id)
        logging.info(user)
        return user.attribute_values
    except UserModel.DoesNotExist:
        logging.error("User does not exist.")
        raise HTTPException(status_code=404, detail="user_not_found")


# ユーザー更新API
@app.put("/users/{user_id}")
def update_user(user_id:str, user: User):
    try:
        # 更新対象ユーザー取得
        target_user = UserModel.get(user_id)
        # ユーザー更新
        target_user.update(actions=[
            UserModel.email.set(user.email)
        ])
        return target_user.attribute_values
    except UserModel.DoesNotExist:
        logging.error("User does not exist.")
        raise HTTPException(status_code=404, detail="user_not_found")

# ユーザー削除API
@app.delete("/users/{user_id}")
def update_user(user_id:str):
    try:
        # 削除対象ユーザー取得
        target_user = UserModel.get(user_id)
        # ユーザー削除
        target_user.delete()
        return {}
    except UserModel.DoesNotExist:
        logging.error("User does not exist.")
        raise HTTPException(status_code=404, detail="user_not_found")    
```



## 動作確認

* **API起動**

  ```shell
  uvicorn app:app --reload --port 5000
  ```

* **ユーザー登録API**

  * リクエスト

    ```http
    POST /users HTTP/1.1
    Host: 127.0.0.1:5000
    Content-Type: application/json
    Content-Length: 37
    
    {
        "email":"test0@example.com"
    }
    ```

  * レスポンス

    ```json
    {
        "id": "87527962cbd14293943d323548f403da",
        "email": "test0@example.com"
    }
    ```

* **ユーザー取得API**

  * リクエスト

    ```http
    GET /users/87527962cbd14293943d323548f403da HTTP/1.1
    Host: 127.0.0.1:5000
    ```

  * レスポンス

    ```json
    {
        "email": "test0@example.com",
        "id": "87527962cbd14293943d323548f403da"
    }
    ```

* **ユーザー更新API**

  * リクエスト

    ```http
    PUT /users/87527962cbd14293943d323548f403da HTTP/1.1
    Host: 127.0.0.1:5000
    Content-Type: application/json
    Content-Length: 44
    
    {
        "email":"test0_update@example.com"
    }
    ```

  * レスポンス

    ```json
    {
        "email": "test0_update@example.com",
        "id": "87527962cbd14293943d323548f403da"
    }
    ```

* **ユーザー削除API**

  * リクエスト

    ```http
    DELETE /users/87527962cbd14293943d323548f403da HTTP/1.1
    Host: 127.0.0.1:5000
    ```

  * レスポンス

    ```json
    {}
    ```

## 参考情報

* [PynamoDB](https://pynamodb.readthedocs.io/en/latest/)