quart-schema icon indicating copy to clipboard operation
quart-schema copied to clipboard

Add 'mode' field to PydanticDumpOptions TypedDict

Open KalleDK opened this issue 5 months ago • 4 comments

This is used when objects like IPv4Address or other custom objects is not reconised by the json serializer.

Often you want your model_dump to give you the object not serialized like IPv4Addresses, but you want model_dump(mode="json") to give you the string.

I would even suggest that mode="json" should be default, but I'm afraid that would break som peoples code.

from ipaddress import IPv4Address
import pydantic

class Demo(pydantic.BaseModel):
    ip: IPv4Address


d = Demo(ip=IPv4Address("127.0.0.1"))
d.model_dump()
# {'ip': IPv4Address('127.0.0.1')}
d.model_dump(mode="json")
# {'ip': '127.0.0.1'}

KalleDK avatar Nov 14 '25 09:11 KalleDK

I'm not sure this is required, the addition of a JSONEncoder here should ensure that it is converted to JSON

pgjones avatar Dec 02 '25 21:12 pgjones

I can only have this working when I set mode

Without mode

from http import HTTPStatus
from ipaddress import IPv4Address

import pydantic
import quart_schema
from quart import Quart


class DemoResp(pydantic.BaseModel):
    message: str
    ip: IPv4Address


schema = quart_schema.QuartSchema()
app = Quart(__name__)
schema.init_app(app)


@app.get("/hello")
@quart_schema.validate_response(DemoResp, HTTPStatus.OK)
def hello() -> tuple[DemoResp, int]:
    return DemoResp(message="Hello, World!", ip=IPv4Address("127.0.0.1")), HTTPStatus.OK


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'ipaddress.IPv4Address'>
[2025-12-03 09:37:49 +0100] [39524] [INFO] 127.0.0.1:60923 GET /hello 1.1 500 265 67192

With mode

from http import HTTPStatus
from ipaddress import IPv4Address

import pydantic
import quart_schema
from quart import Quart


class DemoResp(pydantic.BaseModel):
    message: str
    ip: IPv4Address


schema = quart_schema.QuartSchema()
app = Quart(__name__)
app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"] = {"mode": "json"}
schema.init_app(app)


@app.get("/hello")
@quart_schema.validate_response(DemoResp, HTTPStatus.OK)
def hello() -> tuple[DemoResp, int]:
    return DemoResp(message="Hello, World!", ip=IPv4Address("127.0.0.1")), HTTPStatus.OK


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)
[2025-12-03 09:39:29 +0100] [12112] [INFO] Running on http://127.0.0.1:5000 (CTRL + C to quit)
[2025-12-03 09:39:54 +0100] [12112] [INFO] 127.0.0.1:55864 GET /hello 1.1 200 45 8726

KalleDK avatar Dec 03 '25 08:12 KalleDK

@pgjones And an example of some models that handles what I call stupid api xD

from http import HTTPStatus

import pydantic
import quart_schema
from quart import Quart


class StupidApiModel(pydantic.BaseModel):
    name: str
    version: str

    @pydantic.field_validator("version", "name", mode="before")
    @classmethod
    def _validate_value_dict(cls, v: dict[str, str] | str) -> str:
        if isinstance(v, str):
            return v
        return v["value"]

    @pydantic.field_serializer("version", "name", when_used="json")
    def _serialize_value_dict(self, v: str) -> dict[str, str]:
        return {"value": v}


schema = quart_schema.QuartSchema()
app = Quart(__name__)
schema.init_app(app)


@app.get("/world")
@quart_schema.validate_response(StupidApiModel, HTTPStatus.OK)
def world() -> tuple[StupidApiModel, int]:
    return StupidApiModel(name="World", version="1.0"), HTTPStatus.OK


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

This incorrectly returns {"name":"World","version":"1.0"}

With the json mode

from http import HTTPStatus

import pydantic
import quart_schema
from quart import Quart


class StupidApiModel(pydantic.BaseModel):
    name: str
    version: str

    @pydantic.field_validator("version", "name", mode="before")
    @classmethod
    def _validate_value_dict(cls, v: dict[str, str] | str) -> str:
        if isinstance(v, str):
            return v
        return v["value"]

    @pydantic.field_serializer("version", "name", when_used="json")
    def _serialize_value_dict(self, v: str) -> dict[str, str]:
        return {"value": v}


schema = quart_schema.QuartSchema()
app = Quart(__name__)
app.config["QUART_SCHEMA_PYDANTIC_DUMP_OPTIONS"] = {"mode": "json"}
schema.init_app(app)


@app.get("/world")
@quart_schema.validate_response(StupidApiModel, HTTPStatus.OK)
def world() -> tuple[StupidApiModel, int]:
    return StupidApiModel(name="World", version="1.0"), HTTPStatus.OK


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

Correctly returns {"name":{"value":"World"},"version":{"value":"1.0"}}

KalleDK avatar Dec 03 '25 08:12 KalleDK

I'm not sure what is going wrong here, when I try

from http import HTTPStatus
from ipaddress import IPv4Address

import pydantic
import quart_schema
from quart import Quart


class DemoResp(pydantic.BaseModel):
    message: str
    ip: IPv4Address


schema = quart_schema.QuartSchema()
app = Quart(__name__)
schema.init_app(app)


@app.get("/hello")
@quart_schema.validate_response(DemoResp, HTTPStatus.OK)
def hello() -> tuple[DemoResp, int]:
    return DemoResp(message="Hello, World!", ip=IPv4Address("127.0.0.1")), HTTPStatus.OK


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000)

it works.

Do you have the full stack trace for the pydantic_core._pydantic_core.PydanticSerializationError error?

pgjones avatar Dec 14 '25 11:12 pgjones