Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object of type 'Promise' is not JSON serializable occurs when multiple levels of async resolvers used #12

Closed
mcakes opened this issue May 15, 2018 · 3 comments

Comments

@mcakes
Copy link

mcakes commented May 15, 2018

  • GraphQL AioWS version: 0.2.0 (Note graphql_ws.__version__ still shows 0.1.0)
  • Python version: 3.6
  • Operating System: Windows

Description

Accessing a field with an async resolver in an object type that resolves with an async resolver doesn't seem to work.

What I Did

Consider the following modification to https://github.com/graphql-python/graphql-ws/blob/master/examples/aiohttp/schema.py

import asyncio
import graphene


class Query(graphene.ObjectType):
    base = graphene.String()

class NewType(graphene.ObjectType):
    new_field = graphene.String()

    async def resolve_new_field(self, info):
        await asyncio.sleep(1.)
        return 'hello'

class Subscription(graphene.ObjectType):
    new_type = graphene.Field(NewType)

    async def resolve_new_type(root, info):
        while True:
            yield NewType()
            await asyncio.sleep(1.)

schema = graphene.Schema(query=Query, subscription=Subscription)

Results in

TypeError: Object of   type 'Promise' is not JSON serializable
--
Traceback (most recent call last):
File "src/cars/sub/app.py", line 43, in <module>
web.run_app(app, port=8000)
File   "C:\programs\anaconda3\envs\cars\lib\site-packages\aiohttp\web.py",   line
121, in run_app
loop.run_until_complete(runner.cleanup())
...

Changing resolve_new_field to

def resolve_new_field(self, info):
    return 'hello'

works as expected, but I should think having an async resolver here should be fine as well.

@fierysolid
Copy link

+1

@zerlok
Copy link

zerlok commented Mar 27, 2019

Our team also have the same problem when clients try to subscribe on multiple levels of graphql objects. We're using aiohttp and we made a simple workaround to solve the problem.

import asyncio
import inspect

from typing import Union, Awaitable, Any, List, Tuple, Dict
from graphql_ws.aiohttp import AiohttpSubscriptionServer


class SubscriptionServer(AiohttpSubscriptionServer):

    async def send_execution_result(self, connection_context, op_id, execution_result) -> None:
        resolving_items: List[Awaitable[Any]] = []

        queue: List[Tuple[Union[List[Any], Dict[Union[int, str], Any]], Union[int, str], Any]] = [
            (
                None,
                None,
                execution_result.data,
            ),
        ]
        while queue:
            container, key, item = queue.pop(0)

            if isinstance(item, list):
                self.__extend_list_item(queue, item)

            elif isinstance(item, dict):
                self.__extend_dict_item(queue, item)

            elif inspect.isawaitable(item):
                container = container if container is not None else queue
                key = key if key is not None else 0

                resolving_items.append(self.__resolve_container_item(container, key, item))

        await asyncio.gather(*resolving_items)

        await super().send_execution_result(connection_context, op_id, execution_result)

        return None

    async def __resolve_container_item(
            self,
            container: Union[List[Any], Dict[Union[int, str], Any]],
            key: Union[int, str],
            item: Awaitable[Any],
    ) -> None:
        container[key] = await item

    def __extend_list_item(
            self,
            queue: List[Tuple[Union[List[Any], Dict[Union[int, str], Any]], Union[int, str], Any]],
            item: List[Any],
    ) -> None:
        queue.extend(
            (
                item,
                index,
                value,
            )
            for index, value in enumerate(item)
        )

    def __extend_dict_item(
            self,
            queue: List[Tuple[Union[List[Any], Dict[Union[int, str], Any]], Union[int, str], Any]],
            item: Dict[Union[int, str], Any],
    ) -> None:
        queue.extend(
            (
                item,
                key,
                value,
            )
            for key, value in item.items()
        )

The main idea is to walk through the containers' objects and await for awaitable objects

@SmileyChris
Copy link
Contributor

Hopefully fixed. Reopen if you can still reproduce using code on master

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants