8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FastAPI リクエストの Header/Body など取得方法まとめ

Posted at

この記事について

ヘッダとボディ、それからクエリパラメータ、パスパラメータの取得方法をまとめました。他にもクッキーとか色々取得できるものはあるのですが、この記事では先の 4つ (+メモ) にとどめています。

もちろん公式リファレンスはあるのですが、複数ページに分かれていて、コンパクトに 1つにまとめようと思ってこの記事にまとめた次第です。各詳細は各公式リファレンスをどうぞ。

レスポンスのまとめはこちら:

コード全文

main.py
# 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 {}
Form の例.sh
curl -X POST \
  -F "key=val" \
  "http://localhost:8000"
JSON の例.sh
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 {}
JSON の例.sh
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 エラーが返されます。

ヘッダが application/json でない.sh
# ボディがあるとき、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
ボディが JSON 構文でない.sh
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 {}

参照したもの

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?