fastapi custom response class as default response class

Question:

I am trying to use a custom response class as default response.

from fastapi.responses import Response
from bson.json_util import dumps

class MongoResponse(Response):
    def __init__(self, content, *args, **kwargs):
        super().__init__(
            content=dumps(content),
            media_type="application/json",
            *args,
            **kwargs,
        )

This works fine when I explicitly use the response class.

@app.get("/")
async def getDoc():
    foo = client.get_database('foo')
    result = await foo.bar.find_one({'author': 'fool'})
    return MongoResponse(result)

However, when I try to pass this as argument to the FastAPI constructor, it doesn’t seem to be used when only returning the data from the request handler.

app = FastAPI(default_response_class=MongoResponse)

@app.get("/")
async def getDoc():
    foo = client.get_database('foo')
    result = await foo.bar.find_one({'author': 'fool'})
    return result

When I look at below stack trace, it seems like its still using the normal default response which is the json response.

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/uvicorn/protocols/http/httptools_impl.py", line 390, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/fastapi/applications.py", line 181, in __call__
    await super().__call__(scope, receive, send)  # pragma: no cover
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/fastapi/routing.py", line 199, in app
    is_coroutine=is_coroutine,
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/fastapi/routing.py", line 122, in serialize_response
    return jsonable_encoder(response_content)
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/fastapi/encoders.py", line 94, in jsonable_encoder
    sqlalchemy_safe=sqlalchemy_safe,
  File "/home/blue/podman/test/.venv/lib/python3.6/site-packages/fastapi/encoders.py", line 139, in jsonable_encoder
    raise ValueError(errors)
ValueError: [TypeError("'ObjectId' object is not iterable",), TypeError('vars() argument must have __dict__ attribute',)]

Asked By: The Fool

||

Answers:

So it turns out that the default response class as well as the response class on the route are only there for the open API documentation. By default the documentation will document every endpoint as if they would return json.

So with the below example code every response would be marked as content type text/html.
On the second rout this is overwritten with application/json

app = FastAPI(default_response_class=HTMLResponse)

@app.get("/")
async def getDoc():
    foo = client.get_database('foo')
    result = await foo.bar.find_one({'author': 'Mike'})
    return MongoResponse(result)


@app.get("/other", response_class=JSONResponse)
async def json():
    return {"json": "true"}

image

In that sense, I should probably use my class explicitly and leave the default response class as JSON so that they are documented as JSON responses.

Answered By: The Fool

I resorted to monkeypatching

from fastapi import routing as fastapi_routing
from fastapi.responses import ORJSONResponse

def api_route(self, path, **kwargs):
    def decorator(func):
        if type(kwargs["response_class"]) == DefaultPlaceholder:
            kwargs["response_class"] = Default(ORJSONResponse)
        self.add_api_route(
            path,
            func,
            **kwargs,
        )
        return func

    return decorator


fastapi_routing.APIRouter.api_route = api_route
Answered By: acw
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.