2023年6月14日水曜日

Flask でリクエストボディが xml でも json でもどちらでも受けれるようにするサンプルコード

Flask でリクエストボディが xml でも json でもどちらでも受けれるようにするサンプルコード

概要

content-type: application/xml or content-type: application/json どちらでも受けれるような API を考えます

環境

  • macOS 13.4
  • Python 3.11.3
  • Flask 2.3.2

方針

  • デコレータを使って xml -> dict をいい感じに変換する
  • 最終的には変換はオブジェクトに変換する
  • 各種エラー対応はできる限りしっかりしたい
  • レスポンスに対しては現在は考慮しない

サンプルコード

  • vim app.py
import copy
import json
import traceback
from json.decoder import JSONDecodeError
from xml.parsers.expat import ExpatError

import xmltodict
from flask import Flask, request

app = Flask(__name__)


class User:
    def __init__(self, name: str = "default_user", **_) -> None:
        self.name = name
        self.age = 10


# dict から User オブジェクトへの変換
def convert_obj(func):
    def wrapper(*args, content_dict: dict, **kwargs):
        try:
            user = User(**content_dict["xml"])
        except KeyError:
            # データ構造が正しくない場合はエラー
            raise ValueError("Root attribute must start with 'xml'.")

        return func(*args, user=user, **kwargs)

    return wrapper


# xml から dict への変換、すでに dict の場合は何もしない
def convert_dict(func):
    def wrapper(*args, **kwargs):
        data = copy.copy(request.data)
        try:
            data = xmltodict.parse(data)
        except ExpatError:
            try:
                data = json.loads(data)
            except JSONDecodeError:
                # xml じゃないかつ dict じゃない場合はエラー
                raise ValueError("You xml or dict must be specified.")

        return func(*args, content_dict=data, **kwargs)

    return wrapper


@app.route("/", methods=["GET", "POST"])
@convert_dict
@convert_obj
def parse_xml(user: User):
    return user.name


@app.errorhandler(ValueError)
def handle_bad_request(e):
    return traceback.format_exception_only(e), 400

動作確認

正常系

curl localhost:5000 -XPOST -d '{"xml": {"name":"hawksnowlog"}}' -H 'content-type: application/json'
curl localhost:5000 -XPOST -d '<xml><name>hawksnowlog</name></xml>' -H 'content-type: application/xml' -H 'Accept: application/xml'

異常系

curl localhost:5000 -XPOST -d '{"xml2": {"name":"hawksnowlog"}}' -H 'content-type: application/json'
curl localhost:5000 -XPOST -d 'hoge' -H 'content-type: application/json'

最後に

レスポンスは accept ヘッダのタイプに応じて勝手に変換してくれるようになっているとかっこいい

0 件のコメント:

コメントを投稿