Fast APIを試してみた

# 背景・目的
FastAPIに触れる機会があったので、基本的な特徴などを整理します。

# まとめ
下記に特徴を整理します。

|特徴|説明|
|:--|:--|
|FirstAPI|標準のPython型ヒントに基づいてPythonでAPIを構築するためのモダンなハイパフォーマンスなWebフレームワーク|
|特徴|・高速<BR>・高速なコーディング<BR>・少ないバグ<BR>・直感的<BR>・簡単<BR>・簡潔<BR>・堅牢<BR>・標準ベース|
|Requirements|下記を前提にしています。<BR>・Starlette<BR>・Pydantic|
|Dependencies|下記に依存しています。<BR>・Pydantic<BR>・Starlette|
|FastAPI CLI|FastAPI アプリの提供、FastAPI プロジェクトの管理などに使用できるコマンドラインプログラム<BR><BR>開発モードと本番モードをパラメータによって分ける|
|ライセンス|MITライセンス|


# 概要
## コンセプト
下記を基に整理します。

https://fastapi.tiangolo.com/

- FirstAPIは、標準のPython型ヒントに基づいてPythonでAPIを構築するためのモダンなハイパフォーマンスなWebフレームワークです

- 主な特徴は下記の通り
    - 高速
        - Node JSやGoと同等の非常に高いパフォーマンス
        - Pythonフレームワークの中で最も高速
    - 高速なコーディング
        - 開発速が200%〜300%向上する
    - バグの減少
        - 開発者によるエラーの約40%を削減する
    - 直感的
        - 優れたエディタのサポート
        - 完成されている
        - デバックにかかる時間が短縮される
    - 簡単
        - 簡単に使いやすく学びやいように設計されている
        - ドキュメントを読む時間が短縮される
    - 簡潔
        - コードの重複を最小限にする
        - 各パラメータ宣言からの複数の機能
        - バグが少なくなる
    - 堅牢
        - 本番に対応したコードの取得
        - 自動対話型ドキュメントを使用する
    - 標準ベース
        - APIのオープンスタンダードに基づいている
        - OpenAPI、JSONスキーマに準拠

### Requirements
下記を前提にしています。
- [Starlette](https://www.starlette.io/)（Web）
- [Pydantic](https://docs.pydantic.dev/latest/)（Data）

### Dependencies
Fast APIは、PydanticとStarletteに依存しています。

- Pydantic
    - email-validator
        - メールの検証用
- Starlette
    - httpx
        - TestClientを使用する場合は必須
    - jinja2
        - デフォルトのテンプレート構成を使用する場合は必須
    - python-multipart
        - request.formを使用してフォームの解析をする場合に必須
- FastAPI / Starlette
    - uvicorn
        - アプリケーションをロードして提供するサーバ用
        - 高いパフォーマンスの提供に必要ないくつかの依存関係が含まれる
    - fastapi-cli
        - fastapiコマンドを提供
#### 追加のオプションの依存関係
- Pydantic
    - pydantic-setting
        - 設定管理
    - pydantic-extra-types
        - Pydanticで使用される追加の型
- FastAPI
    - orjson
        - ORJSONResponseを使用する場合
    - ujson
        - UJSONResponseを使用する場合

### License
MIT ライセンス

## FastAPI CLI
- FastAPI CLI は、FastAPI アプリの提供、FastAPI プロジェクトの管理などに使用できるコマンドラインプログラムである
- FastAPI をインストールすると fastapi-cli というパッケージが含まれる。このパッケージはターミナルで fastapi コマンドを提供する
- 開発用に FastAPI アプリを実行するには、fastapi dev コマンドを使用できる
- fastapiというコマンドは、fastapi cliを指す
- FastAPI CLI は、Python プログラム (main.py など) へのパスを取得し、FastAPI インスタンス (通常は app という名前) を自動的に検出し、正しいインポート プロセスを決定して、それを提供する
- 本番環境では、代わりに fastapi run を使用する
- FastAPI CLI は内部的に、高性能で実稼働対応の ASGI サーバーである Uvicorn を使用する

### fastapi dev
fastapi dev を実行すると、開発モードが開始される。
- デフォルトでは自動リロードが有効になっており、コードを変更するとサーバーが自動的にリロードされる
    - これはリソースを大量に消費するため、無効にした場合よりも安定性が低下する可能性がある
- 開発のみに使用する
- IP アドレス 127.0.0.1 もListenする
    - マシンがそれ自体 (ローカルホスト) とのみ通信するための IP

### fastapi run
fastapi run を実行すると、デフォルトで実稼働モードで FastAPI が開始される。
- デフォルトでは、自動リロードは無効になっている
- IP アドレス 0.0.0.0 もリッスンする
    - これは、使用可能なすべての IP アドレスを意味する
    - これにより、マシンと通信できるすべてのユーザーがパブリックにアクセスできるようになる
- 通常、実稼働環境 (コンテナーなど) で実行する方法
- ほとんどの場合、HTTPS を処理する「terminal proxy」をトップに置くことになる 
- これはアプリケーションのデプロイ方法によって異なる
- プロバイダーがこれを行う場合もあれば、自分でセットアップする必要がある場合もある

# 実践
## Installation
下記を基に試します。

https://fastapi.tiangolo.com/#installation

1. fastapiをインストールします
    ```bash
    $ pip install fastapi
    ```
## Example
下記を基に試します。
https://fastapi.tiangolo.com/#example

### Create it
1. main.pyファイルに下記のコードを書きます
    ```python
    from typing import Union
    
    from fastapi import FastAPI
    
    app =FastAPI()
    
    @app.get("/")
    def read_root():
        return {"Hello": "World"}
    
    @app.get("/items/{item_id}")
    def read_item(item_id: int, q: Union[str, None] = None):
        return {"item_id": item_id, "q": q}
    ```
### Run it
fastapi devコマンドにより、上記で作成したmain.pyファイルを読み取り、
その中のFastAPIアプリを検出し、Uvicornを使用してサーバを起動します。

デフォルトでは、fastapi devはローカル開発用に自動リロードを有効にして起動します。

1. 下記のコマンドを実行します
```bash
fastapi-tutorial % ls main.py 
main.py
% fastapi dev main.py
INFO     Using path main.py                                                                                                                                                                                                      
INFO     Resolved absolute path /XXXXX/XXXXX/XXXXX/fastapi-tutorial/main.py                                                                                                                                                    
INFO     Searching for package file structure from directories with __init__.py files                                                                                                                                            
INFO     Importing from /XXXXX/XXXXX/XXXXX/fastapi-tutorial                                                                                                                                                                    
                                                                                                                                                                                                                                 
 ╭─ Python module file ─╮                                                                                                                                                                                                        
 │                      │                                                                                                                                                                                                        
 │  🐍 main.py          │                                                                                                                                                                                                        
 │                      │                                                                                                                                                                                                        
 ╰──────────────────────╯                                                                                                                                                                                                        
                                                                                                                                                                                                                                 
INFO     Importing module main                                                                                                                                                                                                   
INFO     Found importable FastAPI app                                                                                                                                                                                            
                                                                                                                                                                                                                                 
 ╭─ Importable FastAPI app ─╮                                                                                                                                                                                                    
 │                          │                                                                                                                                                                                                    
 │  from main import app    │                                                                                                                                                                                                    
 │                          │                                                                                                                                                                                                    
 ╰──────────────────────────╯                                                                                                                                                                                                    
                                                                                                                                                                                                                                 
INFO     Using import string main:app                                                                                                                                                                                            
                                                                                                                                                                                                                                 
 ╭────────── FastAPI CLI - Development mode ───────────╮                                                                                                                                                                         
 │                                                     │                                                                                                                                                                         
 │  Serving at: http://127.0.0.1:8000                  │                                                                                                                                                                         
 │                                                     │                                                                                                                                                                         
 │  API docs: http://127.0.0.1:8000/docs               │                                                                                                                                                                         
 │                                                     │                                                                                                                                                                         
 │  Running in development mode, for production use:   │                                                                                                                                                                         
 │                                                     │                                                                                                                                                                         
 │  fastapi run                                        │                                                                                                                                                                         
 │                                                     │                                                                                                                                                                         
 ╰─────────────────────────────────────────────────────╯                                                                                                                                                                         
                                                                                                                                                                                                                                 
INFO:     Will watch for changes in these directories: ['/XXXXX/XXXXX/XXXXX/fastapi-tutorial']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [72826] using WatchFiles
INFO:     Started server process [72830]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

```

### Check it
上記で作成した`main.py`と、起動されたfastapiを使用してブラウザから挙動を確認します。

1. ブラウザから、`http://127.0.0.1:8000/items/5?q=somequery`を開きます
1. 下記が表示されした
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/260a58d7-6e24-f862-dc8d-cf2e331b5a98.png" width=500 />

1. 次に、ブラウザから`http://127.0.0.1:8000/`（items以降とパラメータなし）を開いてみます
1. Hello worldが表示されました
    <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/220facac-66e0-a34f-25ca-746cbfa81d5b.png" width=400 />

### Interactive API docs
APIドキュメントを確認してみます。Swagger UIが表示されます。

1. ブラウザから`http://127.0.0.1:8000/docs`を開きます
1. 下記が表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/6708dfae-433d-ab3c-8720-3e33ace8f6e0.png)

### Alternative API docs
もう一つ、APIドキュメントを確認します。redocが表示されます。

1. ブラウザから`http://127.0.0.1:8000/redoc`を開きます
1. 下記が表示されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/888232ff-d86e-740b-dc78-9a929c8e3237.png)

### Example upgrade
PUT リクエストから本文を受け取るようにファイル main.py を変更します。 
Pydantic により、標準の Python 型を使用して本体を宣言します。

1. main.pyを下記のように変更します。（追加した箇所には、Addedのコメントを残しています）
    ```python
    from typing import Union
    
    from fastapi import FastAPI
    # Added
    from pydantic import BaseModel
    
    app =FastAPI()
    
    # Added
    class Item(BaseModel):
        name: str
        price: float
        is_offer: Union[bool, None] = None
    
    @app.get("/")
    def read_root():
        return {"Hello": "World"}
    
    @app.get("/items/{item_id}")
    def read_item(item_id: int, q: Union[str, None] = None):
        return {"item_id": item_id, "q": q}
    
    # Added
    @app.put("/items/{item_id}")
    def update_item(item_id: int, item: Item):
        return {"item_name": item.name, "item_id": item_id}
    ```
1. ファイルを保存すると、自動リロードされます。（便利です）
    ```bash
    WARNING:  WatchFiles detected changes in 'main.py'. Reloading...
    INFO:     Shutting down
    INFO:     Waiting for application shutdown.
    INFO:     Application shutdown complete.
    INFO:     Finished server process [73765]
    INFO:     Started server process [73811]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    ```

### Interactive API docs upgrade
上記のPutメソッド追加したので、APIドキュメントを確認します。

1. ブラウザから`http://127.0.0.1:8000/docs`を開きます
1. 追加されていました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/ad61b37a-8879-9e15-0715-31fc822f0617.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/23c409bc-35c2-b764-efe2-399dfb52c86c.png)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/4abdc32a-9f7c-b8e3-c99a-a9d98fc7c1bd.png)

1. 「Try it out」をクリックし、パラメータを入力してみます
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/d2cccd7a-6932-7d14-5181-05f0eea97fde.png)

1. ①「item_id」、②「Requrest body」を変更して、③「Execute」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/76ee2497-f1d0-c83b-e769-fd3bfd8759e3.png)

1. curlでリクエストされ、サーバで処理されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/57ca3195-63f4-5d38-b941-01ff70773165.png)
1. fastapiのログからも「200」が返されていました
    ```bash
    INFO:     127.0.0.1:51929 - "GET /docs HTTP/1.1" 200 OK
    INFO:     127.0.0.1:51929 - "GET /openapi.json HTTP/1.1" 200 OK
    INFO:     127.0.0.1:51937 - "PUT /items/1 HTTP/1.1" 200 OK
    ```


### Alternative API docs upgrade
redocでも確認します。

1. ブラウザで、`http://127.0.0.1:8000/redoc`を開きます
1. 下記が表示されました。（putのUpdate Itemが追加されています）

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/806ed295-bcdb-ec11-5b32-c44feb5122d7.png)


# 考察
今回、FastAPIを試してみました。下記が便利と感じました。今後もチュートリアルを通して理解を深めたいと思います。
- 少量のコード
- わかりやすい記述
- OpenAPIドキュメントを自動で作成
- 自動リロード

# 参考
https://fastapi.tiangolo.com/

https://github.com/fastapi/fastapi

https://www.starlette.io/

https://docs.pydantic.dev/latest/

https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7

https://github.com/OAI/OpenAPI-Specification

https://json-schema.org/

https://github.com/swagger-api/swagger-ui

https://github.com/Redocly/redoc