diff --git a/.travis.yml b/.travis.yml
index a3ef963..67a356c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -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
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 01d606e..a2315ad 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -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::
@@ -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 .
@@ -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
-
diff --git a/README.rst b/README.rst
index 90ee500..fb968b6 100644
--- a/README.rst
+++ b/README.rst
@@ -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 `__ library)
-Currently supports:
+Python 2 application servers:
+
+ * `Gevent compatible servers`_ such as Flask
+ * `Django v1.x`_
+ (via `channels v1.x `__)
-* `aiohttp `__
-* `Gevent `__
-* Sanic (uses `websockets `__
- library)
Installation instructions
=========================
@@ -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)
@@ -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)
@@ -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
@@ -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
@@ -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
diff --git a/examples/aiohttp/app.py b/examples/aiohttp/app.py
index 56dcaff..336a0c6 100644
--- a/examples/aiohttp/app.py
+++ b/examples/aiohttp/app.py
@@ -10,24 +10,25 @@
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)
@@ -35,9 +36,9 @@ async def subscriptions(request):
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)
diff --git a/examples/aiohttp/schema.py b/examples/aiohttp/schema.py
index 3c23d00..ae107c7 100644
--- a/examples/aiohttp/schema.py
+++ b/examples/aiohttp/schema.py
@@ -20,14 +20,14 @@ 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.)
+ await asyncio.sleep(1.0)
yield up_to
async def resolve_random_int(root, info):
i = 0
while True:
yield RandomType(seconds=i, random_int=random.randint(0, 500))
- await asyncio.sleep(1.)
+ await asyncio.sleep(1.0)
i += 1
diff --git a/examples/aiohttp/template.py b/examples/aiohttp/template.py
index 0b74e96..709f7cf 100644
--- a/examples/aiohttp/template.py
+++ b/examples/aiohttp/template.py
@@ -1,9 +1,9 @@
-
from string import Template
def render_graphiql():
- return Template('''
+ return Template(
+ """
@@ -116,10 +116,11 @@ def render_graphiql():
);
-''').substitute(
- GRAPHIQL_VERSION='0.10.2',
- SUBSCRIPTIONS_TRANSPORT_VERSION='0.7.0',
- subscriptionsEndpoint='ws://localhost:8000/subscriptions',
+