Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1be0d6b
Implementation of a websockets transport using asyncio
leszekhanusz Mar 25, 2020
159a35c
Add missing 'pathlib' dependency
leszekhanusz Mar 25, 2020
d65c697
Now using AsyncClient and AsyncTransport classes
leszekhanusz Mar 26, 2020
81b7950
Adding gql-cli.py to allow to send queries to a websocket endpoint fr…
leszekhanusz Mar 27, 2020
ae1ac36
Better management of ConnectionClosed Exceptions
leszekhanusz Apr 3, 2020
ada2d6c
Fix dependencies
leszekhanusz Apr 3, 2020
2218850
Using black to format code
leszekhanusz Apr 6, 2020
0a6863b
Fix flake8 style enforcement
leszekhanusz Apr 6, 2020
ff86744
Adding typing hints
leszekhanusz Apr 6, 2020
2398866
Adding gql-cli as a python script + added to MANIFEST
leszekhanusz Apr 6, 2020
3e314b8
setup.py adding websockets dependency only if python version is >= 3.6
leszekhanusz Apr 7, 2020
00f2433
Moving python3 code in separate files to try to fix tests for python2
leszekhanusz Apr 7, 2020
4197748
Fix typo in setup.py
leszekhanusz Apr 7, 2020
930ae7e
Using relative imports to fix mypy
leszekhanusz Apr 7, 2020
55807c2
Using relative imports to fix mypy - try 2
leszekhanusz Apr 7, 2020
58aa38a
Better management of cleanup + adding some tests
leszekhanusz Apr 8, 2020
ece46e8
Fix asyncio.wait_for for pypy3 ?
leszekhanusz Apr 8, 2020
6c90378
Fix asyncio.wait_for for pypy3 ? - try 2
leszekhanusz Apr 8, 2020
e0cf9bb
Better managenement of edge cases and 100% websocket transport coverage
leszekhanusz Apr 13, 2020
705edd1
Close is now done in a shielded task
leszekhanusz Apr 19, 2020
a329324
TESTS_PY36 add tests for async client validation
leszekhanusz Apr 26, 2020
cad804f
Merge branch 'master' into master
leszekhanusz Apr 26, 2020
f8ead74
Fix tests for pytest-asyncio==0.11.0
leszekhanusz Apr 26, 2020
804d433
Adding init_payload parameter to websockets transport to allow to spe…
leszekhanusz May 4, 2020
ec75cd0
Merge branch 'master' of https://github.com/graphql-python/gql
leszekhanusz May 4, 2020
ff22c0a
Skip online tests if --run-online pytest arg is not set
leszekhanusz May 5, 2020
268d7a7
Fix tests on pypy3
leszekhanusz May 5, 2020
c167755
Implementation of AIOHTTPTransport
leszekhanusz May 7, 2020
51df204
MAKEFILE add clean target
leszekhanusz May 9, 2020
ec5d4ee
Implementation of class AsyncClientSession
leszekhanusz May 9, 2020
789083f
Adding subscribe generator (not async) to AsyncClient
leszekhanusz May 10, 2020
41645be
Using AsyncClient as the gql.Client if python>3.6
leszekhanusz May 10, 2020
8d277c5
Update gql-cli script to use http or websockets transport depending o…
leszekhanusz May 10, 2020
5ff88a7
Adding tests for AIOHTTPTransport
leszekhanusz May 10, 2020
7faa6a2
Fix aiohttp tests
leszekhanusz May 11, 2020
c3a5c18
Fix isort/black compatibility
leszekhanusz May 11, 2020
6250068
Fix test_websocket_exception on pypy3 with python 3.6.1
leszekhanusz May 12, 2020
a9684b2
TRAVIS modify timeout factor to 100 for travis automated tests
leszekhanusz May 12, 2020
f80243f
Increase timeout for pypy3 version 3.6.1
leszekhanusz May 12, 2020
77319a1
Better management of timeouts
leszekhanusz May 13, 2020
8cae234
Set graphql-core dependency for now to 2.3.1 to fix tests
leszekhanusz May 13, 2020
6c29f39
tox.ini add --diff to isort to easily see where the problem is
leszekhanusz May 13, 2020
514cce7
setup.cfg adding ssl to known_standard_library for isort
leszekhanusz May 13, 2020
33b6aa5
Merge branch 'master' of https://github.com/graphql-python/gql
leszekhanusz May 13, 2020
cf7b5c6
Merge branch 'master' of https://github.com/graphql-python/gql
leszekhanusz May 13, 2020
6298125
README.md add sync client.subscribe usage + rename AsyncClient to Client
leszekhanusz May 13, 2020
6d0a85d
Moving pytest fixtures to conftest.py
leszekhanusz May 13, 2020
9eef3fe
tox.ini trying to combine coverage of python2.7 and 3.8
leszekhanusz May 13, 2020
d183573
Remove support for python versions < 3.6
leszekhanusz May 14, 2020
564065b
.travis.yml: stop testing on python 2.7, 3.5 and pypy
leszekhanusz May 14, 2020
e8c46b1
Merge branch 'master' into master
Cito May 15, 2020
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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,6 @@ target/

### VisualStudioCode ###
.vscode/*

# VIM
*.swp
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
env:
global:
- GQL_TESTS_TIMEOUT_FACTOR=100
language: python
sudo: false
python:
- 2.7
- 3.5
- 3.6
- 3.7
- 3.8
- 3.9-dev
- pypy
- pypy3
matrix:
include:
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ include Makefile

include tox.ini

include scripts/gql-cli

recursive-include tests *.py *.yaml *.graphql
recursive-include tests_py36 *.py

Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@ dev-setup:
python pip install -e ".[test]"

tests:
pytest tests --cov=gql -vv
pytest tests --cov=gql -vv

clean:
find . -name "*.pyc" -delete
find . -name "__pycache__" | xargs -I {} rm -rf {}
rm -rf ./htmlcov
rm -rf ./.mypy_cache
rm -rf ./.pytest_cache
rm -rf ./.tox
rm -rf ./gql.egg-info
rm -rf ./dist
rm -rf ./build
rm -f ./.coverage
239 changes: 239 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,245 @@ query = gql('''
client.execute(query)
```

With a python version > 3.6, it is possible to execute GraphQL subscriptions using the websockets transport:

```python
from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport

sample_transport = WebsocketsTransport(url='wss://your_server/graphql')

client = Client(
transport=sample_transport,
fetch_schema_from_transport=True,
)

query = gql('''
subscription yourSubscription {
...
}
''')

for result in client.subscribe(query):
print (f"result = {result!s}")
```

Note: the websockets transport can also execute queries or mutations

# Async usage with asyncio

When using the `execute` or `subscribe` function directly on the client, the execution is synchronous.
It means that we are blocked until we receive an answer from the server and
we cannot do anything else while waiting for this answer.

It is also possible to use this library asynchronously using [asyncio](https://docs.python.org/3/library/asyncio.html).

Async Features:
* Execute GraphQL subscriptions (See [using the websockets transport](#Websockets-async-transport))
* Execute GraphQL queries, mutations and subscriptions in parallel

To use the async features, you need to use an async transport:
* [AIOHTTPTransport](#HTTP-async-transport) for the HTTP(s) protocols
* [WebsocketsTransport](#Websockets-async-transport) for the ws(s) protocols

## HTTP async transport

This transport uses the [aiohttp library](https://docs.aiohttp.org)

GraphQL subscriptions are not supported on the HTTP transport.
For subscriptions you should use the websockets transport.

```python
from gql import gql, Client
from gql.transport.aiohttp import AIOHTTPTransport
import asyncio

async def main():

sample_transport = AIOHTTPTransport(
url='https://countries.trevorblades.com/graphql',
headers={'Authorization': 'token'}
)

async with Client(
transport=sample_transport,
fetch_schema_from_transport=True,
) as session:

# Execute single query
query = gql('''
query getContinents {
continents {
code
name
}
}
''')

result = await session.execute(query)

print(result)

asyncio.run(main())
```

## Websockets async transport

The websockets transport uses the apollo protocol described here:

[Apollo websockets transport protocol](https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md)

This transport allows to do multiple queries, mutations and subscriptions on the same websocket connection

```python
import logging
logging.basicConfig(level=logging.INFO)

from gql import gql, Client
from gql.transport.websockets import WebsocketsTransport
import asyncio

async def main():

sample_transport = WebsocketsTransport(
url='wss://countries.trevorblades.com/graphql',
ssl=True,
headers={'Authorization': 'token'}
)

async with Client(
transport=sample_transport,
fetch_schema_from_transport=True,
) as session:

# Execute single query
query = gql('''
query getContinents {
continents {
code
name
}
}
''')
result = await session.execute(query)
print(result)

# Request subscription
subscription = gql('''
subscription {
somethingChanged {
id
}
}
''')
async for result in session.subscribe(subscription):
print(result)

asyncio.run(main())
```

### Websockets SSL

If you need to connect to an ssl encrypted endpoint:

* use _wss_ instead of _ws_ in the url of the transport
* set the parameter ssl to True

```python
import ssl

sample_transport = WebsocketsTransport(
url='wss://SERVER_URL:SERVER_PORT/graphql',
headers={'Authorization': 'token'},
ssl=True
)
```

If you have a self-signed ssl certificate, you need to provide an ssl_context with the server public certificate:

```python
import pathlib
import ssl

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
localhost_pem = pathlib.Path(__file__).with_name("YOUR_SERVER_PUBLIC_CERTIFICATE.pem")
ssl_context.load_verify_locations(localhost_pem)

sample_transport = WebsocketsTransport(
url='wss://SERVER_URL:SERVER_PORT/graphql',
ssl=ssl_context
)
```

If you have also need to have a client ssl certificate, add:

```python
ssl_context.load_cert_chain(certfile='YOUR_CLIENT_CERTIFICATE.pem', keyfile='YOUR_CLIENT_CERTIFICATE_KEY.key')
```

### Websockets authentication

There are two ways to send authentication tokens with websockets depending on the server configuration.

1. Using HTTP Headers

```python
sample_transport = WebsocketsTransport(
url='wss://SERVER_URL:SERVER_PORT/graphql',
headers={'Authorization': 'token'},
ssl=True
)
```

2. With a payload in the connection_init websocket message

```python
sample_transport = WebsocketsTransport(
url='wss://SERVER_URL:SERVER_PORT/graphql',
init_payload={'Authorization': 'token'},
ssl=True
)
```

### Async advanced usage

It is possible to send multiple GraphQL queries (query, mutation or subscription) in parallel,
on the same websocket connection, using asyncio tasks

```python

async def execute_query1():
result = await session.execute(query1)
print(result)

async def execute_query2():
result = await session.execute(query2)
print(result)

async def execute_subscription1():
async for result in session.subscribe(subscription1):
print(result)

async def execute_subscription2():
async for result in session.subscribe(subscription2):
print(result)

task1 = asyncio.create_task(execute_query1())
task2 = asyncio.create_task(execute_query2())
task3 = asyncio.create_task(execute_subscription1())
task4 = asyncio.create_task(execute_subscription2())

await task1
await task2
await task3
await task4
```

Subscriptions tasks can be stopped at any time by running

```python
task.cancel()
```

## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)
Expand Down
2 changes: 1 addition & 1 deletion gql/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .gql import gql
from .client import Client
from .gql import gql

__all__ = ["gql", "Client"]
Loading