Flask のカスタム URL コンバータを作成する

Flask の URL ルーティングでは <int:id> のようなコンバータを使える。独自のコンバータを作成すれば、URL パラメータの検証や変換を一元化できる。

組み込みコンバータ

Flask には以下のコンバータが組み込まれている。

stringデフォルト。スラッシュを含まない文字列
int正の整数
float正の浮動小数点数
pathスラッシュを含む文字列
uuidUUID 文字列
any指定した値のいずれか

カスタムコンバータの作成

werkzeug.routing.BaseConverter を継承して作成する。

from werkzeug.routing import BaseConverter

class RegexConverter(BaseConverter):
    def __init__(self, map, regex):
        super().__init__(map)
        self.regex = regex

# アプリケーションに登録
app.url_map.converters['regex'] = RegexConverter

@app.route('/user/<regex("[a-z]+"):username>')
def user_profile(username):
    return f'User: {username}'

値の変換を行うコンバータ

to_python で URL から Python オブジェクトへ、to_url で Python オブジェクトから URL へ変換する。

from werkzeug.routing import BaseConverter
from datetime import date

class DateConverter(BaseConverter):
    regex = r'\d{4}-\d{2}-\d{2}'
    
    def to_python(self, value):
        # URL パラメータを date オブジェクトに変換
        return date.fromisoformat(value)
    
    def to_url(self, value):
        # date オブジェクトを URL 文字列に変換
        return value.isoformat()

app.url_map.converters['date'] = DateConverter

@app.route('/events/<date:event_date>')
def event(event_date):
    # event_date は date オブジェクト
    return f'Event on {event_date.strftime("%B %d, %Y")}'

url_for 使用時に to_url が呼ばれる。

url_for('event', event_date=date(2024, 12, 25))
# '/events/2024-12-25'

リストを受け取るコンバータ

class ListConverter(BaseConverter):
    regex = r'[\w,]+'
    
    def to_python(self, value):
        return value.split(',')
    
    def to_url(self, values):
        return ','.join(values)

app.url_map.converters['list'] = ListConverter

@app.route('/tags/<list:tags>')
def filter_by_tags(tags):
    # tags はリスト ['python', 'flask', 'api']
    return f'Tags: {tags}'

モデルを直接取得するコンバータ

class UserConverter(BaseConverter):
    regex = r'\d+'
    
    def to_python(self, value):
        user = User.query.get(int(value))
        if user is None:
            raise ValidationError('User not found')
        return user
    
    def to_url(self, user):
        return str(user.id)

app.url_map.converters['user'] = UserConverter

@app.route('/profile/<user:user>')
def profile(user):
    # user は User オブジェクト
    return f'Profile: {user.name}'

カスタムコンバータを活用すれば、ルーティング層でバリデーションや変換を完結させ、ビュー関数をシンプルに保てる。