先日の記事で、FastAPIが3日間で0.144から0.150まで版を重ね、HTMLフォームの扱いが大きく変わったことをお伝えしました。その後も公開は続き、2026年10月21日には0.156.4に達しています。
今回の続報で特に大事なのが、0.151.0の変更です。JSONで送られてきたnullと、項目そのものを省略した場合を、はっきり区別するようになりました。
nullも省略も、どちらも値がないのだから同じでは?と思う方もいるでしょう。実はこの2つは、APIの設計ではまったく違う意味を持つことがあります。
今回は、新旧の版で同じAPIを呼びながら、この違いを確かめていきます。
まずは0.151から0.156で何が変わったのか¶
最初に、前回の記事以降に公開された版の主な変更を表にまとめます。内容は、FastAPIの公式リポジトリにあるリリースノートにもとづいています。
| 版 | 公開日 | 主な変更 |
|---|---|---|
| 0.151.0 | 10月20日 | JSONのnullと省略を区別する |
| 0.152.0 | 10月20日 | ヘッダーのモデルの検証エラーで、HTTPのヘッダー名を表示する |
| 0.153.0 | 10月20日 | 複数のフォームモデルのUnionにも、フォームの新しい扱いを適用 |
| 0.154.0 | 10月20日 | リクエストの引数にPydanticの検証オプションを追加 |
| 0.155.0 | 10月20日 | 非推奨だった**extraを削除し、知らないキーワード引数はTypeErrorに |
| 0.156.0〜0.156.4 | 10月20日〜21日 | Pydanticのフィールド情報の引き継ぎに関する修正 |
0.151.0と0.155.0は、リリースノートで互換性を壊す変更として扱われています。前回の記事で取り上げた0.150.0の公開と同じ10月20日のうちに、さらに6つの版が出ていたことになります。
フォームの扱いが変わった前回の内容は、こちらで紹介しています。【関連記事】FastAPIが3日で0.150へ。HTMLフォームの扱いが変わった
nullと省略は何が違うのか¶
JSONでは、値がないことを2つの方法で表せます。項目にnullを入れる方法と、項目そのものを書かない方法です。
| 送り方 | JSONの例 | よくある意味 |
|---|---|---|
| nullを送る | {"theme": null} |
この値を空にしてほしい |
| 項目を省略する | {} |
この値には触れないでほしい |
たとえば、設定を一部だけ変える更新APIを考えてみてください。テーマの設定を消したいのか、テーマには触れていないのかは、まったく別の指示ですよね。
この2つを同じに扱うと、利用者の意図を取り違えてしまいます。Pydantic 2.14で正式になったMISSINGも、同じ問題を解決するための仕組みでした。【関連記事】Pydantic 2.14がリリース
0.151.0で何が変わったのか¶
リリースノートによると、0.151.0から、個別のBodyの引数でnullが送られた場合は、Noneとして検証されるようになりました。Noneを受け付ける型なら、そのままNoneになります。
一方で、Noneを受け付けない型の場合は、既定値が決められていてもエラーになります。既定値が使われるのは、値が省略されたときだけです。
新旧の版で同じAPIを呼んでみた¶
実際に試してみましょう。テーマと音量という2つの設定を、部分的に更新するAPIを用意しました。themeはNoneも受け付け、volumeは整数だけを受け付けます。
from typing import Annotated
from fastapi import Body, FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
@app.patch("/settings")
def update(
theme: Annotated[str | None, Body()] = "light",
volume: Annotated[int, Body()] = 50,
):
return {"theme": theme, "volume": volume}
client = TestClient(app)
print(client.patch("/settings", json={}).json())
print(client.patch("/settings", json={"theme": None}).json())
res = client.patch("/settings", json={"volume": None})
print(res.status_code, res.json()["detail"][0]["msg"] if res.status_code == 422 else res.json())
0.150.0と0.156.4を、それぞれ新しい仮想環境に入れて、手元のPython 3.11で実行した結果です。動作に関係のない警告の行は省いています。
FastAPI 0.150.0
{'theme': 'light', 'volume': 50}
{'theme': 'light', 'volume': 50}
200 {'theme': 'light', 'volume': 50}
FastAPI 0.156.4
{'theme': 'light', 'volume': 50}
{'theme': None, 'volume': 50}
422 Input should be a valid integer
1行目の、何も送らない場合は、どちらの版でも既定値が使われています。違いが出たのは、2行目と3行目です。
0.150.0では、nullが既定値で上書きされていた¶
0.150.0では、themeにnullを送っても、既定値のlightに置き換えられていました。テーマを消したいという利用者の意図が、黙って無視されていたわけです。
0.156.4では、themeにnullを送ると、きちんとNoneになりました。そして、Noneを受け付けないvolumeにnullを送ると、422のエラーで知らせてくれます。
リリースノートには、nullを送って既定値を使ってもらっていたクライアントは、代わりに値を省略するよう書かれています。APIを呼ぶ側のコードも、あわせて確認しておきましょう。
部分更新では、モデルとexclude_unsetが便利¶
実際の部分更新APIでは、Pydanticのモデルで受け取り、model_dump(exclude_unset=True)で送られてきた項目だけを取り出す書き方がよく使われます。0.156.4で試してみました。
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
class SettingsUpdate(BaseModel):
theme: str | None = None
volume: int | None = None
app = FastAPI()
@app.patch("/settings")
def update(data: SettingsUpdate):
return data.model_dump(exclude_unset=True)
client = TestClient(app)
print(client.patch("/settings", json={"volume": 80}).json())
print(client.patch("/settings", json={"theme": None}).json())
実行結果は次のとおりです。
{'volume': 80}
{'theme': None}
1つ目は、送られてきたvolumeだけが取り出され、触れていないthemeは含まれていません。2つ目は、明示的にnullを送ったthemeが、空にしたいという指示として残りました。
あとは、この結果に含まれる項目だけをデータベースに書き込めば、利用者の意図どおりの部分更新ができます。nullと省略を区別する考え方が、そのまま形になった書き方です。
JSONの本体全体がnullの場合¶
リリースノートによると、Body(embed=True)を使う場合や、本体の引数が複数ある場合のように、JSONのオブジェクトを期待しているところへ、本体全体としてnullを送ると、検証エラーになるようになりました。
これも、値がないことを、あいまいなまま受け流さないための変更です。JSONの基本的な扱いについては、こちらで紹介しています。【関連記事】PythonでJSONを扱う方法
知らないキーワード引数はエラーに¶
もう1つの互換性を壊す変更が、0.155.0です。Path、Query、Header、Cookie、Body、Form、Fileに渡せた、非推奨の**extraという仕組みが削除されました。
これまでは、知らない名前のキーワード引数を渡しても、黙って受け入れられていました。0.155.0からは、TypeErrorになります。
実際に、Query(custom="value")を2つの版で実行してみました。
| 版 | 結果 |
|---|---|
| 0.150.0 | エラーにならずに受け入れられた |
| 0.156.4 | TypeError: Query() got an unexpected keyword argument 'custom' |
リリースノートによると、OpenAPIの仕様書に独自の情報を加えたい場合は、Query(json_schema_extra={"custom": "value"})のように書き換えます。引数の名前を打ち間違えたときにも、エラーで教えてもらえるようになったのは嬉しいところです。
私は10年ほどエンジニアとして開発に関わってきましたが、引数の名前のちょっとした打ち間違いが黙って無視され、設定が効かない原因を探して半日つぶしたことがあります。間違いをその場でエラーにしてくれる変更は、長い目で見ると開発をずっと楽にしてくれます。
そのほかの変更¶
ほかにも、いくつかの改善が入っています。代表的なものを紹介します。
0.152.0では、ヘッダーをまとめたモデルの検証エラーに、Pythonのフィールド名ではなく、実際のHTTPのヘッダー名が表示されるようになりました。リリースノートの例では、int_valではなくint-valと表示されます。
0.154.0では、リクエストの引数に、Pydanticの検証オプションを指定できるようになりました。0.156.0から0.156.4までは、PydanticのField()で付けた情報が、正しく引き継がれるようにする修正が続いています。
FastAPIそのものの基本は、こちらで紹介しています。【関連記事】PythonのFastAPIとは?
上げる前にやっておきたいこと¶
最後に、0.150以前から上げる場合に確かめておきたいことを整理します。前回の記事のフォームの確認とあわせて見てください。
| 順番 | 確認すること |
|---|---|
| 1 | JSONでnullを送って既定値を使ってもらうクライアントがないか |
| 2 | Noneを受け付けない型に、nullが届く可能性がないか |
| 3 | Queryなどに、知らない名前のキーワード引数を渡していないか |
| 4 | ヘッダーの検証エラーの文面に頼ったテストがないか |
| 5 | 使うFastAPIの版をrequirements.txtなどで固定しているか |
以前、APIの仕様書にnullと省略の違いを書かずにいたせいで、呼ぶ側のチームと解釈が食い違い、データを消してしまう寸前までいったことがあります。nullと省略をどう扱うかは、APIを作る側と使う側で必ず言葉にしておくことをおすすめします。
まとめ¶
FastAPIの連続リリースは、2026年10月21日の0.156.4まで続きました。0.151.0ではJSONのnullと省略を区別するようになり、0.155.0では知らないキーワード引数がTypeErrorになっています。
値を空にしたいのか、触れたくないのか。この違いをフレームワークがきちんと区別してくれるのは、更新APIを作るうえで大きな安心材料です。上げる前に、上の確認ポイントを一度見直してみてください。
ここまでお読みいただきありがとうございました。