PythonのWebフレームワークの土台として使われているStarletteの1.9.0が2026年10月18日に、1.10.0が10月24日に公開されました。どちらも、FastAPIを使っている人に関係する変更を含んでいます。
特に嬉しいのが、テスト用のTestClientの変更です。少しずつ届く応答を、届いた順にその場で読めるようになりました。
一方で、URLの組み立て方や、クエリパラメータを比べたときの結果も変わっています。今回は、1.8.0と1.10.0で同じコードを動かしながら、何が変わったのかを見ていきます。
まずはStarletteとはどんなものか¶
最初に、Starletteを知らない方のために、どんなライブラリなのかを簡単に紹介します。Starletteは、ASGIと呼ばれる非同期のWebアプリの決まりごとに沿って作られた、軽量なWebフレームワークです。
その名前を直接聞く機会は少ないかもしれません。けれども、FastAPIはこのStarletteの上に作られていて、リクエストの受け取りやテストの仕組みなど、多くの部分をStarletteに任せています。
FastAPIとの関係¶
たとえば、FastAPIのテストで使うfastapi.testclient.TestClientは、中身を見るとStarletteのTestClientをそのまま読み込んでいるだけです。手元のFastAPIでファイルを確かめると、1行だけの次のような中身でした。
from starlette.testclient import TestClient as TestClient # noqa
PyPIの記録によると、FastAPI 0.162.0が求めるのはstarlette>=0.46.0です。つまり、FastAPIを入れ直すと、Starletteの最新版が一緒に入ることがあります。
FastAPIそのものについては、こちらで紹介しています。【関連記事】PythonのFastAPIとは?
1.9と1.10で何が変わったのかを整理しよう¶
続いて、2つの版の主な変更を表にまとめます。内容は、Starletteの公式リポジトリにある変更履歴release-notes.mdにもとづいています。
| 版 | 公開日 | 主な変更 |
|---|---|---|
| 1.9.0 | 10月18日 | TestClientが、応答を少しずつ受け取れるように |
| 1.9.0 | 10月18日 | request.urlを組み立てるとき、パスの中の?や#を符号化するように |
| 1.10.0 | 10月24日 | QueryParamsやFormDataの比べ方を変更 |
| 1.10.0 | 10月24日 | 項目の多いフォームで、メモリと処理の負担を軽く |
| 1.10.0 | 10月24日 | 送られてきたフォームの最後の項目を読み落とす問題などを修正 |
ひとつ前の1.8.0は、Python 3.10のサポートを終えた版でした。その内容は、こちらでまとめています。【関連記事】Starlette 1.8も3.10を卒業。主要ライブラリの対応状況
TestClientで、少しずつ届く応答を確かめられるように¶
1.9.0のいちばんの変更が、TestClientです。変更履歴によると、client.stream()が、アプリの処理が終わる前に戻ってくるようになりました。
少しずつ届く応答とは¶
Webアプリの応答は、全部できあがってから一度に返すものばかりではありません。生成AIのチャットのように、できた部分から順に送る応答もあります。
Starletteでは、こうした応答をStreamingResponseで作れます。中身は、yieldで少しずつ値を返すジェネレーターです。
yieldの基本については、こちらで紹介しています。【関連記事】Pythonのyieldとは?
1.8.0と1.10.0で動かしてみた¶
実際に試してみましょう。1秒おきに数字を1行ずつ送るアプリを作り、TestClientで届いた時間を測ります。
import asyncio
import time
from starlette.applications import Starlette
from starlette.responses import StreamingResponse
from starlette.routing import Route
from starlette.testclient import TestClient
async def numbers():
for i in range(3):
yield f"{i}\n"
await asyncio.sleep(1)
async def endpoint(request):
return StreamingResponse(numbers(), media_type="text/plain")
app = Starlette(routes=[Route("/numbers", endpoint)])
client = TestClient(app)
start = time.perf_counter()
with client.stream("GET", "/numbers") as res:
for line in res.iter_lines():
print(f"{time.perf_counter() - start:.1f}秒: {line}")
1.8.0と1.10.0を、それぞれ新しい仮想環境に入れ、手元のPython 3.11で実行した結果です。動作に関係のない警告の行は省いています。
Starlette 1.8.0
3.0秒: 0
3.0秒: 1
3.0秒: 2
Starlette 1.10.0
0.0秒: 0
1.0秒: 1
2.0秒: 2
1.8.0では、アプリがすべて送り終わる3秒後に、3行がまとめて届いていました。1.10.0では、0秒、1秒、2秒と、送られた順にその場で届いています。
何が嬉しいのか¶
これまでのTestClientでは、少しずつ届くことそのものを確かめられませんでした。最初の1行がすぐ届くか、という大事な性質を、テストで見られるようになったわけです。
変更履歴によると、読み終わる前に応答を閉じると、アプリの処理も取り消されるようになりました。無限に送り続けるような応答でも、テストが終わらなくなる心配が減ります。
テストの書き方そのものは、こちらで紹介しています。【関連記事】pytest入門|Pythonでテストを書く基本
request.urlの組み立て方が変わった¶
1.9.0では、URLの扱いも直されています。変更履歴によると、request.urlを組み立てるときに、リクエストのパスに含まれる?や#、バックスラッシュ、先頭の//を、パーセント符号化するようになりました。
パーセント符号化とは、URLの中で特別な意味を持つ文字を、%と2桁の数字で表す書き方です。たとえば?は、%3Fと書き表します。
実際に試してみた¶
/a%3Fbというパスにアクセスしたときに、request.urlがどうなるかを2つの版で比べました。パスの一部として?を含む、少し変わったURLです。
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
async def home(request):
return PlainTextResponse(str(request.url))
app = Starlette(routes=[Route("/{path:path}", home)])
client = TestClient(app, base_url="http://example.com")
print(client.get("/a%3Fb").text)
実行した結果です。
Starlette 1.8.0
http://example.com/a?b
Starlette 1.10.0
http://example.com/a%3Fb
1.8.0では、パスの中の?がそのまま戻され、?bがクエリパラメータのように見えるURLになっていました。1.10.0では%3Fのまま組み立てられ、元のURLと同じ意味を保っています。
request.urlの値を、ログに残したり、リダイレクト先に使ったりしている場合は、上げたあとに一度確かめておきましょう。
QueryParamsの比べ方が変わった¶
1.10.0では、クエリパラメータを表すQueryParamsや、フォームの中身を表すFormDataを、==で比べたときの結果が変わりました。変更履歴によると、キーごとに比べつつ、同じキーに複数の値がある場合は、その順番も区別するようになっています。
2つの版で比べてみた¶
まずは、クエリパラメータの比較を試してみます。
from starlette.datastructures import QueryParams
print(QueryParams("a=1&a=2") == QueryParams("a=2&a=1"))
print(QueryParams("a=1&b=2") == QueryParams("b=2&a=1"))
実行した結果です。
| 比べたもの | 1.8.0 | 1.10.0 |
|---|---|---|
a=1&a=2とa=2&a=1 |
True | False |
a=1&b=2とb=2&a=1 |
True | True |
同じaというキーに、1と2が逆の順番で入っている場合は、1.10.0では別物と判断されました。違うキーの並び順だけが違う場合は、これまでどおり同じと判断されます。
ファイルを複数送るフォームでのエラーも直った¶
変更履歴によると、同じ名前で複数のファイルを送ったFormDataは、比べるとTypeErrorになっていました。手元でも、1.8.0では'<' not supported between instances of 'UploadFile' and 'UploadFile'というエラーが出ました。
1.10.0では、同じ比較がエラーにならず、Falseが返ってきました。ファイルどうしを大小で並べ替えようとして失敗していた処理が、なくなったわけです。
そのほかの修正¶
ほかにも、細かな修正がたくさん入っています。初心者にも関係しそうなものを紹介します。
1.10.0では、送られてきたフォームの最後の項目を、条件によって読み落とす問題が直されました。変更履歴によると、リクエストの本体が、空の区切りを送らずに終わった場合に起きていたそうです。
また、.envのような設定ファイルから値を読むConfigで、値を囲む引用符を、外側の1組だけ取り除くように直されています。1.9.0では、Hostによる振り分けやTrustedHostMiddlewareで、ホスト名の大文字と小文字を区別しないようにもなりました。
上げる前にやっておきたいこと¶
最後に、Starletteを1.10.0へ上げる前に確かめておきたいことを整理します。FastAPIを使っている方も同じです。
| 順番 | 確認すること |
|---|---|
| 1 | Python 3.11以上を使っているか |
| 2 | TestClientのstream()を使ったテストが、新しい動きで通るか |
| 3 | request.urlの値を、ログやリダイレクトに使っていないか |
| 4 | QueryParamsやFormDataを==で比べていないか |
| 5 | 使うStarletteの版を、requirements.txtなどで固定しているか |
私は10年ほどエンジニアとして開発に関わってきましたが、少しずつ応答を返すAPIのテストで、最初のデータがすぐ届くかを確かめられず、本番で初めて遅さに気づいたことがあります。テストで確かめられることが増えるのは、地味ですが大きな前進です。
FastAPIの最近の大きな変更については、こちらでまとめています。【関連記事】FastAPIの変更は0.156まで続く
まとめ¶
Starletteの1.9.0が2026年10月18日に、1.10.0が10月24日に公開されました。TestClientで少しずつ届く応答を確かめられるようになり、request.urlの組み立て方やQueryParamsの比べ方も変わっています。
FastAPIを使っている方にとっても、テストの書き方が広がる嬉しい変更です。上げたあとは、上の確認ポイントを一度見直してみてください。
ここまでお読みいただきありがとうございました。