Skip to content
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

Allow to pass arguments to websockets.connect + some ssl tests + some cleaning #83

Merged
merged 60 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 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
1e84f4e
CONTRIBUTING.md fix pip install typo and remove spaces at end of lines
leszekhanusz May 15, 2020
88eb0ac
MAKEFILE add all_tests target to run also run online tests
leszekhanusz May 15, 2020
6286c4e
websockets allow to pass extra arguments to websockets.connect
leszekhanusz May 15, 2020
1e467d8
Fix leftover from bad merge
Cito May 15, 2020
632e5df
Refactor: being more explicit with arguments to async transports
leszekhanusz May 15, 2020
5d6939c
README remove now unnecessary ssl=True and use_json=True in examples
leszekhanusz May 15, 2020
c8d5518
Merge branch 'master' of https://github.com/graphql-python/gql
leszekhanusz May 15, 2020
2f90761
Merge branch 'master' into master
Cito May 15, 2020
84713b9
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
24 changes: 12 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ We welcome all kinds of contributions:

## Getting started

If you have a specific contribution in mind, be sure to check the
[issues](https://github.com/graphql-python/gql/issues)
and [pull requests](https://github.com/graphql-python/gql/pulls)
in progress - someone could already be working on something similar
If you have a specific contribution in mind, be sure to check the
[issues](https://github.com/graphql-python/gql/issues)
and [pull requests](https://github.com/graphql-python/gql/pulls)
in progress - someone could already be working on something similar
and you can help out.

## Project setup
Expand All @@ -31,10 +31,10 @@ virtualenv gql-dev
Activate the virtualenv and install dependencies by running:

```console
python pip install -e[dev]
python pip install -e.[dev]
```

If you are using Linux or MacOS, you can make use of Makefile command
If you are using Linux or MacOS, you can make use of Makefile command
`make dev-setup`, which is a shortcut for the above python command.

### Development on Conda
Expand All @@ -55,7 +55,7 @@ pip install -e.[dev]

And you ready to start development!

<!-- TODO: Provide environment.yml file for conda env -->
<!-- TODO: Provide environment.yml file for conda env -->

## Running tests

Expand All @@ -65,7 +65,7 @@ After developing, the full test suite can be evaluated by running:
pytest tests --cov=gql -vv
```

If you are using Linux or MacOS, you can make use of Makefile command
If you are using Linux or MacOS, you can make use of Makefile command
`make tests`, which is a shortcut for the above python command.

You can also test on several python environments by using tox.
Expand All @@ -77,8 +77,8 @@ Install tox:
pip install tox
```

Run `tox` on your virtualenv (do not forget to activate it!)
and that's it!
Run `tox` on your virtualenv (do not forget to activate it!)
and that's it!

### Running tox on Conda

Expand All @@ -93,5 +93,5 @@ This install tox underneath so no need to install it before.

Then uncomment the `requires = tox-conda` line on `tox.ini` file.

Run `tox` and you will see all the environments being created
and all passing tests. :rocket:
Run `tox` and you will see all the environments being created
and all passing tests. :rocket:
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ include tox.ini
include scripts/gql-cli

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

prune gql-checker

Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
.PHONY: clean tests

dev-setup:
python pip install -e ".[test]"

tests:
pytest tests --cov=gql -vv
pytest tests tests_py36 --cov=gql --cov-report=term-missing -vv

all_tests:
pytest tests tests_py36 --cov=gql --cov-report=term-missing --run-online -vv

clean:
find . -name "*.pyc" -delete
Expand Down
14 changes: 3 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ from gql.transport.requests import RequestsHTTPTransport

sample_transport=RequestsHTTPTransport(
url='https://countries.trevorblades.com/',
use_json=True,
headers={
"Content-type": "application/json",
},
verify=False,
retries=3,
)
Expand Down Expand Up @@ -215,7 +211,6 @@ async def main():

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

Expand Down Expand Up @@ -262,8 +257,7 @@ import ssl

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

Expand Down Expand Up @@ -298,8 +292,7 @@ There are two ways to send authentication tokens with websockets depending on th
```python
sample_transport = WebsocketsTransport(
url='wss://SERVER_URL:SERVER_PORT/graphql',
headers={'Authorization': 'token'},
ssl=True
headers={'Authorization': 'token'}
)
```

Expand All @@ -308,8 +301,7 @@ sample_transport = WebsocketsTransport(
```python
sample_transport = WebsocketsTransport(
url='wss://SERVER_URL:SERVER_PORT/graphql',
init_payload={'Authorization': 'token'},
ssl=True
init_payload={'Authorization': 'token'}
)
```

Expand Down
14 changes: 7 additions & 7 deletions gql/transport/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(
auth: Optional[BasicAuth] = None,
ssl: Union[SSLContext, bool, Fingerprint] = False,
timeout: Optional[int] = None,
**kwargs,
client_session_args: Dict[str, Any] = {},
) -> None:
"""Initialize the transport with the given aiohttp parameters.

Expand All @@ -44,15 +44,15 @@ def __init__(
:param cookies: Dict of HTTP cookies.
:param auth: BasicAuth object to enable Basic HTTP auth if needed
:param ssl: ssl_context of the connection. Use ssl=False to disable encryption
:param kwargs: Other parameters forwarded to aiohttp.ClientSession
:param client_session_args: Dict of extra parameters passed to aiohttp.ClientSession
"""
self.url: str = url
self.headers: Optional[LooseHeaders] = headers
self.cookies: Optional[LooseCookies] = cookies
self.auth: Optional[BasicAuth] = auth
self.ssl: Union[SSLContext, bool, Fingerprint] = ssl
self.timeout: Optional[int] = timeout
self.kwargs = kwargs
self.client_session_args = client_session_args

self.session: Optional[aiohttp.ClientSession] = None

Expand All @@ -78,7 +78,7 @@ async def connect(self) -> None:
)

# Adding custom parameters passed from init
client_session_args.update(self.kwargs)
client_session_args.update(self.client_session_args)

self.session = aiohttp.ClientSession(**client_session_args)

Expand All @@ -95,7 +95,7 @@ async def execute(
document: Document,
variable_values: Optional[Dict[str, str]] = None,
operation_name: Optional[str] = None,
**kwargs,
extra_args: Dict[str, Any] = {},
) -> ExecutionResult:
"""Execute the provided document AST against the configured remote server.
This uses the aiohttp library to perform a HTTP POST request asynchronously to the remote server.
Expand All @@ -114,8 +114,8 @@ async def execute(
"json": payload,
}

# Pass kwargs to aiohttp post method
post_args.update(kwargs)
# Pass post_args to aiohttp post method
post_args.update(extra_args)

if self.session is None:
raise TransportClosed("Transport is not connected")
Expand Down
29 changes: 22 additions & 7 deletions gql/transport/websockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def __init__(
connect_timeout: int = 10,
close_timeout: int = 10,
ack_timeout: int = 10,
connect_args: Dict[str, Any] = {},
) -> None:
"""Initialize the transport with the given request parameters.

Expand All @@ -107,6 +108,7 @@ def __init__(
:param connect_timeout: Timeout in seconds for the establishment of the websocket connection.
:param close_timeout: Timeout in seconds for the close.
:param ack_timeout: Timeout in seconds to wait for the connection_ack message from the server.
:param connect_args: Other parameters forwarded to websockets.connect
"""
self.url: str = url
self.ssl: Union[SSLContext, bool] = ssl
Expand All @@ -117,6 +119,8 @@ def __init__(
self.close_timeout: int = close_timeout
self.ack_timeout: int = ack_timeout

self.connect_args = connect_args

self.websocket: Optional[WebSocketClientProtocol] = None
self.next_query_id: int = 1
self.listeners: Dict[int, ListenerQueue] = {}
Expand Down Expand Up @@ -460,16 +464,27 @@ async def connect(self) -> None:

if self.websocket is None:

# If the ssl parameter is not provided, generate the ssl value depending on the url
ssl: Optional[Union[SSLContext, bool]]
if self.ssl:
ssl = self.ssl
else:
ssl = True if self.url.startswith("wss") else None

# Set default arguments used in the websockets.connect call
connect_args: Dict[str, Any] = {
"ssl": ssl,
"extra_headers": self.headers,
"subprotocols": [GRAPHQLWS_SUBPROTOCOL],
}

# Adding custom parameters passed from init
connect_args.update(self.connect_args)

# Connection to the specified url
# Generate a TimeoutError if taking more than connect_timeout seconds
self.websocket = await asyncio.wait_for(
websockets.connect(
self.url,
ssl=self.ssl if self.ssl else None,
extra_headers=self.headers,
subprotocols=[GRAPHQLWS_SUBPROTOCOL],
),
self.connect_timeout,
websockets.connect(self.url, **connect_args,), self.connect_timeout,
)

self.next_query_id = 1
Expand Down
73 changes: 65 additions & 8 deletions tests_py36/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import json
import logging
import os
import pathlib
import ssl
import types

import pytest
Expand Down Expand Up @@ -79,12 +81,35 @@ class TestServer:
Will allow us to test our client by simulating different correct and incorrect server responses
"""

def __init__(self, with_ssl: bool = False):
self.with_ssl = with_ssl

async def start(self, handler):

print("Starting server")

extra_serve_args = {}

if self.with_ssl:
# This is a copy of certificate from websockets tests folder
#
# Generate TLS certificate with:
# $ openssl req -x509 -config test_localhost.cnf -days 15340 -newkey rsa:2048 \
# -out test_localhost.crt -keyout test_localhost.key
# $ cat test_localhost.key test_localhost.crt > test_localhost.pem
# $ rm test_localhost.key test_localhost.crt
self.testcert = bytes(
pathlib.Path(__file__).with_name("test_localhost.pem")
)
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(self.testcert)

extra_serve_args["ssl"] = ssl_context

# Start a server with a random open port
self.start_server = websockets.server.serve(handler, "localhost", 0)
self.start_server = websockets.server.serve(
handler, "localhost", 0, **extra_serve_args
)

# Wait that the server is started
self.server = await self.start_server
Expand Down Expand Up @@ -137,13 +162,9 @@ async def wait_connection_terminate(ws):
assert json_result["type"] == "connection_terminate"


@pytest.fixture
async def server(request):
"""server is a fixture used to start a dummy server to test the client behaviour.

It can take as argument either a handler function for the websocket server for complete control
OR an array of answers to be sent by the default server handler
"""
def get_server_handler(request):
""" Get the server handler provided from test or use the default
server handler if the test provides only an array of answers"""

if isinstance(request.param, types.FunctionType):
server_handler = request.param
Expand Down Expand Up @@ -179,6 +200,42 @@ async def default_server_handler(ws, path):

server_handler = default_server_handler

return server_handler


@pytest.fixture
async def ws_ssl_server(request):
"""websockets server fixture using ssl

It can take as argument either a handler function for the websocket server for complete control
OR an array of answers to be sent by the default server handler
"""

server_handler = get_server_handler(request)

try:
test_server = TestServer(with_ssl=True)

# Starting the server with the fixture param as the handler function
await test_server.start(server_handler)

yield test_server
except Exception as e:
print("Exception received in server fixture: " + str(e))
finally:
await test_server.stop()


@pytest.fixture
async def server(request):
"""server is a fixture used to start a dummy server to test the client behaviour.

It can take as argument either a handler function for the websocket server for complete control
OR an array of answers to be sent by the default server handler
"""

server_handler = get_server_handler(request)

try:
test_server = TestServer()

Expand Down
Loading