FastAPI + GraphQL getting error NoneType is callable when raise Exception

Viewed 1081

I'm stuck when trying to raise a validation error with FastAPI + GraphQL (graphene). I have a resolver code:

class Query(graphene.ObjectType):  
  list_categories = graphene.List(CategoryGrapheneModel)
  get_category = graphene.Field(CategoryGrapheneModel, id=graphene.Argument(graphene.Int, required=True))

  @staticmethod
  def resolve_list_categories(parent, info):
    return Category.all()

  @staticmethod
  def resolve_get_category(parent, info, id):
    try:
      category = Category.find_or_fail(id)
      return category
    except ModelNotFound as ex:
      raise Exception('Category not found')

But instead of getting 400 HTTP response with the message I got 500 Internal Server Error with traceback:

Traceback (most recent call last):
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/fastapi/applications.py", line 199, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 566, in __call__
    await route.handle(scope, receive, send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/graphql.py", line 52, in __call__
    response = await self.handle_graphql(request)
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/graphql.py", line 105, in handle_graphql
    [format_graphql_error(err) for err in result.errors]
  File "/Users/vitalyradchik/Devel/upwork/tipolim/backend/.venv/lib/python3.9/site-packages/starlette/graphql.py", line 105, in <listcomp>
    [format_graphql_error(err) for err in result.errors]
TypeError: 'NoneType' object is not callable

Googling is not gives me a solution. So please help.

1 Answers

Got handled with it. The problem was in GraphQLApp, there was calling format_graphql_errors that was undefined (None).

To solve a problem I've created a custom child class from GraphQLApp and changed format_graphql_errors to format_error from graphql.error.graphql_error package.

import json
import typing

from starlette.graphql import GraphQLApp
from starlette import status
from starlette.background import BackgroundTasks
from starlette.concurrency import run_in_threadpool
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse, Response
from starlette.types import Receive, Scope, Send
from graphql.error.graphql_error import format_error


class CustomGraphQLApp(GraphQLApp):
  async def handle_graphql(self, request: Request) -> Response:
    if request.method in ("GET", "HEAD"):
      if "text/html" in request.headers.get("Accept", ""):
        if not self.graphiql:
          return PlainTextResponse(
            "Not Found", status_code=status.HTTP_404_NOT_FOUND
          )
        return await self.handle_graphiql(request)

      data = request.query_params  # type: typing.Mapping[str, typing.Any]

    elif request.method == "POST":
      content_type = request.headers.get("Content-Type", "")

      if "application/json" in content_type:
        data = await request.json()
      elif "application/graphql" in content_type:
        body = await request.body()
        text = body.decode()
        data = {"query": text}
      elif "query" in request.query_params:
        data = request.query_params
      else:
        return PlainTextResponse(
            "Unsupported Media Type",
            status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
        )

    else:
      return PlainTextResponse(
        "Method Not Allowed", status_code=status.HTTP_405_METHOD_NOT_ALLOWED
      )

    try:
      query = data["query"]
      variables = data.get("variables")
      operation_name = data.get("operationName")
    except KeyError:
      return PlainTextResponse(
        "No GraphQL query found in the request",
        status_code=status.HTTP_400_BAD_REQUEST,
      )

    background = BackgroundTasks()
    context = {"request": request, "background": background}

    result = await self.execute(
      query, variables=variables, context=context, operation_name=operation_name
    )
    error_data = (
      [format_error(err) for err in result.errors]
      if result.errors
      else None
    )
    response_data = {"data": result.data}
    if error_data:
      response_data["errors"] = error_data
    status_code = (
      status.HTTP_400_BAD_REQUEST if result.errors else status.HTTP_200_OK
    )

    return JSONResponse(
      response_data, status_code=status_code, background=background
    )

I hope this will help others.

Related