FastAPI Tutorialメモ その3 Query Parameters

## はじめに
FastAPI Souce: https://github.com/tiangolo/fastapi
FastAPI Document: https://fastapi.tiangolo.com

対象:[Query Parameters](https://fastapi.tiangolo.com/tutorial/query-params/)

FastAPIチュートリアルのメモ。
基本的にはFastAPIの公式チュートリアルを参考にしていますが、自身の学習のため一部分を省略したり順番を前後させています。
正しい詳細な情報は公式ドキュメントを参考いただければと思います。

Web & Python 初心者かつ翻訳はGoogleとDeepL頼りのため、間違い等ありましたらご指摘いただけますと幸いです。

以前までのメモはこちら

- [FastAPI Tutorialメモ その1 インスト−ル](https://qiita.com/kida_kun_/items/bcbaf3b7c97c5102af4a)
- [FastAPI Tutorialメモ その2 Path Parameters](https://qiita.com/kida_kun_/items/bc8b78235b56ba8175be)


## 開発環境
Ubuntu 20.04 LTS
Python 3.8.2
pipenv 2018.11.26

## 目標
- FastAPIにおける**Query Parameter**の使い方を把握する

## 手順

#### Intro
前回( [FastAPI Tutorialメモ その2 Path Parameters](https://qiita.com/kida_kun_/items/bc8b78235b56ba8175be) )、関数のパラメータとして**Pathパラメータ**を渡す方法について学習しました。
今回は**Queryパラメータ**を渡す方法について学習したいと思います。

FastAPIでは、Pathパラメータではないパラメータを関数の引数として宣言した場合、自動的に**Queryパラメータ**として解釈されます。
下記のコードを確認してください。

```python:main.py
from fastapi import FastAPI

app = FastAPI()

fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):    # <- Queryパラメータを指定している。
    return fake_items_db[skip : skip + limit]

```

HTTPのURIにおいて**クエリ**は基本的に`?`の後に続く`キー`と`値`のペアを`=`でつなげて表現されます。
複数指定する場合は`&`でURIを区切ります。
上記のコードに合わせるのであれば、たとえば下記のようなURLが想定されます:

```
http://127.0.0.1:8000/items/?skip=0&limit=10
```

この場合の**Queryパラメータ**は、

- 値が`0`の`skip`
- 値が`10`の`limit`

の2つが関数に渡されます。

これらはURLの一部であるため、通常は文字列(`str`)として解釈されるはずです。
しかし、上記のコードでは引数にパラメータを指定する際、Pythonの型である`int`型で宣言したため、パラメータは`int`型への変換と検証が行われます。

これは、**Pathパラメータ**で適用された以下の利点が**Queryパラメータ**にも適用されることを意味しています。

- エディタによるサポート
- データの "parsing" (解析)
- データの検証
- 自動ドキュメンテーション

なお、これらの利点はPath、Queryに限らずFastAPIが扱う様々なパラメータでサポートされています。

#### Defaults (デフォルト値)
**Queryパラメータ**はURIのパスで固定されているわけではなく、オプション設定であるため、デフォルト値を指定することが可能です。

先述のコードでは、デフォルト値として`skip=0`と`limit=10`が指定されていることが確認できます。
よって、このようなURL:

```sh
http://127.0.0.1:8000/items/
```

にブラウザからアクセスした場合、

```sh
http://127.0.0.1:8000/items/?skip=0&limit=10
```

上記2つのURLは同じ出力になります。

また、例えば下記のようなURL:

```sh
http://127.0.0.1:8000/items/?skip=20
```

にアクセスした場合、関数のパラメータは次のようになります:

- URLで設定された`skip=20`
- デフォルト値`limit=10`

#### Optional parameters
デフォルト値を`None`とすることにより、オプションとして使用することができる**Queryパラメータ**を宣言することも可能です。
例として、下記のコードを確認してください。

```python:main.py
from typing import Optional     # typingパッケージからOptionalをインポート

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Optional[str] = None):     # Queryパラメータ"q"をオプションパラメータとして宣言
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}

```

この場合、関数のパラメータ`q`はオプション扱いとなり、デフォルトでは`None`として扱われます。

この際にFastAPIが`item_id`が**Pathパラメータ**であり、`q`が**Pathパラメータ**ではなく**Queryパラメータ**であることを理解している点に注目してください。

また、FastAPIでは`=None`が宣言されるだけで`q`がオプションのパラメータであることを認識しています。
`Optional`はFastAPI自体が使用しているわけではありません。
わざわざ`Optional[str]`としてオプションであることを明示しているのは、エディターのサポートによってエラーを事前に発見することができるためです。

#### Query parameter type conversion (Queryパラメータの型の変換)
**Queryパラメータ**として`bool`型を宣言することができ、その場合、パラメータはFastAPIによって適した形に変換されれます。
例として、下記のコードを確認してください。

```python:main.py
from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
# 引数のクエリパラメータにbool型を指定している
async def read_item(item_id: str, q: Optional[str] = None, short: bool = False):
    item = {"item_id": item_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

```

このようなコードの場合、以下のようにアクセスすることができます。

- `http://127.0.0.1:8000/items/foo?short=1`
- `http://127.0.0.1:8000/items/foo?short=True`
- `http://127.0.0.1:8000/items/foo?short=true`
- `http://127.0.0.1:8000/items/foo?short=on`
- `http://127.0.0.1:8000/items/foo?short=yes`

これらのURLの場合、パラメータ`short`はすべて`bool値`で`True`として扱われます。(最初の文字を小文字から大文字にした場合でも同様です)
上記以外の値の場合は`False`となります。
このように、**Queryパラメータ**に`bool型`を指定した場合はFastAPIによる変換が行われます。

#### Multiple path and query parameters (複数のPathパラメータとQueryパラメータ)
FastAPIでは関数の引数に複数の**Pathパラメータ**と**Queryパラメータ**を同時に宣言しても問題なく動作します。
また、それらを特定の順序に従って宣言する必要もありません。
FastAPIでは、パラメータを名前によって認識しているためです。

たとえば、このようなコードがあるとします：

```python:main
from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/users/{user_id}/items/{item_id}")    # <- {user_id}と{item_id}をPathパラメータとして指定
async def read_user_item(
    user_id: int, item_id: str, q: Optional[str] = None, short: bool = False    # <- Pathパラメータではない"q"と"short"はQueryパラメータとして扱う
):
    item = {"item_id": item_id, "owner_id": user_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item

```

この場合、関数の引数の順序を入れ替えたとしても問題ありません。
FastAPIはあくまでもパラメータの名前を基準にして各パラメータを認識します。

#### Required query parameters (必須のクエリパラメータの指定)
Path以外のパラメータ(現時点ではQueryパラメータしか学習していませんが)でデフォルト値を宣言した場合、そのパラメータは省略不可の必須のパラメータではなくなります。
パラメータを省略した場合はデフォルト値が指定されるためです。

また、デフォルト値を`None`とすることで、特定の値を追加せずにパラメータを任意のオプションパラメータとして宣言することもできます。

では、**Queryパラメータ**を省略不可能な必須のパラメータとして設定したい時はどうするかというと、デフォルト値を宣言しないことで実現できます。

```python:main
from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_user_item(item_id: str, needy: str):     # <- デフォルト値を指定せずにパラメータneedyを宣言している
    item = {"item_id": item_id, "needy": needy}
    return item

```

上記の`needy`パラメータは必須の`str`型**Queryパラメータ**として宣言されています。
もしあなたがこのようなURL:

```sh
http://127.0.0.1:8000/items/foo-item
```

をブラウザで開いた場合、必須パラメータである`needy`が追加されていないため、次のようなエラーが表示されるｄでしょう。

```JSON
{
    "detail": [
        {
            "loc": [
                "query",
                "needy"
            ],
            "msg": "field required",
            "type": "value_error.missing"
        }
    ]
}
```

`needy`は必須のパラメータなので、下記のようにURLで指定する必要があります:

```sh
http://127.0.0.1:8000/items/foo-item?needy=sooooneedy
```

このURLは次のように表示されます:

```JSON
{
    "item_id": "foo-item",
    "needy": "sooooneedy"
}
```

もちろん、必須のパラメータとデフォルト値を持つパラメータとオプションのパラメータを同時に定義することも可能です。

```python:main
from typing import Optional

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_user_item(
    item_id: str, needy: str, skip: int = 0, limit: Optional[int] = None    # <-
):
    item = {"item_id": item_id, "needy": needy, "skip": skip, "limit": limit}
    return item
```

上記のコードでは、3つのクエリパラメータが宣言されています。

- `needy`: `str`型で(デフォルト値を持たない)必須のパラメータ
- `skip`: `int`型でデフォルト値`0`のパラメータ
- `limit`: `int`型でオプションのパラメータ

なお、**Pathパラメータ**と同様に**Queryパラメータ**でも`Enum`を使用することができます。
詳細は[Path Parameters](https://fastapi.tiangolo.com/tutorial/path-params/#predefined-values)を確認ください。

## 終わりに
今回は**Queryパラメータ**の使用方法を学習しました。
この記事で使用したコードはすべて[公式ドキュメント](https://fastapi.tiangolo.com/tutorial/query-params/) からの引用になりますが、
私のローカル環境で動作することは確認できています。

次回は[**Request Body**](https://fastapi.tiangolo.com/tutorial/body/) を学習します。