この記事について
ヘッダとボディ、それからクエリパラメータ、パスパラメータの取得方法をまとめました。他にもクッキーとか色々取得できるものはあるのですが、この記事では先の 4つ (+メモ) にとどめています。
もちろん公式リファレンスはあるのですが、複数ページに分かれていて、コンパクトに 1つにまとめようと思ってこの記事にまとめた次第です。各詳細は各公式リファレンスをどうぞ。
レスポンスのまとめはこちら:
コード全文
# coding: UTF-8
from fastapi import FastAPI #
import uvicorn
app = FastAPI()
@app.post("/") #
def any_route(): #
print("any result here") #
return {} #
if __name__ == "__main__":
uvicorn.run("main:app", reload=True)
以降、記事内では上コードの #
部分のみ記述します。
Python スクリプトを実行させた状態で、別ターミナルから curl コマンドで HTTP リクエストを送ります。
Header
全部
from fastapi import FastAPI, Request
@app.post("/")
def any_route(request: Request):
print( request.headers )
print(dict(request.headers)) # 辞書型に変換
return {}
curl -X POST \
"http://localhost:8000"
指定ヘッダのみ
def
の引数名が取得するヘッダ名の指定になります。(ヘッダ名の -
は、引数名で _
と表現するようです。)
これは以降のクエリパラメータや Form データ等も同様です。
from fastapi import FastAPI, Header
@app.post("/")
def any_route(custom_header: str = Header()):
print(custom_header)
return {}
curl -X POST \
-H "custom-header: header" \
"http://localhost:8000"
Header()
にデフォルト値を設定できます。これにより当該ヘッダは必須でなく任意になります。
以降も同様で、デフォルト値を設定した値は任意になります。(詳細は後述)
def any_route(custom_header: str = Header("default")):
クエリパラメータ
全部
from fastapi import FastAPI
@app.post("/")
def any_route(request: Request):
print( request.query_params )
print(dict(request.query_params)) # 辞書型に変換
return {}
curl -X POST \
"http://localhost:8000?query=val"
指定キーのみ
from fastapi import FastAPI, Query
@app.post("/")
def any_route(query: str = Query()):
print(query)
return {}
= Query()
は無くても等価。
def any_route(query: str = Query()):
# 等価
def any_route(query: str):
curl -X POST \
"http://localhost:8000?query=val"
以下のように書くとデフォルト値を設定できます。
def any_route(query: str = Query("default")):
def any_route(query: str = "default"):
パスパラメータ
全部
from fastapi import FastAPI, Request
@app.post("/{path}")
def any_route(request: Request):
print(request.path_params) # Header や Query と違い、なぜかこちらは初めから dict
return {}
curl -X POST \
"http://localhost:8000/path_name"
@app...
に指定した名前をキーに、入力されたパス名を値に持つ辞書型オブジェクトが得られます。上の例では {'path': 'path_name'}
という値が得られます。
指定パスのみ
from fastapi import FastAPI
@app.post("/{path}")
def any_route(path: str):
print(path)
return {}
curl -X POST \
"http://localhost:8000/path_name"
Header()
や Query()
と異なり、デフォルト値設定はできません (常に必須値)。パス抜きだと別のルートで処理されて、ルートがなければ 404 エラーになります。
body
全部
from fastapi import FastAPI, Request
@app.post("/")
async def any_route(request: Request):
print(await request.body())
return {}
curl -X POST \
-F "key=val" \
"http://localhost:8000"
curl -X POST \
-H "content-type: application/json" \
-d '{"key":"val"}' \
"http://localhost:8000"
bytes
型で得られるので、自分で細々した処理をする必要があります。デバッグ用途を除いて推奨できません。
Form
from fastapi import FastAPI, Form
@app.post("/")
def any_route(form_key: str = Form()):
print(form_key)
return {}
curl -X POST \
-F "form_key=val" \
"http://localhost:8000"
Form()
にデフォルト値を設定できます。
def any_route(key: str = Form("default")):
Form (ファイル受信)
@app.post("/")
def any_route(form_file: UploadFile = File()):
print(form_file.filename) # ファイル名
print(form_file.file.read()) # ファイルの中身 (bytes 出力)
return {}
curl -X POST \
-F "form_file=@any-file.txt" \
"http://localhost:8000"
ファイルの中身だけ直接 bytes で得ることも可
型アノテーションを bytes
にする。
@app.post("/")
def any_route(form_file: bytes = File()):
print(form_file)
return {}
この場合、ファイル名や MIME タイプの情報は失われます。
File()
にデフォルト値を設定でき、これにより当該値は必須でなく任意になります。(詳細は後述)
@app.post("/")
def any_route(form_file: UploadFile | None = File(None)):
if form_file == None:
form_file = UploadFile(open("any-file.txt", "rb"), filename="any-file.txt")
print(form_file.filename)
print(form_file.file.read())
return {}
File()
に直接 UploadFile(...)
を書こうとしたら TypeError: cannot pickle '_io.TextIOWrapper' object
というエラーが発生。なので、一度 None
を入れる形で対応。
JSON (スキーマなし)
from fastapi import FastAPI
@app.post("/")
def any_route(json_data: dict):
print(json_data)
return {}
curl -X POST \
-H "content-type: application/json" \
-d '{"json_key":"val"}' \
"http://localhost:8000"
この書き方も一応可 (でもあまりオススメできないかも)
from fastapi import FastAPI, Request
@app.post("/")
async def any_route(request: Request):
print(await request.json())
return {}
JSON でないデータが送られたとき、Internal Server Error となってしまいます。また、Request
の import が必要、async
にしないといけないなど、少し面倒です。
ヘッダが application/json
でないとき、またはボディが JSON 構文でないときは、422 エラーが返されます。
# ボディがあるとき、content-type は デフォルトで application/x-www-form-urlencoded
curl -X POST \
-d '{"key":"val"}' \
"http://localhost:8000"
# {"detail":[{"loc":["body"],"msg":"value is not a valid dict","type":"type_error.dict"}]}
# ステータス: 422 Unprocessable Entity
curl -X POST \
-H "Content-Type: application/json" \
-d 'text' \
"http://localhost:8000"
# {"detail":[{"loc":["body",0],"msg":"Expecting value: line 1 column 1 (char 0)","type":"value_error.jsondecode","ctx":{"msg":"Expecting value","doc":"text","pos":0,"lineno":1,"colno":1}}]}
# ステータス: 422 Unprocessable Entity
デフォルト値を設定すると、空ボディでも処理を進行するようにできます。
from fastapi import FastAPI
@app.post("/")
def any_route(json_data: dict = {}):
print(json_data)
return {}
JSON (スキーマあり)
スキーマ無しと違い、各値の有無チェックや型チェックをやってくれます。
from fastapi import FastAPI
from pydantic import BaseModel
class JsonScheme(BaseModel):
# ~~~~~~~~~~ 任意の名前にできる
key1: str
key2: int
@app.post("/")
def any_route(json_body: JsonScheme):
print(json_body.key1) # 各値を得る
print(json_body.key2) #
print(json_body.dict()) # 辞書型で得る
return {}
curl -X POST \
-H "Content-Type: application/json" \
-d '{"key1":"value1","key2":2}' \
"http://localhost:8000"
JsonScheme
を以下のように書くとデフォルト値を設定でき、これにより当該の値は必須でなく任意になります。
class JsonScheme(BaseModel):
key1: str = "default"
key2: int = 1
curl -X POST \
-H "Content-Type: application/json" \
-d '{}' \
"http://localhost:8000"
# 空でもカッコは必要 (でないと 422 エラーになる)
その他メモ
型アノテーションについて
ここまでのコード例で、def
の引数に対し str
型のアノテーションを付けてきました。
@app.post("/")
def any_route(custom_header: str = Header()):
# ~~~ コレ
print(custom_header)
return {}
無くても動作しますが、あると型チェックをやってくれます。例えば int
としたとき、整数に変換できない値が来たときにはエラーを返してくれます。
@app.post("/")
def any_route(custom_header: int = Header()):
print( custom_header )
print(type(custom_header))
return {}
curl -X POST \
-H "custom-header: header" \
http://localhost:8000
# {"detail":[{"loc":["header","custom-header"],"msg":"value is not a valid integer","type":"type_error.integer"}]}
# ステータス: 422 Unprocessable Entity
必須/任意の指定 (デフォルト値設定)
Header 受け取りのコードを例に。
@app.post("/")
def any_route(custom_header: str = Header()):
print(custom_header)
return {}
上の例で custom-header
ヘッダのないリクエストに対しては、処理が実行されず、エラー内容の記された JSON と共に 422 レスポンスが返されます。
curl -X POST \
http://localhost:8000
# {"detail":[{"loc":["header","custom-header"],"msg":"field required","type":"value_error.missing"}]}
# ステータス: 422 Unprocessable Entity
先で示したそれぞれのデフォルト値設定を行うと、当該の値は任意になります。
@app.post("/")
def any_route(custom_header: str = Header("default")):
print(custom_header)
return {}
curl -X POST \
-H "custom-header: header" \
http://localhost:8000
curl -X POST \
http://localhost:8000
# いずれでもエラーにならない
デフォルト値を None
にすることで、指定ヘッダの有無を確実に判断できます。
@app.post("/")
def any_route(custom_header: str | None = Header(None)):
if(custom_header == None):
print("No header specified")
else:
print("custom_header: " + custom_header)
return {}
参照したもの