Giter Club home page Giter Club logo

graphql-ws's Introduction

GraphQL WS

Websocket backend for GraphQL subscriptions.

Supports the following application servers:

Python 3 application servers, using asyncio:

Python 2 application servers:

Installation instructions

For instaling graphql-ws, just run this command in your shell

pip install graphql-ws

Examples

Python 3 servers

Create a subscribable schema like this:

import asyncio
import graphene


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

    @staticmethod
    def resolve_hello(obj, info, **kwargs):
        return "world"


class Subscription(graphene.ObjectType):
    count_seconds = graphene.Float(up_to=graphene.Int())

    async def resolve_count_seconds(root, info, up_to):
        for i in range(up_to):
            yield i
            await asyncio.sleep(1.)
        yield up_to


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

aiohttp

Then just plug into your aiohttp server.

from graphql_ws.aiohttp import AiohttpSubscriptionServer
from .schema import schema

subscription_server = AiohttpSubscriptionServer(schema)


async def subscriptions(request):
    ws = web.WebSocketResponse(protocols=('graphql-ws',))
    await ws.prepare(request)

    await subscription_server.handle(ws)
    return ws


app = web.Application()
app.router.add_get('/subscriptions', subscriptions)

web.run_app(app, port=8000)

You can see a full example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/aiohttp

websockets compatible servers

Works with any framework that uses the websockets library for its websocket implementation. For this example, plug in your Sanic server.

from graphql_ws.websockets_lib import WsLibSubscriptionServer
from . import schema

app = Sanic(__name__)

subscription_server = WsLibSubscriptionServer(schema)

@app.websocket('/subscriptions', subprotocols=['graphql-ws'])
async def subscriptions(request, ws):
    await subscription_server.handle(ws)
    return ws


app.run(host="0.0.0.0", port=8000)

Django v2+

Django Channels 2

Set up with Django Channels just takes three steps:

  1. Install the apps
  2. Set up your schema
  3. Configure the channels router application

First pip install channels and it to your INSTALLED_APPS. If you want graphiQL, install the graphql_ws.django app before graphene_django to serve a graphiQL template that will work with websockets:

INSTALLED_APPS = [
    "channels",
    "graphql_ws.django",
    "graphene_django",
    # ...
]

Point to your schema in Django settings:

GRAPHENE = {
    'SCHEMA': 'yourproject.schema.schema'
}

Finally, you can set up channels routing yourself (maybe using graphql_ws.django.routing.websocket_urlpatterns in your URLRouter), or you can just use one of the preset channels applications:

ASGI_APPLICATION = 'graphql_ws.django.routing.application'
# or
ASGI_APPLICATION = 'graphql_ws.django.routing.auth_application'

Run ./manage.py runserver and go to http://localhost:8000/graphql to test!

Python 2 servers

Create a subscribable schema like this:

import graphene
from rx import Observable


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

    @staticmethod
    def resolve_hello(obj, info, **kwargs):
        return "world"


class Subscription(graphene.ObjectType):
    count_seconds = graphene.Float(up_to=graphene.Int())

    async def resolve_count_seconds(root, info, up_to=5):
        return Observable.interval(1000)\
                         .map(lambda i: "{0}".format(i))\
                         .take_while(lambda i: int(i) <= up_to)


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

Gevent compatible servers

Then just plug into your Gevent server, for example, Flask:

from flask_sockets import Sockets
from graphql_ws.gevent import GeventSubscriptionServer
from schema import schema

subscription_server = GeventSubscriptionServer(schema)
app.app_protocol = lambda environ_path_info: 'graphql-ws'


@sockets.route('/subscriptions')
def echo_socket(ws):
    subscription_server.handle(ws)
    return []

You can see a full example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/flask_gevent

Django v1.x

For Django v1.x and Django Channels v1.x, setup your schema in settings.py

GRAPHENE = {
    'SCHEMA': 'yourproject.schema.schema'
}

Then pip install "channels<1" and it to your django apps, adding the following to your settings.py

CHANNELS_WS_PROTOCOLS = ["graphql-ws", ]
CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "asgiref.inmemory.ChannelLayer",
        "ROUTING": "django_subscriptions.urls.channel_routing",
    },
}

And finally add the channel routes

from channels.routing import route_class
from graphql_ws.django_channels import GraphQLSubscriptionConsumer

channel_routing = [
    route_class(GraphQLSubscriptionConsumer, path=r"^/subscriptions"),
]

You can see a full example here: https://github.com/graphql-python/graphql-ws/tree/master/examples/django_subscriptions

graphql-ws's People

Contributors

anisjonischkeit avatar bendemaree avatar ciscorn avatar cito avatar colanconnon avatar dependabot[bot] avatar fabienheureux avatar hballard avatar jkimbo avatar leahein avatar mvanlonden avatar smileychris avatar syrusakbary avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-ws's Issues

CDNs Causing GraphIQL Not To Render (fix included)

  • GraphQL AioWS version:GRAPHIQL_VERSION='0.11.7',
    SUBSCRIPTIONS_TRANSPORT_VERSION='0.7.0',
  • Python version: 3.6.2
  • Operating System: Xubuntu 16.04

Description

Subscriptions per the flask_gevent example, nearly verbatim.
Graphiql wouldn't render, nadda zilch. Looked at js console (after many other things I expected that I did wrong)

The CDN wasn't providing the CSS or graphiql js.

What I Did

Switched out the graphiql js.min and css for https://cdnjs.com/libraries/graphiql/0.11.7 one from cloudflare and it rendered / things worked just dandy.

It's a two line change in the template.py, so doesn't seem worth the effort of a pull request :)

Aiohttp example - can't connect from graphql playground

  • GraphQL AioWS version: 0.3.0
  • Python version: 3.6.5
  • Operating System: Linux - Ubuntu 18.04.2 LTS

Description

Attempting to run the example aiohttp application and retrieve subscription information in graphql playground.

What I Did

I am running the example app via app.py in the examples/aiohttp directory.
python app.py
Server starts and is listening on port 8000.
I then start graphql playground and point it to http://localhost:8000/graphql.
It retrieves the schema correctly as follows:

type Query {
  base: String
}

type RandomType {
  seconds: Int
  randomInt: Int
}

type Subscription {
  countSeconds(upTo: Int): Float
  randomInt: RandomType
}

I attempt to open up the websocket connection to listen for server updates via:

subscription {
   countSeconds
}

What happened:
The server logged the following output

Error handling request
Traceback (most recent call last):
  File "/home/danny/.virtualenvs/subs/lib/python3.6/site-packages/aiohttp/web_protocol.py", line 418, in start
    resp = await task
  File "/home/danny/.virtualenvs/subs/lib/python3.6/site-packages/aiohttp/web_app.py", line 458, in _handle
    resp = await handler(request)
  File "./app.py", line 15, in graphql_view
    payload = await request.json()
  File "/home/danny/.virtualenvs/subs/lib/python3.6/site-packages/aiohttp/web_request.py", line 584, in json
    return loads(body)
  File "/usr/lib/python3.6/json/__init__.py", line 354, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.6/json/decoder.py", line 339, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.6/json/decoder.py", line 357, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Graphql playground displayed the following error:

{
  "error": "Could not connect to websocket endpoint ws://localhost:8000/graphql. Please check if the endpoint url is correct."
}

What I expected:
I expected to see the normal response in graphql playground for a subscription ->

Listening ...

Deep nested objects in django won't work due to async

  • GraphQL AioWS version: (not using AioWS, only django-channels)
  • Python version: 3.8.2
  • Operating System: MacOSX

Description

I am trying to get clients to subscribe to my graphql with a nested object in django. The request looks like this:

subscription {
  taskUpdated(id:"04a5e92bc9084ad981481ab4314a1d33", updateInterval: 3) {
    id
    token
    result {
      id,
      profiles {
        id
      }
    }
  } 
}

Tasks contain one result which contain multiple profiles.

What I Did

In my subscription class I used the sync_to_async method which works if used for the first level:

class Subscription(graphene.ObjectType):
    task_updated = graphene.Field(
        TaskType, id=graphene.String(), update_interval=graphene.Int()
    )

    async def resolve_task_updated(root, info, id, update_interval):
        print("TRYING TO RESOLVE")
        while True:
            print("While true")
            task = await sync_to_async(Task.objects.get, thread_sensitive=True)(
                pk=id
            )
            yield task
            await asyncio.sleep(update_interval)

Using the query only targeting task.id everything works perfectly. Once I try to target task.result (which is a new object). I receive the error:

You cannot call this from an async context - use a thread or sync_to_async.

I managed to get a workaround by updating the TaskType and updating the resolver for the result like so:

class TaskType(DjangoObjectType):
    class Meta:
        model = TaskModel
        fields = "__all__"

    result = graphene.Field(TaskResultType)

    async def resolve_result(self, info):
        return await sync_to_async(
            TaskResult.objects.get, thread_sensitive=True
        )(task=self.id)

That way I could get to the next level (task -> result). When I try to subscribe to task -> result -> profiles there is no way to receive the data anymore. The same error from above is thrown. Even if I update the ResultType accordingly to fetch all profiles async:

class TaskResultType(DjangoObjectType):
    class Meta:
        model = TaskResult
        fields = "__all__"

    profiles = graphene.List(ProfileType)

    async def resolve_profiles(self, info):
        return await sync_to_async(
            Profile.objects.filter, thread_sensitive=True
        )(task_result=self.id)

I thought dynamic querying of objects and subscribing to nested objects would be an out of the box functionality but it is really difficult. I can't be the only person using this repo to actually receive ORM data?

Flask SocketIO Implementation

  • GraphQL AioWS version: 0.3.1
  • Python version: 3.7
  • Operating System: MacOS 10.15.5

Description

I have the flask subscription example up and running and everything worked perfect. I'm trying to get it to work using flask socketio without any luck. Has anybody else ran into this using flask socketio? I feel like it's probably really obvious but I've searched pretty extensively with no luck.

Here is the original flask example(I left out the rest of the file since it's identical to the example) with the flask sockets commented out.

#sockets = Sockets(app)
socket_io = SocketIO(app)

# @sockets.route('/subscriptions')
# def echo_socket(ws):
#     subscription_server.handle(ws)
#     return []

@socket_io.on('subscriptions', namespace='/subscriptions')
def echo_socket(ws):
    subscription_server.handle(ws)
    return []

There was no errors, but also nothing returned in GraphiQL when I tested the subscription.

Is graphql-ws dead?

Seems nothing is in the progress on the library since July. Are there any plans on supporting the GraphQL Subscription anytime soon?

Impossible to use HTTP Headers with GraphIQL when using graphql-ws

  • graphql-ws version : 0.4.4
  • GraphQL-core version : 2.3.2
  • graphene-django version :2.15.0
  • Python version: Python 3.8.9
  • django version : 3.2.9

GraphQL-ws seems to work fine for me but when I'm using it it seems like I have a version of GraphIQl where the "HTTP Headers" window is missing. How could I solve that ?

In my settings.py : INSTALLED_APPS = [ ... 'channels', 'graphql_ws.django', 'graphene_django', ]

[Question] How to get payload on connect in aiohttp

simple example:

from graphql_ws.aiohttp import AiohttpSubscriptionServer
from .schema import schema

subscription_server = AiohttpSubscriptionServer(schema)


async def subscriptions(request):
    ws = web.WebSocketResponse(protocols=('graphql-ws',))
    await ws.prepare(request)
    # payload = request.get('payload') ???
    await subscription_server.handle(ws, request_context=payload)
    return ws


app = web.Application()
app.router.add_get('/subscriptions', subscriptions)

web.run_app(app, port=8000)

How to get payload when init connection to socket?

{"type":"connection_init","payload":{"FOO":"BAR"}}

No module named 'graphql.execution.executors' using graphql-ws

  • Python version: Python 3.8.10
  • Operating System: Ubuntu 20.04.3 LTS

Hi!
I'm using graphql-ws module to make a simple subscription. To write this webapp's part I followed documentation posted on git. I thought all was ok but when I run my server it raises this error:

File "/home/maestro/exelio/gpexe/gpexe/urls.py", line 16, in <module>
    from graphql_ws.django_channels import GraphQLSubscriptionConsumer
  File "/home/maestro/exelio/gpexe/env2/lib/python3.8/site-packages/graphql_ws/django_channels.py", line 11, in <module>
    from .base_sync import BaseSyncSubscriptionServer
  File "/home/maestro/exelio/gpexe/env2/lib/python3.8/site-packages/graphql_ws/base_sync.py", line 1, in <module>
    from graphql.execution.executors.sync import SyncExecutor
ModuleNotFoundError: No module named 'graphql.execution.executors'

I read that grapql-ws requires graphql-core==2.* , but i need the latest version of it...
Here is a part of my requirments:

graphene~=3.0b7
graphene-django==3.0.0b7
django-graphql-jwt==0.3.2
graphql-ws==0.4.4
graphene-django-optimizer==0.9.0
graphene_file_upload==1.3.0
graphql-core==3.1.6

How can I fix it? Thanks for the help in advance!

Unbreakable flask_gevent subscription resolving

  • GraphQL AioWS version: Flask==1.1.2 Flask-Sockets==0.2.1 graphene==2.1.8 graphql-ws==0.3.1
  • Python version: 3.7.3
  • Operating System: Windows x64

Description

I checked the flask_gevent example and finded out the resolve stream does not ended when client is disconnected.

I mean the lambda inside of this

    def resolve_random_int(root, info):
        return Observable.interval(1000).map(lambda i: RandomType(seconds=i, random_int=random.randint(0, 500)))

continues be called every 1000 ms after a client breaks ws connection.

Is there way to break resolving somehow? I want to a client be subscribed all time and got a new portion of data time by time

Django Channels Instructions/Support is Outdated

  • GraphQL AioWS version: 0.3.1
  • Python version: 3.9.0
  • Operating System: Ubuntu/Alpine
  • Django version: 3.1.2
  • Django Channels version: 2.4.0
  • ASGIRef version: 3.2.10

Description

The Django Channels Routing example is outdated:

ImportError: cannot import name 'route_class' from 'channels.routing' (/home/MYUSERNAME/.pyenv/versions/3.9.0/envs/ENVNAME/src/channels/channels/routing.py)

What I Did

route_class seems to be from 1.1.8x, which is from 2017. It's too old of a package for me to revert to. There are nested dependencies in Django and Channels. There should at least be an alternative in the instructions on how to achieve the same result in Channels 2.4.0+.

Publish graphql-ws to Conda Forge

  • GraphQL WS version: 0.3.0
  • Python version: 3.7
  • Operating System: Ubuntu LTS

Description

Hi, we are using graphql-ws as part of our system. We have a meta-package that installs multiple dependencies including one JS app. Among the dependencies, the last external dependency not already in Conda Forge is graphql-ws.

We are starting the process to publish it there, which is pretty straightforward, and I am adding myself as maintainer (basically will update recipe and merge/create PR's/issues if necessary).

What I Did

Created recipe for Conda Forge conda-forge/staged-recipes#9765 with myself as maintainer.

Happy if any project maintainer would like to be a Conda recipe maintainer too - this would allow for faster release to Conda Forge and/or create any automation later.

Thanks!

graphql-core v3?

Hello!

Is there any plan to update graphql-core dependency to the latest version?

Bind subscriptions to mutations

  • GraphQL AioWS version: 0.3.0
  • Python version: 3.6
  • Operating System: ubuntu
  • Django: 1.11
  • Channels: 2.1

Description

Hello, how would I do if I wanted to bind the subscription to an update from a mutation ? For instance, I would like to subscribe to the adding of a particular type of object, a sort of "AddObjectMutation". The example in the documentation is nice but quite far from a real usage.

Thanks in advance,

GeventSubscriptionServer ignore schema validation errors

  • GraphQL AioWS version: 0.3.1
  • Python version: Python 3.8.5
  • Operating System: Debian

Description

GeventSubscriptionServer ignore any schema validation errors.
I use Altair GraphQL client. When I send subscription request with schema text errors (for example: wrong field name), this error is covered by GeventSubscriptionServer and just raise "A subscription must return an observable"

I think problem in following lines in gevent.py, line 74:75:

assert isinstance(execution_result, Observable), \ "A subscription must return an observable"

There's check only for Observable, but in case of schema error its return ExecutionResult object with errors, then assert is failed, another error is raising

What I Did

Any subscription query with any schema error

ASGI app implementation

Description

Django channels implements the ASGI spec. Many other frameworks also implement that spec, and any ASGI-compliant "app" can be mounted as part of another ASGI app(though usually framework-specific integrations are created to better exploit framework-specific features).

Recent versions of django channels and Starlette implements ASGI 3.0.

An ASGI implementation should enable integration with django-channels, starlette(and starlette-based frameworks) and any other asgi-based frameworks.

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

  • 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.

Playground+SSL+subscriptions...

  • GraphQL AioWS version: 0.3.0
  • Python version: 3.7.1
  • Operating System: Debian 10

queries using SSL from the GraphQL playground work; subscriptions do not.

I added an ssl_context to web.run_app; queries worked fine from the Playground client.

But when I tried a subscription (and it worked without SSL!):

Playground: "error": "Could not connect to websocket endpoint ws://myhostnamealias.com:9000/subscriptions. Please check if the endpoint url is correct."

Server: SSL error in data received protocol: <asyncio.sslproto.SSLProtocol object at 0x7f03ce8cbe48> transport: <_SelectorSocketTransport closing fd=8 read=idle write=<idle, bufsize=0>> Traceback (most recent call last): File "/usr/lib/python3.7/asyncio/sslproto.py", line 526, in data_received ssldata, appdata = self._sslpipe.feed_ssldata(data) File "/usr/lib/python3.7/asyncio/sslproto.py", line 189, in feed_ssldata self._sslobj.do_handshake() File "/usr/lib/python3.7/ssl.py", line 763, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [SSL: HTTP_REQUEST] http request (_ssl.c:1056)

Now one curious thing I noticed: I was using certs for a specific host, and was using that same host in my URL in the GraphQL playground; but the playground error was using an alias host for the box. Is that coming from GraphQL Playground doing a reverse IP lookup? Or was aiohttp or graphql-ws doing a reverse name lookup somewhere because I used the aiohttp default host of "0.0.0.0"??

To rule that out, I reran the server with certificates for the other host name GraphQL had somewhere got and thought was the "right" one to use, and on aiohttp's web.run_app I used a "host" argument with the fully qualified host name the playground seemed to think was the "correct" one. Same result: queries worked; a subscription query reissued the same "Could not connect to websocket endpoint..." error. Removed the SSL from aiohttp and used "http:" in my queries+subscriptions -all worked.

Are there perhaps extra config options for the Playground when using SSL+subscriptions? I couldn't find any relevant ones in a search. Who's injecting that alias name into the Playground's error message? (even though my URL in playground did NOT use it...) Playground? aiohttp? Or graphql-ws??

Something else I just noticed: the Playground client error is showing a "ws:" scheme, as opposed to a "wss:" one. Perhaps that has to be configured in the Playground client? In the graphql-ws aiohttp example, "protocols" is set to "graphql-ws"; is "graphql-wss" also supported? (or about to be supported??)

Passing the request_context to graphql

Not sure if this is intended or not but shouldn't the context be taken outside the message processing?
In base.py:

def execute(self, request_context, params):
        return graphql(
            self.schema, **dict(params, allow_subscriptions=True))

Should be:

self.schema, **dict(params, context_value= request_context, allow_subscriptions=True))

Otherwise, how can you pass the request_context? Does it make sense to be per message? Or is it intended for another use?

Error running Django with graphql-ws

  • GraphQL AioWS version: running off master branch
  • Python version: 3.9.5
  • Operating System: Arch Linux

Description

I'm trying to run a basic Django server with subscriptions enabled and I'm receiving an error on the command line.

What I Did

I prepared a basic Django server, enabled channels, and confirmed everything was working.

I then setup graphql-ws as described in the README. When I launch the server I see the following on the command line (it gets printed repeatedly):

Exception inside application: object.__init__() takes exactly one argument (the instance to initialize)
Traceback (most recent call last):
  File "/home/luke/.cache/pypoetry/virtualenvs/annai-EdSLGIjn-py3.9/lib/python3.9/site-packages/channels/staticfiles.py", line 44, in __call__
    return await self.application(scope, receive, send)
  File "/home/luke/.cache/pypoetry/virtualenvs/annai-EdSLGIjn-py3.9/lib/python3.9/site-packages/channels/routing.py", line 71, in __call__
    return await application(scope, receive, send)
  File "/home/luke/.cache/pypoetry/virtualenvs/annai-EdSLGIjn-py3.9/lib/python3.9/site-packages/channels/routing.py", line 150, in __call__
    return await application(
  File "/home/luke/.cache/pypoetry/virtualenvs/annai-EdSLGIjn-py3.9/lib/python3.9/site-packages/asgiref/compatibility.py", line 33, in new_application
    instance = application(scope)
  File "/home/luke/.cache/pypoetry/virtualenvs/annai-EdSLGIjn-py3.9/lib/python3.9/site-packages/channels/generic/websocket.py", line 159, in __init__
    super().__init__(*args, **kwargs)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)

test graphql-ws subscription

  • GraphQL AioWS version: 0.3.1
  • Python version: 3.5.3
  • Operating System: UBUNTU 18.04

Description

Hello, i have a simple subscription which receive a just number. It works fine when i execute in Altair, but when i try to test it (I use pytest) it fails when i try to send my request

Here is my subscription (work fine)

    @async_generator
    async def resolve_get_number(self, info, number):
        await yield_(number)
        await asyncio.sleep(1.)

### my test

```
async def test_url(
       cli):
    async with cli.ws_connect("/subscriptions") as websocket:
          my_dict = dict(id=1, type='start', payload={ # NOQA
                    'query': 'subscription{getNumber(number:10)}', 
                    'variables': {},
                }) 
            ss = await websocket.send_str(json.dumps(my_dict)) #  None
            msg = await websocket.recieve() # recieve empty WSMessage(type=<WSMsgType.CLOSED: 257>, data=None, extra=None)
            await websocket.close()
    assert 'ok' == 'ok'

  File "/home/dmitriy/.pyenv/versions/3.5.3/lib/python3.5/asyncio/futures.py", line 380, in __iter__
    yield self  # This tells Task to wait for completion.
```

resolve_ methods on nested objects

Is it possible to use resolve_ method on nested objects like in following updated aiohttp example code?

import random
import asyncio
import graphene


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


class RandomType(graphene.ObjectType):
    seconds = graphene.Int()
    random_int = graphene.Int()

  async def resolve_random_int(_, __):
    return random.randint(0, 500)


class Subscription(graphene.ObjectType):
    random_int = graphene.Field(RandomType)

    async def resolve_random_int(root, info):
        i = 0
        while True:
            yield RandomType(seconds=i)
            await asyncio.sleep(1.)
            i += 1


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

I'm getting following error message

{
  "id": 26,
  "type": "data",
  "payload": {
    "data": {
      "random_int": null
    },
  "errors": [
     {
       "message": "int() argument must be a string, a bytes-like object or a number, not 'AnonymousObservable'"
     }
]}}

Asyncio tasks are not destroyed

  • GraphQL AioWS version: 0.2.0
  • Python version: Python 3.6.6 (default, Jun 27 2018, 14:44:17)
  • Operating System: Debian Stretch

Then I close ws connection to server after subscription some asyncio tasks are not destroyed. If I reload page of my webapp several times, there are a lot of existing tasks. It's possible memory leak.

Example app:

from aiohttp.web import Application, run_app, WebSocketResponse
from graphql_ws.aiohttp import AiohttpSubscriptionServer

from graphene import Schema, ObjectType, Field, ID

# Schema
class HistoryRecord(ObjectType):
    id = ID()

class Query(ObjectType):
    history = Field(HistoryRecord)
    def resolve_history(self, info):
        return HistoryRecord(id='first')

class Subscription(ObjectType):
    history = Field(HistoryRecord)
    def resolve_history(self, info):
        async def resolver(info):
            yield HistoryRecord(id='second')
        return resolver(info)

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

# Server
class Server(AiohttpSubscriptionServer):
    async def on_open(self, *args, **kwargs):
        print('Connection open')
        await super().on_open(*args, **kwargs)
    def on_close(self, *args, **kwargs):
        print('Connection close')
        super().on_close(*args, **kwargs)
subscription_server = Server(schema)

# WS handler
async def subscriptions(request):
    ws = WebSocketResponse(protocols=('graphql-ws',))
    await ws.prepare(request)

    await subscription_server.handle(ws)
    return ws

# Application
app = Application()
app.router.add_get('/ws', subscriptions)

# List tasks
from asyncio import Task, get_event_loop, sleep
from sys import getrefcount
from time import time

async def test():
    count = 0
    while True:
        await sleep(15)
        count += 1
        print('. Timer iteration N', count)

        tasks = Task.all_tasks()
        print('.. Total tasks: %s' % len(tasks))
        for task in sorted(tasks, key=lambda x: str(x)):
            print('... refs={} {}'.format(getrefcount(task), task))

loop = get_event_loop()
loop.create_task(test())

# Fire!
run_app(app, host='0.0.0.0', port=8888)

Task list after 3 connections. Full log attached.

Connection open
. Timer iteration N 6
.. Total tasks: 14
... refs=4 <Task finished coro=<AiohttpSubscriptionServer._handle() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:54> result=None>
... refs=4 <Task finished coro=<AiohttpSubscriptionServer._handle() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:54> result=None>
... refs=4 <Task finished coro=<AiohttpSubscriptionServer.on_connection_init() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:93> result=None>
... refs=4 <Task finished coro=<AiohttpSubscriptionServer.on_connection_init() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:93> result=None>
... refs=5 <Task finished coro=<AiohttpSubscriptionServer.on_connection_init() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:93> result=None>
... refs=6 <Task finished coro=<AiohttpSubscriptionServer.on_start() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:101> result=None>
... refs=4 <Task finished coro=<AiohttpSubscriptionServer.on_start() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:101> result=None>
... refs=4 <Task finished coro=<AiohttpSubscriptionServer.on_start() done, defined at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:101> result=None>
... refs=6 <Task finished coro=<RequestHandler.start() done, defined at /usr/lib/python3/dist-packages/aiohttp/web_protocol.py:340> result=None>
... refs=5 <Task finished coro=<RequestHandler.start() done, defined at /usr/lib/python3/dist-packages/aiohttp/web_protocol.py:340> result=None>
... refs=4 <Task finished coro=<RequestHandler.start() done, defined at /usr/lib/python3/dist-packages/aiohttp/web_protocol.py:340> result=None>
... refs=5 <Task pending coro=<AiohttpSubscriptionServer._handle() running at /home/gik/src/graphql/graphql-ws/graphql_ws/aiohttp.py:62> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fcec832af48>()]> cb=[shield.<locals>._done_callback() at /usr/lib/python3.6/asyncio/tasks.py:688]>
... refs=8 <Task pending coro=<RequestHandler.start() running at /usr/lib/python3/dist-packages/aiohttp/web_protocol.py:376> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at0x7fcec832ab58>()]>>
... refs=6 <Task pending coro=<test() running at ./bin/ex.py:63>>
Connection close

logfile.txt

How to manage an infinite stream of values in a subscription?

  • GraphQL AioWS version:
    • aiohttp-graphql==1.0.0
    • graphene==2.0.1
    • graphql-core==2.0
    • graphql-relay==0.4.5
    • graphql-server-core==1.0.dev20170322001
    • graphql-ws==0.2.0
    • websockets==4.0.1
    • aiohttp==2.3.6
    • aiohttp-graphql==1.0.0
  • Python version: 3.6.3
  • Operating System: Ubuntu

Description

I'm have a subscription going, an async def method (like the example in the readme) but it uses while True instead of range, so it never ends, it is constantly sending (yield) new values. I can connect to it using Relay, and I can see the frames coming in DevTools, and I can see the frame telling the GraphQL server to stop on that subscription, and then the frames stop coming.

The problem

After the stop frame I get errors like this in the console:

2017-12-27 12:01:43,393 [25768] ERROR    asyncio: Task was destroyed but it is pending!
task: <Task pending coro=<AiohttpSubscriptionServer.on_start() done, defined at ~/.env/lib/python3.6/site-packages/graphql_ws/aiohttp.py:80> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fcb0dc4b918>()]>>

And sometime I have to wait a bit for the server to shut down (by hitting Ctrl-C), like if there are more "tasks" pending.

This is the subscription class I'm using:

class DataPoint(graphene.ObjectType):
    time = graphene.Float(required=True)
    value = graphene.Float(required=True)


class Subscriptions(graphene.ObjectType):
    graph_data = graphene.List(DataPoint, object_id=graphene.ID(required=True))

    async def resolve_graph_data(self, info, object_id=None):
        time = 0
        logger.debug('>>>>>>>> Start graph')
        while True:
            yield [DataPoint(time=time, value=randint(100, 400))]
            await asyncio.sleep(1.)
            time += 1
        logger.debug('>>>>>>>> End graph')

I can see the Start graph line being printed, but I never see the End graph line.

I guess what we need is some way of "breaking" the while True: loop from outside, right?

New pypi release?

The current pypi release is pretty outdated, especially the AiohttpSubscriptionServer. Would a new release be possible currently?

Release current master branch to pypi

Hello maintainers,
Thank you for this repo ๐Ÿ˜ƒ
Just wanted to know if you have any plans for releasing the current version of the master branch to PyPI?
Since release 0.3.1 does not include Django sub-module.

Django Channels Unresolved Import channels.generic.websockets

  • GraphQL AioWS version: Django 2.1.5
  • Python version: Python 3.6.6 :: Anaconda, Inc.
  • Operating System: macOS Mojave 10.14

Description

I'm trying to set up GraphQL Subscriptions with Django Channels 2, but the code keeps on throwing this error:
ModuleNotFoundError: No module named 'channels.generic.websockets'

What I Did

I checked the Django Channels docs and also the source code of graphql_ws and figured a typo:
In the first line of django_channels.py, instead of

from channels.generic.websockets import JsonWebsocketConsumer

should be

from channels.generic.websocket import JsonWebsocketConsumer

reference: https://channels.readthedocs.io/en/latest/topics/consumers.html#jsonwebsocketconsumer

python manage.py runserver

wrong version specifier in setup.py

This: graphql-core>=2.0<3 will install graphql-core-3.1.0b0 which has incompatible changes.

The correct format: graphql-core>=2.0,<3 or use the compatible release operator: graphql-core~=2.0

How to reproduce:

pip instal -r graphql-ws/examples/aiohttp/requirements.txt
python app.py

AIOHTTP implementation imports but does not use inspect.isasyncgen; Breaks Python 3.5.x

  • GraphQL-WS version: 3.0.0
  • Python version: 3.5.6
  • Operating System: Linux/Ubuntu18.04

Description

Python 3.5.* cannot work with the current package contents because PEP 525 doesn't introduce inspect.isasyncgen until Python 3.6. This function is imported but never used, and injecting a fake version lets subscriptions work (but can have unintended consequences on other libraries).

What I Did

It's just an import- try running it on Python 3.5.3+ with AIOHTTP if you need a repro.

Using Django's GenericForeignKey with Async Graphene Subscription

  • GraphQL AioWS version:
  • Python version: 3
  • Operating System: Mac OS

Description

I've implemented graphql_ws to subscribe to updates from a Notification model that uses multiple GenericForeignKeys.

My setup works well, except when I try to query information from those foreign objects. I then get the following error:

graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.

From what I seem to understand, that's because some database query operations are being done outside the async context of get_notifications (that is, in the resolve_actor function). But I'm really not clear on how I can pull the operations of resolve_actor
into the async context.

What I Did

I've unsuccessfully tried using prefetch_related (plus I'm not sure it'll work with multiple content types per Django's docs).

Here's the code

models.py


class Notification(TimeStampedModel):
	# ...
	actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor', on_delete=models.CASCADE)
	actor_object_id = models.CharField(max_length=255)
	actor = GenericForeignKey('actor_content_type', 'actor_object_id')
	# ...

schema.py

class ActorTypeUnion(graphene.Union):
	"""
	All possible types for Actors
	(The object that performed the activity.)
	"""
	class Meta:
		types = (UserType,) # here's there's only one type, but other fields have multiple


class NotificationType(DjangoObjectType):
	actor = graphene.Field(ActorTypeUnion)
	
	def resolve_actor(self, args):
		if self.actor	is not None:
			model_name = self.actor._meta.model_name
			app_label = self.actor._meta.app_label
			model = ContentType.objects.get(app_label=app_label, model=model_name)
			return model.get_object_for_this_type(pk=self.actor_object_id)
		return None
	# ...
	class Meta:
		model = Notification

class Subscription(graphene.ObjectType):
	unread_notifications = graphene.List(NotificationType)

	async def resolve_unread_notifications(self, info, **kwargs):
		user = info.context['user']
		if user.is_anonymous:
			raise Exception('Not logged in!')
		
		@database_sync_to_async
		def get_notifications(user):
			notifications = Notification.objects.filter(
				recipient=user,
				read=False,
				organization=user.active_organization,
			)
			return [notifications]

		while True:
			await asyncio.sleep(1)
			yield await get_notifications(user)

The query (things works well except when I query fields on actor)

subscription {
   unreadNotifications {
      id,
      read,
      actor {
        ... on UserType {
          __typename,
          id

        }
      }
    }
  }

Full traceback

Traceback (most recent call last):
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executor.py", line 452, in resolve_or_error
    return executor.execute(resolve_fn, source, info, **args)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/graphql/execution/executors/asyncio.py", line 74, in execute
    result = fn(*args, **kwargs)
  File "/Users/benjaminsoukiassian/Projects/logbook-back/logbook/notifications/schema.py", line 52, in resolve_actor
    model = ContentType.objects.get(app_label=app_label, model=model_name)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 431, in get
    num = len(clone)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 262, in __len__
    self._fetch_all()
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 1324, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/query.py", line 51, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1173, in execute_sql
    cursor = self.connection.cursor()
  File "/Users/benjaminsoukiassian/.pyenv/versions/logbook/lib/python3.8/site-packages/django/utils/asyncio.py", line 24, in inner
    raise SynchronousOnlyOperation(message)
graphql.error.located_error.GraphQLLocatedError: You cannot call this from an async context - use a thread or sync_to_async.

Thank you very much for your help

Django Channels Example issues

  • Python version: 3.6
  • Operating System: OSX Sierra

Description

When testing the Django Channels examples, I went through some errors:

1: graphql_ws/django_channels.py file is not on the latest package version. But I downloaded it manually inside the package.

2: I was getting the error "Subscriptions are not allowed. You will need to either use the subscribe function or pass allow_subscriptions=True". And, since the GraphQLView object does not support the allow_subscriptions parameter, I changed it inside the package to True (just for testing).

3. After that, I was getting the error Subscription must return Async Iterable or Observable. Reading this issue, I decided to add 'MIDDLEWARE: [] into the GRAPHENE settings variable.

4. After that, I started receiving this error: 'AnonymousObservable' object has no attribute 'errors'. Then I got a little bit frustrated and stop trying ๐Ÿ˜…

Does anyone have a clue why this is happening?

Thanks!

graphql-ws-next

I have forked graphql-ws-next (fork) and made changes to make it compatible with graphql-core v3. Would there be any interest in integrating these chagnes into graphql-ws or are the two totally split now?

django-channels v2

I'm baffled why I can't find any issues on using django-channels since channels have been out with v2 as default since february, and nor the example or the code is compatible..

There are a couple of pr's (#18 and #9), but none of them is merged..

Any status on when we will have a v2 compatible release? What is missing?

403 error (failing WebSocket opening handshake ('Access denied'))

  • GraphQL AioWS version: graphql_ws 0.4.4
  • Python version: Python 3.8.10
  • Operating System: Ubuntu

Description

I'm running Daphnie with asgi.py but when I try to test it with wscat it gives 403 (access denied) error in logs in daphne servers.

What I Did

asgi.py

import os

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import OriginValidator


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "company.settings.base")
from graphql_ws.django.routing import websocket_urlpatterns

# This configures route to /subscriptions.
# https://github.com/graphql-python/graphql-ws/blob/474f733761c04f39ccd9367190b5a85a5033fc2c/graphql_ws/django/routing.py#L20

application = ProtocolTypeRouter({
    "websocket": URLRouter(websocket_urlpatterns),
})

settings.py

# ...
INSTALLED_APPS = (
# ...
    'channels',
    "graphql_ws.django",
    'graphene_django',
# ...
)


GRAPHENE = {
    'SCHEMA': 'schema.schema', 
    'MIDDLEWARE': [
        'graphql_jwt.middleware.JSONWebTokenMiddleware',
    ],
    # https://docs.graphene-python.org/projects/django/en/latest/subscriptions/#subscriptions
    "SUBSCRIPTION_PATH": "/subscriptions",
}

ASGI_APPLICATION = 'app.asgi.application'

# ...

Then I run daphne -p 8001 app.asgi:application -v 2
In other console, I ruh websocket commands with wscat -c ws://localhost:8001/subscriptions
it was giving me the logs (errors)

2022-01-21 12:23:10,555 INFO     Listening on TCP address 127.0.0.1:8001
127.0.0.1:54400 - - [21/Jan/2022:12:23:16] "WSCONNECTING /subscriptions" - -
2022-01-21 12:23:16,476 DEBUG    Upgraded connection ['127.0.0.1', 54400] to WebSocket
2022-01-21 12:23:16,476 INFO     failing WebSocket opening handshake ('Access denied')
2022-01-21 12:23:16,477 WARNING  dropping connection to peer tcp4:127.0.0.1:54400 with abort=False: Access denied
2022-01-21 12:23:16,477 DEBUG    WebSocket ['127.0.0.1', 54400] rejected by application
127.0.0.1:54400 - - [21/Jan/2022:12:23:16] "WSREJECT /subscriptions" - -
2022-01-21 12:23:16,477 DEBUG    WebSocket closed for ['127.0.0.1', 54400]
127.0.0.1:54400 - - [21/Jan/2022:12:23:16] "WSDISCONNECT /subscriptions" - -

wscat

> root # wscat -c ws://localhost:8001/subscriptions
error: Unexpected server response: 403

Seems like authorization error. I've tried with
OriginValidator(URLRouter(websocket_urlpatterns), ["*"]), but no luck. I think it's related to graphql-ws configurations that I set up or maybe some steps I'm missing?

Paste the command(s) you ran and the output.
If there was a crash, please include the traceback here.

detect disconnect

  • GraphQL AioWS version: 0.3.1
  • Python version: 3.8.6
  • Operating System: MacOS Catalina

Description

Trying to figure out how to know when someone isn't subscribed anymore.

What I Did

Ran the example which works great but unable to know when someone is disconnected..

import asyncio
import graphene


class Subscription(graphene.ObjectType):
    count_seconds = graphene.Float(up_to=graphene.Int())

    async def resolve_count_seconds(root, info, up_to=5):
        for i in range(up_to):
            print("YIELD SECOND", i)
            yield i
            await asyncio.sleep(1.)
        yield up_to

schema = graphene.Schema(subscription=Subscription)

using this with signals

hello I am very new to this but wanted to know if there is a way to use this with django signals so as to send object to user on creation

Flask Gevent Example - can't connect from Graphql playground

  • GraphQL AioWS version: 0.3.0
  • Python version: 3.6.5
  • Operating System: Linux - Ubuntu 18.04.2 LTS

Description

Attempting to run the example gevent application and retrieve subscription information in graphql playground.

What I Did

I am running the example app via app.py in the examples/flask-gevent directory.
python app.py
Server starts and is listening on port 5000.
I then start graphql playground and point it to http://localhost:5000/graphql.
It retrieves the schema correctly as follows:

type Query {
  base: String
}

type RandomType {
  seconds: Int
  randomInt: Int
}

type Subscription {
  countSeconds(upTo: Int): Int
  randomInt: RandomType
}

I attempt to open up the websocket connection to listen for server updates via:

subscription {
   countSeconds
}

The server displayed no output whatsoever.
Graphql playground displayed the following error:

{
  "error": "Could not connect to websocket endpoint ws://localhost:5000/graphql. Please check if the endpoint url is correct."
}

What I expected:
I expected to see the normal response in graphql playground for a subscription ->

Listening ...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.