本文へスキップ
タンヴィル・アハメド

Python・バックエンド

FastAPI 0.146へのアップグレード:root_pathの非推奨化とCLIの変更

FastAPI 0.146ではFastAPI(root_path=...)が非推奨になりました。サブパスの設定をCLIやASGIサーバーへ移行し、プロキシ設定やテストを更新する方法を解説します。

著者:タンヴィル・アハメド・公開 ・約17分で読めます

AIの支援を受けて調査・執筆した記事です。出典へのリンクは以下に掲載しています。

Read this article in English

Nginx、Traefik、AWS Application Load Balancerなど、パスを削除するリバースプロキシの背後で動作するアプリケーションでは、対話型のOpenAPIドキュメントやURL生成処理に外部プレフィックスを認識させるため、FastAPI()コンストラクタにroot_pathを直接渡すことがよくあります。2026年10月18日にリリースされたFastAPI 0.146.0では、FastAPI()へのroot_pathの指定が公式に非推奨となり、起動時にFastAPIDeprecationWarningが出力されるようになりました。FastAPI 0.145.0でのopenapi_prefixおよび**extraキーワード引数の削除とあわせて、Pythonのアプリケーション定義とASGIサーバーのデプロイパラメーターとの境界がより明確に分離されています。本記事では、FastAPI 0.143から0.146までのすべての変更点、アプリケーションファクトリからfastapi runやUvicornへroot_pathを移行する方法、そしてサブパスのルーティングを壊さずにOpenAPIのプロキシ設定やテストスイートを更新する方法を解説します。

なぜFastAPI 0.146.0でアプリケーションのroot_pathパラメーターが非推奨になったのか

ASGI仕様において、root_pathは特定の問題を解決するために存在します。外部のリバースプロキシが/api/v1/appのようなサブパスで待ち受け、/api/v1プレフィックスを取り除いたうえで、ローカルポートで待ち受けるバックエンドサーバーへ/appとしてリクエストを転送するケースです。また、親となるASGIアプリケーションの内部にサブアプリケーションをマウントする用途でも使われます。バックエンドアプリケーションが相対URLを生成したり、/docs用のOpenAPIスキーマを構築したりする際には、外部の呼び出し元が/api/v1経由でアクセスしてくることを把握しておく必要があります。

これまでFastAPIでは、このプレフィックスを2つの場所で設定できました。ASGIサーバーのコマンドライン(uvicorn --root-path /api/v1またはfastapi run --root-path /api/v1)と、FastAPI(root_path="/api/v1")を使ったPythonコード内です。更新されたBehind a Proxyガイドに記載されているとおり、FastAPI(root_path=...)パラメーターはサーバー側の設定と挙動が一貫していなかったため、非推奨となりました。

root_pathがFastAPI()にのみ渡されていた場合、Starletteおよび基盤となるASGIサーバーは、--root-pathで設定されたASGIサーバーと同じようには受信リクエストのscope["root_path"]を設定しませんでした。この不一致により、設定をPythonコードに書くかサーバーの起動コマンドに書くかによって、ルート、マウントされたサブアプリケーション、リクエストスコープの挙動に微妙な食い違いが生じていました。

FastAPI 0.146.0(PR #16520)では、FastAPIクラスリファレンスにおいてFastAPI.__init__のroot_pathパラメーターに@typing_extensions.deprecatedが付与され、空でない文字列が渡されると実行時に警告が発生するようになりました。PEP 702(deprecated())をサポートする静的型チェッカーはエディタやCIパイプラインでこのパラメーターを検出し、実行時の初期化処理では次のチェックが実行されます。

if root_path:
    warnings.warn(
        "The `root_path` parameter is deprecated. Configure the "
        "server's root path instead, for example with "
        "`fastapi run --root-path /api/v1`.",
        category=FastAPIDeprecationWarning,
        stacklevel=2,
    )
self.root_path = root_path

FastAPI()からroot_pathを排除することで、ポータビリティも向上します。Pythonのアプリケーションインスタンスは、自身がローカルコンテナの/、プレビュー環境の/staging/api、本番環境の/api/v1のどこにデプロイされているかを知るべきではありません。そうしたトポロジーは、プロセスランナーとリバースプロキシの責務です。

FastAPI 0.143から0.146までのその他の変更点

FastAPI 0.143.x以前から0.146.0へ直接アップグレードする場合、2026年10月のリリース期間中にFastAPIリリースノートへ記載された他の変更点も、起動処理、ルーティング、テストに影響する可能性があります。

0.145.0での破壊的な削除:openapi_prefixとextraキーワード引数

FastAPI 0.145.0(PR #16518)では、長らく非推奨だった2つのパラメーター、openapi_prefixと任意の追加キーワード引数(**extra)がFastAPI.__init__から削除されました。

以前のバージョンでは、FastAPI(custom_setting="value")のように未定義のキーワード引数を渡したり、レガシーなopenapi_prefix="/api/v1"パラメーターを使用したりしても、**extraに吸収されるか非推奨の警告が出力されるだけでした。0.145.0以降では、いずれかを渡すとインポート時またはアプリケーションファクトリの実行時に即座にTypeErrorが送出されます。初期化時にFastAPIインスタンスへカスタムメタデータや設定を付与している場合は、コンストラクタのキーワード引数として渡すのではなく、FastAPI()オブジェクトの作成後にapp.stateへそれらの属性を保存してください。

0.145.0でのテンプレートURLパスの更新

同じくFastAPI 0.145.0(PR #16517)では、Templatesドキュメントが更新され、プロキシの背後で動作する際にJinja2テンプレートがどのようにURLを生成すべきかが明確化されました。テンプレート内でurl_for('read_item', id=id).pathを使用すると、/で始まり、サーバーに設定されたroot_pathを自動的に含み、スキーム、ホスト、クエリパラメーター、フラグメントを除いたサイトルート相対パスが生成されます。プロキシの背後でスキームとドメインを含む完全なURLが必要な場合は、サーバー側で--forwarded-allow-ipsと--root-pathの両方を設定する必要があります。

0.144.0でのセキュリティ依存関係におけるWebSocketサポート

0.144.0リリース(PR #10147)では、fastapi.securityの標準セキュリティ依存関係(HTTPBearer、APIKeyHeader、APIKeyCookie、APIKeyQuery、OAuth2PasswordBearerなど)にWebSocketのサポートが追加されました。以前は、これらの依存関係がStarletteの共通オブジェクトであるHTTPConnectionやWebSocketではなくRequestインスタンスを期待していたため、@app.websocketルートに注入するとTypeError: __call__() missing 1 required positional argument: 'request'で失敗していました。現在は、HTTPエンドポイントとWebSocketエンドポイントの両方で同じセキュリティ依存関係クラスを再利用できます。

0.143.xシリーズにおける主な変更点

FastAPIリリースノートから、直近のリリースにおけるその他の変更点をまとめます。

  • 0.143.5: バイト型のファイルリストパラメーター(list[bytes] = File()、PR #9383)における空の値の扱いを修正しました。
  • 0.143.4: 0.143.0でのStarlette 1.7.0へのアップデートに伴い、オプションの依存関係にhttpx2を追加しました(PR #16512)。
  • 0.143.3: JSONLおよびServer-Sent Events(SSE)ストリームにおける非有限浮動小数点数(NaN、Infinity、-Infinity)のシリアライズを修正しました(PR #16509)。
  • 0.143.2: フロントエンドのフォールバックファイル確認処理がブロッキングしていた問題を修正しました(PR #16503)。
  • 0.143.1: iter_route_contexts()においてインクルードされたルートのメタデータを修正しました(PR #16484)。
  • 0.143.0: OpenTelemetryエクスポーターの自動セットアップがオプトイン方式になりました。これまでOTEL_*環境変数からFastAPIがOpenTelemetryエクスポーターを自動設定する仕組みに依存していた場合は、環境変数でFASTAPI_OTEL_AUTO_CONFIGURE=trueを設定するか、FastAPI(telemetry={"auto_configure": True})を渡す必要があります。

root_pathをFastAPI()からCLIおよびASGIサーバーへ移行する

FastAPI 0.146.0へクリーンにアップグレードするには、FastAPI()コンストラクタの呼び出しからroot_path、openapi_prefix、および未定義のキーワード引数を削除します。アップグレード前の典型的なアプリケーションファクトリと、そのリファクタリング方法を見ていきましょう。

以下は、環境設定からroot_pathを読み取り、FastAPI()に任意のキーワード引数を渡すレガシーな構成です。0.145.0および0.146.0では、このコードは(environmentに対して)TypeErrorを送出するか、(root_pathに対して)FastAPIDeprecationWarningを出力します。

# main_legacy.py - FastAPI 0.145以降では避けるべき書き方
import os
from fastapi import FastAPI, Request
 
# FastAPI 0.145.0では'environment'のような未知のキーワード引数でTypeErrorが発生します。
# FastAPI 0.146.0では'root_path'が空でない場合にFastAPIDeprecationWarningが出力されます。
app = FastAPI(
    title="Inventory Service",
    root_path=os.getenv("ROOT_PATH", "/api/v1"),
)
 
 
@app.get("/app")
def read_main(request: Request) -> dict[str, str | None]:
    return {
        "message": "Hello World",
        "root_path": request.scope.get("root_path"),
    }

このコードをFastAPI 0.146.0向けに更新するには、FastAPI()コンストラクタからroot_pathを取り除き、カスタムのアプリケーション属性をapp.stateへ移動します。

# main.py - FastAPI 0.146.0対応
import os
from fastapi import FastAPI, Request
 
app = FastAPI(title="Inventory Service")
app.state.environment = os.getenv("ENVIRONMENT", "production")
 
 
@app.get("/app")
def read_main(request: Request) -> dict[str, str | None]:
    return {
        "message": "Hello World",
        "root_path": request.scope.get("root_path"),
    }

Pythonコードをプロキシのサブパスから切り離したら、ASGIサーバーを起動する箇所で--root-pathを設定します。

FastAPI CLIでの実行

公式のBehind a Proxyドキュメントでは、fastapi runに--root-pathを直接渡すことが推奨されています。内部では、fastapi runがUvicornを呼び出し、ルートパスのオプションをASGIサーバーへ転送します。

fastapi run main.py --root-path /api/v1

TLSを終端しX-Forwarded-ForやX-Forwarded-Protoヘッダーを設定する信頼されたリバースプロキシの背後でアプリケーションを実行する場合は、生成されるURL(テンプレートのurl_for()呼び出しを含む)に外部のスキームとホストが反映されるよう、--root-pathと--forwarded-allow-ipsを組み合わせます。

fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1

Uvicornでの直接実行

コンテナのエントリーポイントやプロセスマネージャーがFastAPI CLIではなくuvicornを直接呼び出している場合は、同じ--root-pathフラグをUvicornに渡します。

uvicorn main:app --host 0.0.0.0 --port 8000 --root-path /api/v1

また、Pythonのエントリーポイントスクリプトからプログラム経由でUvicornを起動している場合は、uvicorn.run()にroot_pathを渡します。これにより、FastAPIコンストラクタの非推奨警告を発生させることなく、ASGIサーバー層でscope["root_path"]を設定できます。

# run.py
import os
import uvicorn
 
if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=8000,
        root_path=os.getenv("ROOT_PATH", "/api/v1"),
    )

リバースプロキシ背後でのOpenAPIのserversとドキュメントの扱い

--root-path /api/v1を設定する際によくある誤解は、Uvicorn自体がhttp://127.0.0.1:8000/api/v1/appで待ち受けるようになると思い込むことです。Behind a Proxyガイドで説明されているとおり、Uvicornは自身のソケットに到達するリクエストにプロキシのプレフィックスが含まれていることを想定していません。リバースプロキシが/api/v1を取り除き、http://127.0.0.1:8000/appへ/appを送信します。Uvicornは、request.scope["root_path"] = "/api/v1"を設定してFastAPIへ引き渡すためだけに--root-path /api/v1を使用します。

OpenAPIスキーマ生成におけるscope["root_path"]の使われ方

ブラウザやクライアントが/openapi.jsonをリクエストすると、FastAPIはrequest.scope.get("root_path", "").rstrip("/")を検査します。デフォルトでは、FastAPI()はroot_path_in_servers=Trueを設定しています。ASGIリクエストスコープに空でないroot_pathが存在し、かつそれがself.serversにまだ含まれていない場合、FastAPIは生成されるOpenAPI JSONのservers配列の先頭に{"url": root_path}を自動的に追加します。

{
  "openapi": "3.1.0",
  "info": {
    "title": "Inventory Service",
    "version": "0.1.0"
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "paths": {
    "/app": {
      "get": {
        "summary": "Read Main",
        "operationId": "read_main_app_get",
        "responses": {
          "200": {
            "description": "Successful Response"
          }
        }
      }
    }
  }
}

プロキシ経由でhttps://example.com/api/v1/docsのSwagger UIを開くと、ブラウザはOpenAPIスキーマを取得し、/api/v1のサーバーエントリを確認して、「Try it out」の対話型リクエストをhttps://example.com/api/v1/appへ送信します。Uvicornはプロキシが/api/v1を取り除くことを前提としているため、プロキシを経由せずにローカルポートのhttp://127.0.0.1:8000/docsを直接開いた場合、Swagger UIがUvicornへ直接http://127.0.0.1:8000/api/v1/appをリクエストしてしまうため、「Execute」をクリックしても失敗します。

serversとroot_path_in_serversの制御

0.146.0でFastAPI()のroot_pathは非推奨となりましたが、FastAPI()のserversパラメーターとroot_path_in_serversパラメーターはFastAPIクラスリファレンスにおいて引き続き完全にサポートされています。

明示的なアップストリームサーバー(複数のリージョンのプロキシURLなど)を指定しており、ローカルのASGIのroot_pathをOpenAPIのserversリストへFastAPIに自動注入させたくない場合は、root_path_in_servers=Falseを渡します。

# main_custom_servers.py
from fastapi import FastAPI
 
app = FastAPI(
    title="Inventory Service",
    servers=[
        {"url": "https://stg.example.com/api/v1", "description": "Staging"},
        {"url": "https://prod.example.com/api/v1", "description": "Production"},
    ],
    root_path_in_servers=False,
)
 
 
@app.get("/app")
def read_main() -> dict[str, str]:
    return {"message": "Hello World"}

serversが省略され、かつroot_pathが空または/である場合、FastAPIはOpenAPIスキーマからserversフィールド自体を省略します。

0.146.0へのアップグレードに伴うテストとCIの更新

これまでのテストスイートで、テストフィクスチャ内でFastAPI(root_path="/api/v1")をインスタンス化してプロキシのサブパス挙動やOpenAPIのservers生成をテストしていた場合、それらのテストは0.146.0でFastAPIDeprecationWarningを出力します。

アプリケーション側にroot_pathを設定する代わりに、TestClientへroot_pathを渡してください。FastAPI TestClientリファレンスに示されているとおり、TestClient.__init__はroot_path: str = ""引数を受け取り、Uvicornと同様にシミュレートされたすべてのリクエストでASGIスコープを設定します。

# test_main.py
from fastapi import FastAPI, Request
from fastapi.testclient import TestClient
 
app = FastAPI(title="Inventory Service")
 
 
@app.get("/app")
def read_main(request: Request) -> dict[str, str | None]:
    return {
        "message": "Hello World",
        "root_path": request.scope.get("root_path"),
    }
 
 
def test_root_path_scope_and_openapi() -> None:
    client = TestClient(app, root_path="/api/v1")
 
    response = client.get("/app")
    assert response.status_code == 200
    assert response.json() == {
        "message": "Hello World",
        "root_path": "/api/v1",
    }
 
    openapi_response = client.get("/openapi.json")
    assert openapi_response.status_code == 200
    assert openapi_response.json()["servers"] == [{"url": "/api/v1"}]

テストがclient.get("/api/v1/app")ではなくclient.get("/app")をリクエストしている点に注目してください。これは、パスを削除するリバースプロキシの背後にあるASGIサーバーが、scope["root_path"]から/api/v1を読み取りつつ、プレフィックスが取り除かれたパスを受け取る仕組みをそのまま反映しています。

CIでFastAPIDeprecationWarningをエラーとして扱う

新しいFastAPI(root_path=...)の呼び出しやその他の非推奨となったFastAPIのパターンがコードベースに再び紛れ込むのを防ぐには、pyproject.tomlでpytestを設定し、FastAPIDeprecationWarningをエラーとして扱うようにします。

# pyproject.toml
[tool.pytest.ini_options]
filterwarnings = [
    "error::fastapi.exceptions.FastAPIDeprecationWarning",
]

CIでこのフィルターを有効にすると、FastAPI(root_path="/api/v1")を初期化するテストやアプリケーションファクトリは、呼び出し元(stacklevel=2)を示す明確なトレースバックとともに即座に失敗します。

FastAPI 0.146.0へアップグレードした後の次のステップとして、Dockerfileのエントリーポイント、Kubernetesマニフェスト、またはsystemdのユニットファイルを点検し、fastapi runやuvicornコマンドとともに--root-pathおよび--forwarded-allow-ipsが定義されていることを確認してください。あわせて、FastAPIDeprecationWarningをerrorに設定した状態でpytestを実行し、Python内でプロキシパスを設定しているアプリケーションファクトリが残っていないことを確認してください。

参考資料

  • FastAPI
  • Python
  • ASGI
  • バックエンド