Open11
FastAPIで遊ぶ
HelloWorld
main.py
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: str = None):
return {"item_id": item_id, "q": q}
確認
$ curl localhost:8080
{"Hello":"World"}
$ curl localhost:8080/items/1
{"item_id":1,"q":null}
パスパラメータ
単一
main.py
@app.get("/path/{path_id}")
def read_path(path_id: int):
return {"path_id": path_id}
$ curl localhost:8080/user/1/items/1
{"user_id":1,"item_id":1}
複数
main.py
@app.get("/user/{user_id}/items/{item_id}")
def read_user_item(user_id: int, item_id: int):
return {"user_id": user_id, "item_id": item_id}
$ curl localhost:8080/user/1/items/1
{"user_id":1,"item_id":1}
文字列
main.py
@app.get("/users/{user_name}")
def read_user(user_name: str):
return {"user_name": user_name}
$ curl localhost:8080/users/not75743
{"user_name":"not75743"}
クエリパラメータ
シンプル
main.py
@app.get("/items")
def read_items(int1: int = 1, int2: int = 2):
return {"int1": int1, "int2": int2}
$ curl localhost:8080/items
{"int1":1,"int2":2}
$ curl localhost:8080/items?int1=100
{"int1":100,"int2":2}
$ curl localhost:8080/items?int1=100\&int2=1000
{"int1":100,"int2":1000}
オプショナル
main.py
from typing import Optional
@app.get("/items2")
def read_items2(int1: Optional[int] = None, int2: int = 10):
if int1:
return {"int1": int1, "int2": int2}
return {"int2": int2}
$ curl localhost:8080/items2
{"int2":10}
$ curl localhost:8080/items2?int2=10000000
{"int2":10000000}
$ curl localhost:8080/items2?int1=50000\&int2=10000
{"int1":50000,"int2":10000}
バージョンによりいろいろな書き方が
パスパラメータとの併用
main.py
from typing import Optional
@app.get("/items3/{item_id}")
def read_items3(item_id: int, int1: Optional[int] = None, int2: int = 10):
if int1:
return {"item_id": item_id, "int1": int1, "int2": int2}
return {"item_id": item_id, "int1": int1, "int2": int2}
$ curl localhost:8080/items3/777?int2=20
{"item_id":777,"int1":null,"int2":20}
$ curl localhost:8080/items3/777?int1=30\&int2=20
{"item_id":777,"int1":30,"int2":20}
ドキュメント
/docs
にアクセスすることで対話的なドキュメントが見れる
パス、クエリパラメータ、それが必須かどうか、などの様々な情報が見れる
pydanticによるデータの検証
main.py
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
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
curlで確認
## 成功
$ curl -X POST "curl -X POST "http://localhost:8080/items/" \
-H "Content-Type: application/json" \
-d '{
"name": "sample item",
"description": "A sample item description",
"price": 15.99,
"tax": 1.5
}'
{"name":"sample item","description":"A sample item description","price":15.99,"tax":1.5}
## nameが不正のためエラー
$ curl -s -X POST "http://localhost:8080/items/" \
-H "Content-Type: application/json" \
-d '{
"name": 3,
"description": "A sample item description",
"price": 150,
"tax": 1.5
}' | jq
{
"detail": [
{
"type": "string_type",
"loc": [
"body",
"name"
],
"msg": "Input should be a valid string",
"input": 3,
"url": "https://errors.pydantic.dev/2.5/v/string_type"
}
]
}
## optionalのため成功
$ curl -X POST "curl -X POST "http://localhost:8080/items/" \
-H "Content-Type: application/json" \
-d '{
"name": "sample item",
"price": 15.99
}'
{"name":"sample item","description":null,"price":15.99,"tax":null}
リクエストボディの表示
バリデーションの追加
文字数を50に制限
main.py
@app.get("/items/")
async def read_items(q: Optional[str] = Query(None, max_length=20)):
return {"query": q}
$ curl localhost:8080/items?q=aaaaaaaaaaaaaaaaaaaa
{"query":"aaaaaaaaaaaaaaaaaaaa"}
$ curl -s localhost:8080/items?q=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | jq
{
"detail": [
{
"type": "string_too_long",
"loc": [
"query",
"q"
],
"msg": "String should have at most 20 characters",
"input": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"ctx": {
"max_length": 20
},
"url": "https://errors.pydantic.dev/2.5/v/string_too_long"
}
]
}
その他いろいろなバリデーション
docstringでdescription
main.py
@app.get("/items")
async def read_items(q: Optional[str] = Query(None, max_length=20)):
"""
Create an item with all the information:
- **name**: each item must have a name
- **description**: a long description
- **price**: required
- **tax**: if the item doesn't have tax, you can omit this
- **tags**: a set of unique tag strings for this item
"""
return {"query": q}
見た目
マークダウン使えるんですね
SummaryとDescription
main.py
@app.get(
"/items",
summary="Create an item",
description="Create an item with all the information, name, description, price, tax and a set of unique tags",
)
async def read_items(q: Optional[str] = Query(None, max_length=20)):
return {"query": q}
見た目
docstringだったりSummaryだったりは以下を参照
パスのバリデーション
main.py
from fastapi import FastAPI, Path
app = FastAPI(
title="fastapi test",
)
@app.get("/items/{item_id}")
async def read_items(
item_id: int = Path(title="The ID of the item", ge=1, le=10)
):
return {"item_id": item_id}
# 1文字のint、成功
$ curl -X 'GET' \
'http://localhost:8080/items/1' \
-H 'accept: application/json'
{"item_id":1}
# 11文字のint、失敗
$ curl -s -X 'GET' \
'http://localhost:8080/items/11' \
-H 'accept: application/json' | jq
{
"detail": [
{
"type": "less_than_equal",
"loc": [
"path",
"item_id"
],
"msg": "Input should be less than or equal to 10",
"input": "11",
"ctx": {
"le": 10
},
"url": "https://errors.pydantic.dev/2.5/v/less_than_equal"
}
]
}
パスのバリデーション Annotated
Annotated
を使うのが望ましい
バージョンによっては使えないっぽい?
main.py
@app.get("/items-annotated/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item", ge=1, le=10)]
):
return {"item_id": item_id}
複数のモデルを受け取る
main.py
from typing import Optional
from fastapi import Body, FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
class User(BaseModel):
username: str
full_name: Optional[str] = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item,
user: User,
importance: int = Body(..., gt=0),
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results