はじめての FastAPI（前編）

後編はこちら
https://qiita.com/uturned0/items/16eeaf0bcb84d5c88853


# FastAPIを使ってみる

ref
https://fastapi.tiangolo.com/tutorial/

# install

pip install fastapi[all]

# run

pycharmで動くように、こうする

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

app = FastAPI()


@app.get("/")
async def read_root():
    return {"Hello": "World"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
```

PyCharmの Run 設定は
普通の Python で
script path -> Module name にして uvicorn を選ぶ
main:app --reload --port 8000

しかし、これだと auto reload されなかった。

どうしてもappとrunが別ファイルじゃないとダメみたいなので、仕方ない
debug用のfileを作る。

```py:debug.py
import uvicorn

# Importing app here makes the syntax cleaner as it will be picked up by refactors
from main import app

if __name__ == "__main__":
    uvicorn.run("debug:app", host="0.0.0.0", port=8000, reload=True)
```

これをpythonで走らせれば、 auto reload もok. uvicornをする必要はない。



イマイチだけど公式にもpycharm用のmanualがあった
https://fastapi.tiangolo.com/tutorial/debugging/


### さあ見てみよう

API
http://0.0.0.0:8000/

Doc
http://0.0.0.0:8000/docs

Redoc
http://127.0.0.1:8000/redoc

Open API json scheme
http://127.0.0.1:8000/openapi.json


見れた！

# Path Parameters¶

### add an endpoint

```py
@app.get("/items/{item_id}")
async def read_item(item_id):
    return {"item_id": item_id}

http://0.0.0.0:8000/items/1
```

### 型 validation

```py
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

http://0.0.0.0:8000/items/strings
```

```json
{
detail: [
{
loc: [
"path",
"item_id"
],
msg: "value is not a valid integer",
type: "type_error.integer"
}
]
}
```


### Use ENUM

```py
from enum import Enum

from fastapi import FastAPI


class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"
    lenet = "lenet"


@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    return {"model_name": model_name, "message": "Have some residuals"}
```

このとき注意点として、 model_name に入るのはstringsではなく、ModelName object.

```py
model_name = {ModelName} ModelName.resnet
model_name.value = 'resnet'
```

ただし不思議な力で、 model_name をそのまま return すると model_name.value の strings が返る。


http://0.0.0.0:8000/models/foo

エラーの中にちゃんと使えるENUMが。優しい。

```json
{
detail: [
{
loc: [
"path",
"model_name"
],
msg: "value is not a valid enumeration member; permitted: 'alexnet', 'resnet', 'lenet'",
type: "type_error.enum",
ctx: {
enum_values: [
"alexnet",
"resnet",
"lenet"
]
}
}
]
}
```

### PyDantic

validationは pydantic を使ってるので困ったらここを調べる。
https://pydantic-docs.helpmanual.io/

# Query Parameters¶

ref
https://fastapi.tiangolo.com/tutorial/query-params/#query-parameters

本題だ。

## pagination

```py
fake_items_db = [{"name": "a"}, {"name": "b"}, {"name": "c"},{"name": "d"}, {"name": "e"}, {"name": "f"},{"name": "g"}, {"name": "h"}, {"name": "i"}]
@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):
    return fake_items_db[skip : skip + limit]

```


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

```json
[
{
name: "a"
},
{
name: "b"
},
{
name: "c"
}
]
```

http://127.0.0.1:8000/items/?skip=5&limit=2

```json
[
{
name: "f"
},
{
name: "g"
}
]
```

## optional parameters

types の Optional を使って、 deafult = None にするのがpoint

```py
from typing import Optional

@app.get("/items/{item_id}")
async def read_item(item_id: str, q: Optional[str] = None):
    if q:
        return {"item_id": item_id, "q": q}
    return {"item_id": item_id}
```


http://0.0.0.0:8000/items/foo

```json
{
item_id: "foo"
}

```
http://0.0.0.0:8000/items/foo?q=1

```json
{
item_id: "foo",
q: "1"
}
```


## List だけ、 keyなし の model

> Custom Root Types🔗
Pydantic models can be defined with a custom root type by declaring the __root__ field.
The root type can be any type supported by pydantic, and is specified by the type hint on the __root__ field. The root value can be passed to the model __init__ via the __root__ keyword argument, or as the first and only argument to parse_obj.

こういうとき

```py
pets : Pets = Pets(["cat", "dog"])
```

`__root__` を使う。

```py
from pydantic import BaseModel

class Pets(BaseModel):
    __root__: List[str]
```

これだと loop　できないので __iter__ をつける

```py
from pydantic import BaseModel

class Pets(BaseModel):
    __root__: List[str] 

    def __iter__(self):
        return iter(self.__root__)

    def __getitem__(self, item):
        return self.__root__[item]

    # len() が使えなかったので足してみた
    def __len__(self):
        return len(self.__root__)
```

呼び出し方法

```py
print(Pets(__root__=['dog', 'cat']))
```




## return に含める query parameter を変更する方法

最初に item を宣言して、後から必要に応じて item.update() する。

```py

@app.get("/items/{item_id}")
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
```

## bool queryが true になるとき

これ全部 true

```py
http://127.0.0.1:8000/items/foo?short=1
or


http://127.0.0.1:8000/items/foo?short=True
or


http://127.0.0.1:8000/items/foo?short=true
or


http://127.0.0.1:8000/items/foo?short=on
or


http://127.0.0.1:8000/items/foo?short=yes
```


## query param を必須にするときは default 値をつけない

必ず 値が帰ってきそうに見えるが ↓

```py
@app.get("/items/")
async def read_user_item(needy: str):
    return {"needy": 1}
```

これはokだが
http://127.0.0.1:8000/needy/?needy=1

これはNG
http://127.0.0.1:8000/needy/

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

# Request Body¶
https://fastapi.tiangolo.com/tutorial/body/


## POST を受け取る

POST はまず ojbect を定義する。継承するのは pydantic の BaseModel.
keyに対して型を定義できる。

```py
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.post("/items/")
async def create_item(item: Item):
    return item
```

POSTのテストは /docs を使うといい。

このとき、post内容に無駄なkey:valueを入れておくと、自然にomitされる。

```json
curl -X 'POST' \
  'http://0.0.0.0:8000/items/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "string",
  "description": "string",
  "price": 0,
  "tax": 0,
  "unnecessary" : "value"
}'


Response body
{
  "name": "string",
  "description": "string",
  "price": 0,
  "tax": 0
}
```

required な nameがないと、当然優しいエラー

```json
curl -X 'POST' \
  'http://0.0.0.0:8000/items/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "description": "string",     # <------
  "price": 0,
  "tax": 0
}'

Response body
{
  "detail": [
    {
      "loc": [
        "body",
        "name"
      ],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
```

## POST にアクセス

POST data は　model object で受け取るので、パラメータはmodel経由で受け取る。

item.price とか item.tax とか。


```py

@app.post("/items/")
async def create_item(item: Item):
    item_dict = item.dict()
    if item.tax:
        price_with_tax = item.price + item.tax
        item_dict.update({"price_with_tax": price_with_tax})
    return item_dict

```

## POST をそのまま return したいときは `**item.dict()`

```py

@app.put("/items/{item_id}")
async def create_item(item_id: int, item: Item):
    return {"item_id": item_id, **item.dict()} <-------
```

## key なしの POST body を受け取る `Body(...)`

これびっくりした。何だ ... って。
すっかり nodejs みたいになってるな・・


```py
@app.put("/body")
async def update_item(
    any_name: int = Body(...)
):
    results = {"any_name": any_name}
    return results
```

つまり↓の、keyなしvalueだけをpostしたときは

```json
{
	1
}
```

それを `Body(...)` で受け取れる。

```json
curl -X 'PUT' \
  'http://0.0.0.0:8000/body' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '111'

Response body
{
  "any_name": 111
}
```

これは 他のbodyと混ざっててもok.


```py
@app.put("/items/{item_id}")
async def update_item(
    item_id: int, item: Item, user: User, importance: int = Body(...)
):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results
```

まあ・・こんなことしないよね・・・

ちなみにvalidationさせることもできて、gt=100 で greater than 100になる

```py
    any_name: int = Body(..., gt=100)
```

99投げるとエラー

```py
{
  "detail": [
    {
      "loc": [
        "body"
      ],
      "msg": "ensure this value is greater than 100",
      "type": "value_error.number.not_gt",
      "ctx": {
        "limit_value": 100
      }
    }
  ]
}
```


# Query Parameters and String Validations¶


## パラメータに validation
関数の引数に Query() をつけると、その中で条件をかけれる


```py
from fastapi import FastAPI, Body, Query
from typing import Optional

@app.get("/max_length/")
async def read_items(q: str = Query(None, max_length=50)):
    return {"q": q}
```

## regex validation

```py
    q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
```

## Query(None) の None は default value
こうすると、q を指定しないで投げると勝手に 'this-is-default-value' が返ってくる。

```py
# q is required
@app.get("/default-value/")
async def default-value(q: str = Query('this-is-default-value', min_length=3)):
    return {"q": q}

```

result

```json
curl -X 'GET' \
  'http://0.0.0.0:8000/default-value/' \
  -H 'accept: application/json'

Response body
{
  "q": "this-is-default-value"
}
```

## Make it required¶

- **Query() を使っているときの Required は `Query(...)`**
    - この ... は省略記号じゃないよ！このままピリオド3つを書くんだよ！
- **Not requiredは `Query(None)` または `Query(<何かデフォルト値>)`**

わっかりづらー！ よくわからんのやけど、Query()を使うときは Optional があまり意味をなさない。
それはこの例でわかる。

```py
# q is NOT required
@app.get("/required4/")
async def required4(q: str = Query(None, min_length=3)):
    return {"q": q}
```

`q: Optional[str]` じゃないのに、これ NOT required なんです。それは `Query(None)` だから。

いろいろ試しました。↓

```py

# q is required
@app.get("/required1/")
async def required1(q: str):
    return {"q": q}

# q is NOT required
@app.get("/required2/")
async def required2(q: Optional[str] = Query(None, min_length=3)):
    return {"q": q}

# q is required
@app.get("/required3/")
async def required3(q: Optional[str] = Query(..., min_length=3)):
    return {"q": q}

# q is NOT required
@app.get("/required4/")
async def required4(q: str = Query(None, min_length=3)):
    return {"q": q}

# q is required
@app.get("/required5/")
async def required5(q: str = Query(..., min_length=3)):
    return {"q": q}

# q is required
@app.get("/required6/")
async def required6(q: str = Query('this-is-default-value', min_length=3)):
    return {"q": q}

```

#### 結論

**`Query(...)` だけが required になる。** Optionalは関係ない。 Quiery(None) または Query(default-value) は NOT required.


## query param を重複しても list で受け取れる **スゴイ**

```py
from typing import List, Optional

@app.get("/duplicated/")
async def read_items(q: Optional[List[str]] = Query(None)):
    query_items = {"q": q}
    return query_items
```

こんな無茶なurl投げると
http://0.0.0.0:8000/duplicated/?q=string1&q=string2


```json
{
  "q": [
    "string1",
    "string2"
  ]
}

```

えっ昨今のAPIってこれ当たり前なの！？！？！　おっさん死にそう・・・


## parameterの説明はコメントじゃなくて descriptionに書こう

/docs, /redoc で説明が見えるようになる。title/description は api responseには影響がない。

```json
@app.get("/items/")
async def read_items(
    q: Optional[str] = Query(
        None,
        title="Query string",
        description="Query string for the items to search in the database that have a good match",
        min_length=3,
    )
):
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results
```

## query key にハイフンが付く場合は Query(, alias)

そもそも、これで body.q が取れるのが不思議だよね

```py
async def required5(q: str = Query(...)):
```

これは引数の名前を使って fastapiが勝手にbodyから同じ 名前の q を探してくれてる。
でも、もしurlが /?key-foo=1 の場合、pythonで body.key-foo というオブジェクトは作れないので
alias　を設定する。

```json
async def read_items(q: Optional[str] = Query(None, alias="item-query")):
```

こうすると、item-query が q に代入される!

## deprecated は /docs に出るだけ

response に注意分が出るかと思ったけど、変わらなかった。

```json
@app.get("/deprecated/")
async def deprecated(q: Optional[str] = Query(None, deprecated=True)):
    return q
```

# Path Parameters and Numeric Validations¶

明日はここから
https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/

あと読むの必須 memo

Error handling
https://fastapi.tiangolo.com/tutorial/handling-errors/

SQL (Relational) Databases¶
https://fastapi.tiangolo.com/tutorial/sql-databases/

Bigger Applications - Multiple Files¶
https://fastapi.tiangolo.com/tutorial/bigger-applications/

Testing
https://fastapi.tiangolo.com/tutorial/testing/

 けっこうあるなー。一日掛かりそう

グダグダな後編はこちら
https://qiita.com/uturned0/items/16eeaf0bcb84d5c88853