Skip to content

Simplify code with better base classes #46

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

Merged
merged 42 commits into from
Mar 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c00066b
Split base classes into sync and async
SmileyChris May 17, 2020
b6951ed
Fix tests to match deduplication changes
SmileyChris May 17, 2020
4fe4736
Add some base tests
SmileyChris Jun 1, 2020
f7ef09f
Add base async tests
SmileyChris Jun 6, 2020
944d949
Add django_channels tests
SmileyChris Jun 6, 2020
e4b3d9f
Remove a redundant check for an internal detail
SmileyChris May 19, 2020
7b21f0f
Move execute back to base
SmileyChris May 19, 2020
75cad35
Move operation unsubscription to BaseSubscriptionServer
SmileyChris Jun 25, 2020
e904b15
Black the example code
SmileyChris Jun 25, 2020
9499ae9
Black the modules in graphql_ws root
SmileyChris Jun 25, 2020
738f447
Skip flake8 false positives and remove unneeded import
SmileyChris Jun 28, 2020
f641e58
Update contributing doc
SmileyChris Jun 28, 2020
c007f58
Correctly test a bad graphql parameter
SmileyChris Jun 28, 2020
bb4f1be
Make removing an operation from context fail silently
SmileyChris Jun 28, 2020
a4eef79
Make async methods send an error if an operation raises an exception
SmileyChris Jun 28, 2020
3e670e6
Send completion messages when the sync observer completes / errors out.
SmileyChris Jun 28, 2020
650db34
Cody tidy
SmileyChris Jun 28, 2020
a8c2f33
Abstract ensuring async task is a future
SmileyChris Jun 29, 2020
8d32f4b
Tidy up django_channels (1) backend and example
SmileyChris Jun 29, 2020
7797c29
Update readme
SmileyChris Jun 29, 2020
5ed4f1d
Fix a readme typo
SmileyChris Jun 30, 2020
a3197d0
Recursively resolve Promises, fix async tests
SmileyChris Jul 1, 2020
84d5d17
Ignore cancellederror when closing connections
SmileyChris Jul 29, 2020
7bfc590
Fix async processing messages
SmileyChris Jul 29, 2020
583f3f0
Fix async unsubscribe
SmileyChris Jul 29, 2020
de8ced3
Move unsubscribe logic to the connection context
SmileyChris Jul 29, 2020
9bec86e
Remove a redundant async method
SmileyChris Jul 29, 2020
a1d2ebc
Only send messages for operations that exist
SmileyChris Jul 30, 2020
94d8740
Iterators are considered awaitable with the new method, so check only…
SmileyChris Jul 30, 2020
218c7fc
Add request context directly to the payload rather than a request_con…
SmileyChris Jul 30, 2020
ae0b0c7
Correctly unsubscribe after on_start operation is complete
SmileyChris Nov 24, 2020
a964800
Fix tests
SmileyChris Nov 24, 2020
cdbdda1
Async unsubscription needs to wait around for the future to cancel
SmileyChris Mar 29, 2021
4554636
Allow collection of tests even if aiohttp isn't installed
SmileyChris Mar 29, 2021
56f46a1
Make the python 2 async observer send graphql error for exceptions ex…
SmileyChris Mar 29, 2021
5abd858
asyncio.wait receiving coroutines is deprecated, create tasks explicitly
SmileyChris Mar 29, 2021
d2d55a1
Tidy up a test warning
SmileyChris Mar 29, 2021
f7cb773
Rename TestServer to avoid it being collected by pytest
SmileyChris Mar 29, 2021
80890c3
Update test matrix
SmileyChris Mar 29, 2021
9ced609
Update travis python versions
SmileyChris Mar 29, 2021
3adfaa9
Try using a newer travis dist to fix cryptography building issues
SmileyChris Mar 29, 2021
703e407
Use python 3.6 friendly asyncio method
SmileyChris Mar 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ deploy:
tags: true
install: pip install -U tox-travis
language: python
dist: focal
python:
- 3.9
- 3.8
- 3.7
- 3.6
- 3.5
- 2.7
script: tox
15 changes: 2 additions & 13 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Ready to contribute? Here's how to set up `graphql_ws` for local development.

$ mkvirtualenv graphql_ws
$ cd graphql_ws/
$ python setup.py develop
$ pip install -e .[dev]

4. Create a branch for local development::

Expand All @@ -79,11 +79,8 @@ Ready to contribute? Here's how to set up `graphql_ws` for local development.
5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox::

$ flake8 graphql_ws tests
$ python setup.py test or py.test
$ tox

To get flake8 and tox, just pip install them into your virtualenv.

6. Commit your changes and push your branch to GitHub::

$ git add .
Expand All @@ -101,14 +98,6 @@ Before you submit a pull request, check that it meets these guidelines:
2. If the pull request adds functionality, the docs should be updated. Put
your new functionality into a function with a docstring, and add the
feature to the list in README.rst.
3. The pull request should work for Python 2.6, 2.7, 3.3, 3.4 and 3.5, and for PyPy. Check
3. The pull request should work for Python 2.7, 3.5, 3.6, 3.7 and 3.8. Check
https://travis-ci.org/graphql-python/graphql_ws/pull_requests
and make sure that the tests pass for all supported Python versions.

Tips
----

To run a subset of tests::

$ py.test tests.test_graphql_ws

199 changes: 99 additions & 100 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
==========
GraphQL WS
==========

Websocket server for GraphQL subscriptions.
Websocket backend for GraphQL subscriptions.

Supports the following application servers:

Python 3 application servers, using asyncio:

* `aiohttp`_
* `websockets compatible servers`_ such as Sanic
(via `websockets <https://github.com/aaugustin/websockets/>`__ library)

Currently supports:
Python 2 application servers:

* `Gevent compatible servers`_ such as Flask
* `Django v1.x`_
(via `channels v1.x <https://channels.readthedocs.io/en/1.x/inshort.html>`__)

* `aiohttp <https://github.com/graphql-python/graphql-ws#aiohttp>`__
* `Gevent <https://github.com/graphql-python/graphql-ws#gevent>`__
* Sanic (uses `websockets <https://github.com/aaugustin/websockets/>`__
library)

Installation instructions
=========================
Expand All @@ -19,21 +28,54 @@ 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:

.. code:: python

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
~~~~~~~

For setting up, just plug into your aiohttp server.
Then just plug into your aiohttp server.

.. code:: python

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)
Expand All @@ -47,21 +89,26 @@ For setting up, just plug into your aiohttp server.

web.run_app(app, port=8000)

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


Works with any framework that uses the websockets library for it’s
websocket implementation. For this example, plug in your Sanic server.
websockets compatible servers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

.. code:: python

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)
Expand All @@ -70,80 +117,73 @@ websocket implementation. For this example, plug in your Sanic server.

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

And then, plug into a subscribable schema:

Python 2 servers
-----------------

Create a subscribable schema like this:

.. code:: python

import asyncio
import graphene
from rx import Observable


class Query(graphene.ObjectType):
base = graphene.String()
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
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)

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

Gevent
~~~~~~
Gevent compatible servers
~~~~~~~~~~~~~~~~~~~~~~~~~

For setting up, just plug into your Gevent server.
Then just plug into your Gevent server, for example, Flask:

.. code:: python

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 []

And then, plug into a subscribable schema:

.. code:: python

import graphene
from rx import Observable


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


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)

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

Django Channels
~~~~~~~~~~~~~~~
Django v1.x
~~~~~~~~~~~

First ``pip install channels`` and it to your django apps
For Django v1.x and Django Channels v1.x, setup your schema in ``settings.py``

Then add the following to your settings.py
.. code:: python

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

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

.. code:: python

Expand All @@ -153,53 +193,9 @@ Then add the following to your settings.py
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "django_subscriptions.urls.channel_routing",
},

}

Setup your graphql schema

.. code:: python

import graphene
from rx import Observable


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

def resolve_hello(self, info, **kwargs):
return 'world'

class Subscription(graphene.ObjectType):

count_seconds = graphene.Int(up_to=graphene.Int())


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
)

Setup your schema in settings.py

.. code:: python

GRAPHENE = {
'SCHEMA': 'path.to.schema'
}

and finally add the channel routes
And finally add the channel routes

.. code:: python

Expand All @@ -209,3 +205,6 @@ and finally add the channel routes
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
21 changes: 11 additions & 10 deletions examples/aiohttp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,35 @@

async def graphql_view(request):
payload = await request.json()
response = await schema.execute(payload.get('query', ''), return_promise=True)
response = await schema.execute(payload.get("query", ""), return_promise=True)
data = {}
if response.errors:
data['errors'] = [format_error(e) for e in response.errors]
data["errors"] = [format_error(e) for e in response.errors]
if response.data:
data['data'] = response.data
data["data"] = response.data
jsondata = json.dumps(data,)
return web.Response(text=jsondata, headers={'Content-Type': 'application/json'})
return web.Response(text=jsondata, headers={"Content-Type": "application/json"})


async def graphiql_view(request):
return web.Response(text=render_graphiql(), headers={'Content-Type': 'text/html'})
return web.Response(text=render_graphiql(), headers={"Content-Type": "text/html"})


subscription_server = AiohttpSubscriptionServer(schema)


async def subscriptions(request):
ws = web.WebSocketResponse(protocols=('graphql-ws',))
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)
app.router.add_get('/graphiql', graphiql_view)
app.router.add_get('/graphql', graphql_view)
app.router.add_post('/graphql', graphql_view)
app.router.add_get("/subscriptions", subscriptions)
app.router.add_get("/graphiql", graphiql_view)
app.router.add_get("/graphql", graphql_view)
app.router.add_post("/graphql", graphql_view)

web.run_app(app, port=8000)
Loading