後編はこちら
https://qiita.com/uturned0/items/16eeaf0bcb84d5c88853
FastAPIを使ってみる
ref
https://fastapi.tiangolo.com/tutorial/
install
pip install fastapi[all]
run
pycharmで動くように、こうする
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を作る。
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/
さあ見てみよう
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
@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
@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
{
detail: [
{
loc: [
"path",
"item_id"
],
msg: "value is not a valid integer",
type: "type_error.integer"
}
]
}
Use ENUM
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.
model_name = {ModelName} ModelName.resnet
model_name.value = 'resnet'
ただし不思議な力で、 model_name をそのまま return すると model_name.value の strings が返る。
エラーの中にちゃんと使えるENUMが。優しい。
{
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
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]
[
{
name: "a"
},
{
name: "b"
},
{
name: "c"
}
]
[
{
name: "f"
},
{
name: "g"
}
]
optional parameters
types の Optional を使って、 deafult = None にするのがpoint
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}
{
item_id: "foo"
}
{
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.
こういうとき
pets : Pets = Pets(["cat", "dog"])
__root__
を使う。
from pydantic import BaseModel
class Pets(BaseModel):
__root__: List[str]
これだと loop できないので iter をつける
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__)
呼び出し方法
print(Pets(__root__=['dog', 'cat']))
return に含める query parameter を変更する方法
最初に item を宣言して、後から必要に応じて item.update() する。
@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
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 値をつけない
必ず 値が帰ってきそうに見えるが ↓
@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/
detail: [
{
loc: [
"query",
"needy"
],
msg: "field required",
type: "value_error.missing"
}
]
}
Request Body¶
POST を受け取る
POST はまず ojbect を定義する。継承するのは pydantic の BaseModel.
keyに対して型を定義できる。
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される。
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がないと、当然優しいエラー
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 とか。
@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()
@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 みたいになってるな・・
@app.put("/body")
async def update_item(
any_name: int = Body(...)
):
results = {"any_name": any_name}
return results
つまり↓の、keyなしvalueだけをpostしたときは
{
1
}
それを Body(...)
で受け取れる。
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.
@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になる
any_name: int = Body(..., gt=100)
99投げるとエラー
{
"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() をつけると、その中で条件をかけれる
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
q: Optional[str] = Query(None, min_length=3, max_length=50, regex="^fixedquery$")
Query(None) の None は default value
こうすると、q を指定しないで投げると勝手に 'this-is-default-value' が返ってくる。
# 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
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 があまり意味をなさない。
それはこの例でわかる。
# 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)
だから。
いろいろ試しました。↓
# 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 で受け取れる スゴイ
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
{
"q": [
"string1",
"string2"
]
}
えっ昨今のAPIってこれ当たり前なの!?!?! おっさん死にそう・・・
parameterの説明はコメントじゃなくて descriptionに書こう
/docs, /redoc で説明が見えるようになる。title/description は api responseには影響がない。
@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 が取れるのが不思議だよね
async def required5(q: str = Query(...)):
これは引数の名前を使って fastapiが勝手にbodyから同じ 名前の q を探してくれてる。
でも、もしurlが /?key-foo=1 の場合、pythonで body.key-foo というオブジェクトは作れないので
alias を設定する。
async def read_items(q: Optional[str] = Query(None, alias="item-query")):
こうすると、item-query が q に代入される!
deprecated は /docs に出るだけ
response に注意分が出るかと思ったけど、変わらなかった。
@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