2023年8月21日月曜日

PydanticV2 で Xml なレスポンスを扱う方法を考える

PydanticV2 で Xml なレスポンスを扱う方法を考える

概要

過去 に Pydantic V1 で検討しましたが V2 でも検討してみます
ydantic-xml が V1 にしか対応していないので pydantic-xml を使わない方針で作成してみます

環境

  • Python 3.11.3
  • fastapi 0.100.1
  • pypdantic 2.1.1

方針

  • Xml レスポンスは fastapi のレスポンスクラスを継承して作成
  • Xml 用のモデル定義は pydantic の BaseModel を継承する
    • レスポンスを Xml に変換するためのシリアライザを持たせる
    • Xml フィールドのキーはパスカルケースに変換する

サンプルコード

import textwrap
from typing import LiteralString, Union

import dicttoxml
from fastapi import FastAPI, Response
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_pascal

app = FastAPI()


class User(BaseModel):
    # alias_generator を指定することで Xml のキー情報をパスカルケースにする
    model_config = ConfigDict(populate_by_name=True, alias_generator=to_pascal)

    name: str = Field()
    age: int = Field()

    # model_dump (dict) を xml (str) に変換する
    def to_xml(self) -> LiteralString | bytes:
        return dicttoxml.dicttoxml(
            self.model_dump(by_alias=True),
            custom_root=to_pascal("some_custom_root"),
            attr_type=False,
        )


# fastapi で Xml レスポンスを生成するためのクラス
class XmlResponse(Response):
    def __init__(self, content: Union[str, bytes], status_code: int = 200):
        super().__init__(content, status_code=status_code, media_type="application/xml")


@app.get(
    "/",
    response_class=XmlResponse,
    response_model=User,
    # openapi-doc はここで生成する
    responses={
        200: {
            "description": "Success",
            "content": {
                "application/xml": {
                    "schema": {"$ref": "#/components/schemas/User"},
                    "example": textwrap.dedent(
                        """
                        <?xml version="1.0" encoding="UTF-8"?>
                        <User>
                          <Name>hawk</Name>
                          <Age>20</Age>
                        </User>
                    """
                    )[1:-1],
                },
                "application/json": None,
            },
        },
    },
)
def xml():
    result = User(name="hawksnowlog", age=10)
    return XmlResponse(content=result.to_xml())

動作確認

  • curl localhost:8000
<?xml version="1.0" encoding="UTF-8" ?><SomeCustomRoot><Name>hawksnowlog</Name><Age>10</Age></SomeCustomRoot>

localhost:8000/docs

最後に

pydantic V2 で Xml レスポンスを表現する方法を検討してみました
to_xml などはシリアライザを指定して自動で呼び出してくれるようになるともっといい感じなるかなと思います

参考サイト

0 件のコメント:

コメントを投稿