Flask でカスタムデコレータを作成すると、認証、権限チェック、ロギングなどの共通処理を再利用可能な形で実装できる。
基本的なデコレータ
from functools import wraps from flask import session, redirect, url_for def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): if 'user_id' not in session: return redirect(url_for('login')) return f(*args, **kwargs) return decorated_function @app.route('/dashboard') @login_required def dashboard(): return 'Dashboard'
@wraps(f) を使うことで、元の関数名やドキュメントが保持される。これがないと endpoint が decorated_function になってしまう。
引数を取るデコレータ
def role_required(role): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): if g.user is None or g.user.role != role: return 'Forbidden', 403 return f(*args, **kwargs) return decorated_function return decorator @app.route('/admin') @login_required @role_required('admin') def admin_panel(): return 'Admin Panel'
外側から role_required('admin') → login_required → admin_panel の順で適用される。実行時は内側から外側へ評価される。
デコレータの順序
@app.route('/path') @decorator_a @decorator_b def view(): pass # 実際の実行順序: # 1. app.route でルーティング登録 # 2. decorator_a がラップ # 3. decorator_b がラップ # リクエスト時: decorator_a → decorator_b → view
@app.route は必ず一番上に置くこと。
汎用的なロギングデコレータ
import time from functools import wraps def log_execution_time(f): @wraps(f) def decorated_function(*args, **kwargs): start = time.time() result = f(*args, **kwargs) elapsed = time.time() - start app.logger.info(f'{f.__name__} took {elapsed:.3f}s') return result return decorated_function
JSON レスポンスを強制するデコレータ
from flask import jsonify def json_response(f): @wraps(f) def decorated_function(*args, **kwargs): result = f(*args, **kwargs) if isinstance(result, dict): return jsonify(result) return result return decorated_function @app.route('/api/user') @json_response def get_user(): return {'id': 1, 'name': 'Alice'} # 自動的に jsonify される
レートリミットデコレータ
from flask import request from functools import wraps import time rate_limit_store = {} def rate_limit(max_requests, window_seconds): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): key = f'{f.__name__}:{request.remote_addr}' now = time.time() if key in rate_limit_store: requests, window_start = rate_limit_store[key] if now - window_start < window_seconds: if requests >= max_requests: return 'Too Many Requests', 429 rate_limit_store[key] = (requests + 1, window_start) else: rate_limit_store[key] = (1, now) else: rate_limit_store[key] = (1, now) return f(*args, **kwargs) return decorated_function return decorator @app.route('/api/data') @rate_limit(10, 60) # 60秒間に10リクエストまで def get_data(): return {'data': 'value'}
カスタムデコレータを活用すれば、ビュー関数をシンプルに保ちながら横断的な関心事を分離できる。