FastAPIのデコレータとは
FastAPIのデコレータは、Pythonの関数やクラスに対して追加の動作を与えるための仕組みです。特にFastAPIでは、APIエンドポイントの定義やリクエストの処理にデコレータが頻繁に活用されており、これにより効率的な開発が可能になります。
デコレータは@シンボル
で始まります。@の後にデコレータ名(関数やクラス)が続き、その下の関数やメソッド、クラスに装飾的な動作を付加します。
@decorator_name
def function_name():
# 関数の処理
pass
デコレータの動作と役割
デコレータの基本的な動作は、関数やクラスをラップし、追加の処理をその関数に組み込むことです。
Pythonでのデコレータは、実行時に関数やクラスに付加される装飾的な関数といえますが、FastAPIにおいてはこの仕組みを活用して、APIリクエストの管理やデータのバリデーション、セキュリティチェックといった機能を提供します。
基本的なデコレータとして@app.get
, @app.post
, @app.put
, @app.delete
などのHTTPメソッドデコレータ、依存関係の注入に使用される@Depends
、ミドルウェアを設定するための@app.middleware
、特定のエラーを処理するための@app.exception_handler
があります。
例えば、基本的なGETリクエストの例は下記のようになります。
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def hello():
return {"message": "Hello, FastAPI!"}
このように、@app.get("/hello")
というデコレータをhello関数に付け加えることで、/hello
というURLにGET
リクエストが送られた際に、関数が実行され、{"message": "Hello, FastAPI!"}
というJSONレスポンスが返されます。
- HTTPメソッドデコレータの内部動作
FastAPIのデコレータは、APIRouteという内部クラスを通じて、各関数をURLパスとHTTPメソッドに関連付けます。
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
1. デコレータの実行:FastAPIは、デコレータが付いた関数を読み込み、URL /items/{item_id}にGETリクエストが来た際にread_item関数を実行するように設定します。
2. パスパラメータの抽出:URLの一部からitem_idを抽出し、定義したデータ型(ここではint)に基づいて変換・バリデーションを行います。
3. エラーハンドリング:バリデーションが成功すれば、関数が実行され、指定されたJSONレスポンスが返ります。失敗した場合には、適切なエラーレスポンス(400 Bad Request など)を返します。
このように、FastAPIではデコレータを利用してリクエストからレスポンスまでの流れを簡潔に実現しているため、ルーティングがシンプルで見やすいコードになります。
依存性の注入を活用する
大規模なAPI構築においてコードの再利用性やテストのしやすさを高めるために、依存性の注入を活用することができます。Depends
を活用した依存性の注入により、関数に外部リソースや共通処理を柔軟に組み込むことができます。これにより、認証やデータベース接続といったロジックを使いまわし、テストもしやすくなります。
依存性注入の仕組
依存性注入の基本的な考え方は、関数やクラスが必要とする外部リソースや共通のロジックを、関数内部で定義するのではなく、外部から提供することです。
FastAPIでは、Dependsを使って依存する関数を注入することで、この外部リソースやロジックを管理できます。
Dependsは、他のDependsを内包することもでき、依存関係を持つ関数を再帰的に呼び出す構造を実現できます。
基本構文
from fastapi import FastAPI, Depends
app = FastAPI()
# 依存関数の定義
def dependency_function():
return "依存関係から提供されたデータ"
# 依存関係を注入するエンドポイント
@app.get("/dependency")
def read_data(data: str = Depends(dependency_function)):
return {"data": data}
このコードでは、dependency_function
がエンドポイント/dependency
のread_data関数
に注入されています。この場合、エンドポイントが呼ばれるたびにdependency_function
が実行され、その結果がdata引数
に代入されます。
依存性注入のユースケース
ユースケースとして認証ロジックの注入が挙げられます。
APIが認証を必要とする場合、認証処理をエンドポイントごとに記述するのは冗長でエラーの原因となります。Depends
を使って認証関数をエンドポイントに注入することで、共通の認証ロジックを簡単に適用できます。
from fastapi import Depends, FastAPI, HTTPException, status
app = FastAPI()
# 認証関数の定義
def authenticate_user(token: str):
if token != "valid_token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid token",
)
return "認証済みユーザー"
# 認証を要求するエンドポイント
@app.get("/protected")
def protected_route(user=Depends(authenticate_user)):
return {"message": f"Hello, {user}!"}
上記は、authenticate_user関数
が認証を行い、有効なユーザーのみがエンドポイント/protected
にアクセスできます。Depends(authenticate_user)
を使うことで、各エンドポイントで共通の認証処理を実装できます。
ミドルウェアデコレータ
ミドルウェアデコレータは、リクエストとレスポンスの間に特定の処理を挟むための仕組みで、アプリケーション全体に対して共通のロジックを適用する役立ちます。
ミドルウェアデコレータを使うことで、認証、ロギング、リクエストのカスタム処理、CORS設定、リクエストサイズの制限など、リクエストに対する前処理やレスポンスに対する後処理が一括で行えます。
ミドルウェアの仕組み
リクエストを受け取って処理を行い、次の処理にリクエストを渡す「パイプライン」のように機能します。最初にミドルウェアがリクエストを受け取り、その後リクエストがエンドポイントに到達するまでに複数のミドルウェアを通過させることも可能です。
基本構造
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class SimpleMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# 前処理: リクエストを処理する前の処理を記述
response = await call_next(request)
# 後処理: レスポンスが返される前の処理を記述
return response
app.add_middleware(SimpleMiddleware)
このコードでは、SimpleMiddleware
クラスがBaseHTTPMiddleware
を継承し、dispatch
メソッド内でリクエストとレスポンスを処理しています。
call_next
関数はリクエストを次のミドルウェア(またはエンドポイント関数)に渡し、最終的にレスポンスを返します。
この構造により、共通処理を効率的に適用できます。
ミドルウェアのユースケース
代表的なユースケースとして、リクエストのロギングがあります。
例えば、リクエストのメソッドやパス、リクエストのヘッダー情報をログに記録して、システム内でどのようなリクエストが行われているかを確認できるようにします。
import logging
from fastapi import FastAPI, Request
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
logging.info(f"Request: {request.method} {request.url}")
response = await call_next(request)
logging.info(f"Response status: {response.status_code}")
return response
app.add_middleware(LoggingMiddleware)
この例では、リクエストのメソッドとURLを、レスポンスのステータスコードをそれぞれログに記録しています。これにより、リクエストごとのトラフィックを把握しやすくなり、エラーが発生した際のトラブルシューティングにも役立ちます。
カスタム例外ハンドラー
@app.exception_handler
デコレータを使用することで、特定のエラーをカスタムハンドラーで処理するように設定できます。
カスタム例外ハンドラーは、通常のエラーメッセージやステータスコードに加え、カスタムのエラーメッセージや特定のフォーマットでのエラーレスポンスを返すために使用します。これにより、例えば、フロントエンドでエラーレスポンスのフォーマットが統一され、エラー処理がしやすくなります。
基本構造
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
app = FastAPI()
# カスタム例外ハンドラーの定義
@app.exception_handler(HTTPException)
async def custom_http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"error": f"Oops! {exc.detail}", "status_code": exc.status_code},
)
# エラーが発生するエンドポイント
@app.get("/error")
async def error_endpoint():
raise HTTPException(status_code=404, detail="Item not found")
この例では、HTTPException
をカスタム例外ハンドラーで処理して、HTTPException
が発生すると、custom_http_exception_handler
が呼ばれます。レスポンスの内容はカスタマイズされ、エラーメッセージOops! Item not found
がJSON形式で返されます。