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/MANIFEST.in b/MANIFEST.in
index 2c0ba39..e58af99 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -5,6 +5,9 @@ include LICENSE
include README.rst
include tox.ini
+graft graphql_ws/django/templates
+graft examples
+prune examples/django_channels2/.cache
graft tests
global-exclude __pycache__
global-exclude *.py[co]
diff --git a/README.rst b/README.rst
index 90ee500..4882551 100644
--- a/README.rst
+++ b/README.rst
@@ -1,14 +1,24 @@
+==========
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)
+ * `Django v2+`_
-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 +29,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,16 +90,20 @@ 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
+
+
+websockets compatible servers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Works with any framework that uses the websockets library for it’s
-websocket implementation. For this example, plug in your Sanic server.
+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__)
@@ -70,49 +117,61 @@ 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:
-.. code:: python
+Django v2+
+~~~~~~~~~~
- import asyncio
- import graphene
+Django Channels 2
+~~~~~~~~~~~~~~~~~
- class Query(graphene.ObjectType):
- base = graphene.String()
+Set up with Django Channels just takes three steps:
+1. Install the apps
+2. Set up your schema
+3. Configure the channels router application
- class Subscription(graphene.ObjectType):
- count_seconds = graphene.Float(up_to=graphene.Int())
+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:
- async def resolve_count_seconds(root, info, up_to):
- for i in range(up_to):
- yield i
- await asyncio.sleep(1.)
- yield up_to
+.. code:: python
+ INSTALLED_APPS = [
+ "channels",
+ "graphql_ws.django",
+ "graphene_django",
+ # ...
+ ]
- schema = graphene.Schema(query=Query, subscription=Subscription)
+Point to your schema in Django settings:
-You can see a full example here:
-https://github.com/graphql-python/graphql-ws/tree/master/examples/aiohttp
+.. code:: python
-Gevent
-~~~~~~
+ GRAPHENE = {
+ 'SCHEMA': 'yourproject.schema.schema'
+ }
-For setting up, just plug into your Gevent server.
+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:
.. code:: python
- subscription_server = GeventSubscriptionServer(schema)
- app.app_protocol = lambda environ_path_info: 'graphql-ws'
+ ASGI_APPLICATION = 'graphql_ws.django.routing.application'
+ # or
+ ASGI_APPLICATION = 'graphql_ws.django.routing.auth_application'
- @sockets.route('/subscriptions')
- def echo_socket(ws):
- subscription_server.handle(ws)
- return []
+Run ``./manage.py runserver`` and go to
+`http://localhost:8000/graphql`__ to test!
-And then, plug into a subscribable schema:
+
+Python 2 servers
+-----------------
+
+Create a subscribable schema like this:
.. code:: python
@@ -121,7 +180,11 @@ And then, plug into a subscribable schema:
class Query(graphene.ObjectType):
- base = graphene.String()
+ hello = graphene.String()
+
+ @staticmethod
+ def resolve_hello(obj, info, **kwargs):
+ return "world"
class Subscription(graphene.ObjectType):
@@ -135,71 +198,54 @@ And then, plug into a subscribable schema:
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
-~~~~~~~~~~~~~~~
+Gevent compatible servers
+~~~~~~~~~~~~~~~~~~~~~~~~~
-First ``pip install channels`` and it to your django apps
-
-Then add the following to your settings.py
+Then just plug into your Gevent server, for example, Flask:
.. code:: python
- CHANNELS_WS_PROTOCOLS = ["graphql-ws", ]
- CHANNEL_LAYERS = {
- "default": {
- "BACKEND": "asgiref.inmemory.ChannelLayer",
- "ROUTING": "django_subscriptions.urls.channel_routing",
- },
-
- }
-
-Setup your graphql schema
+ from flask_sockets import Sockets
+ from graphql_ws.gevent import GeventSubscriptionServer
+ from schema import schema
-.. code:: python
-
- import graphene
- from rx import Observable
-
-
- class Query(graphene.ObjectType):
- hello = graphene.String()
+ subscription_server = GeventSubscriptionServer(schema)
+ app.app_protocol = lambda environ_path_info: 'graphql-ws'
- def resolve_hello(self, info, **kwargs):
- return 'world'
- class Subscription(graphene.ObjectType):
+ @sockets.route('/subscriptions')
+ def echo_socket(ws):
+ subscription_server.handle(ws)
+ return []
- count_seconds = graphene.Int(up_to=graphene.Int())
+You can see a full example here:
+https://github.com/graphql-python/graphql-ws/tree/master/examples/flask_gevent
+Django v1.x
+~~~~~~~~~~~
- 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)
-
+For Django v1.x and Django Channels v1.x, setup your schema in ``settings.py``
+.. code:: python
- schema = graphene.Schema(
- query=Query,
- subscription=Subscription
- )
+ GRAPHENE = {
+ 'SCHEMA': 'yourproject.schema.schema'
+ }
-Setup your schema in settings.py
+Then ``pip install "channels<1"`` and it to your django apps, adding the
+following to your ``settings.py``
.. code:: python
- GRAPHENE = {
- 'SCHEMA': 'path.to.schema'
+ CHANNELS_WS_PROTOCOLS = ["graphql-ws", ]
+ CHANNEL_LAYERS = {
+ "default": {
+ "BACKEND": "asgiref.inmemory.ChannelLayer",
+ "ROUTING": "django_subscriptions.urls.channel_routing",
+ },
}
-and finally add the channel routes
+And finally add the channel routes
.. code:: python
@@ -209,3 +255,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',
+