Skip to content

Commit b6d5e72

Browse files
authored
Allow to pass arguments to websockets.connect + some ssl tests + some cleaning (#83)
1 parent 6525401 commit b6d5e72

12 files changed

+277
-49
lines changed

CONTRIBUTING.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ We welcome all kinds of contributions:
1212

1313
## Getting started
1414

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

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

3333
```console
34-
python pip install -e[dev]
34+
python pip install -e.[dev]
3535
```
3636

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

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

5656
And you ready to start development!
5757

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

6060
## Running tests
6161

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

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

7171
You can also test on several python environments by using tox.
@@ -77,8 +77,8 @@ Install tox:
7777
pip install tox
7878
```
7979

80-
Run `tox` on your virtualenv (do not forget to activate it!)
81-
and that's it!
80+
Run `tox` on your virtualenv (do not forget to activate it!)
81+
and that's it!
8282

8383
### Running tox on Conda
8484

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

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

96-
Run `tox` and you will see all the environments being created
97-
and all passing tests. :rocket:
96+
Run `tox` and you will see all the environments being created
97+
and all passing tests. :rocket:

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ include tox.ini
1313
include scripts/gql-cli
1414

1515
recursive-include tests *.py *.yaml *.graphql
16-
recursive-include tests_py36 *.py
16+
recursive-include tests_py36 *.py *.cnf *.pem
1717

1818
prune gql-checker
1919

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
.PHONY: clean tests
2+
13
dev-setup:
24
python pip install -e ".[test]"
35

46
tests:
5-
pytest tests --cov=gql -vv
7+
pytest tests tests_py36 --cov=gql --cov-report=term-missing -vv
8+
9+
all_tests:
10+
pytest tests tests_py36 --cov=gql --cov-report=term-missing --run-online -vv
611

712
clean:
813
find . -name "*.pyc" -delete

README.md

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ from gql.transport.requests import RequestsHTTPTransport
6767

6868
sample_transport=RequestsHTTPTransport(
6969
url='https://countries.trevorblades.com/',
70-
use_json=True,
71-
headers={
72-
"Content-type": "application/json",
73-
},
7470
verify=False,
7571
retries=3,
7672
)
@@ -215,7 +211,6 @@ async def main():
215211

216212
sample_transport = WebsocketsTransport(
217213
url='wss://countries.trevorblades.com/graphql',
218-
ssl=True,
219214
headers={'Authorization': 'token'}
220215
)
221216

@@ -262,8 +257,7 @@ import ssl
262257

263258
sample_transport = WebsocketsTransport(
264259
url='wss://SERVER_URL:SERVER_PORT/graphql',
265-
headers={'Authorization': 'token'},
266-
ssl=True
260+
headers={'Authorization': 'token'}
267261
)
268262
```
269263

@@ -298,8 +292,7 @@ There are two ways to send authentication tokens with websockets depending on th
298292
```python
299293
sample_transport = WebsocketsTransport(
300294
url='wss://SERVER_URL:SERVER_PORT/graphql',
301-
headers={'Authorization': 'token'},
302-
ssl=True
295+
headers={'Authorization': 'token'}
303296
)
304297
```
305298

@@ -308,8 +301,7 @@ sample_transport = WebsocketsTransport(
308301
```python
309302
sample_transport = WebsocketsTransport(
310303
url='wss://SERVER_URL:SERVER_PORT/graphql',
311-
init_payload={'Authorization': 'token'},
312-
ssl=True
304+
init_payload={'Authorization': 'token'}
313305
)
314306
```
315307

gql/transport/aiohttp.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(
3535
auth: Optional[BasicAuth] = None,
3636
ssl: Union[SSLContext, bool, Fingerprint] = False,
3737
timeout: Optional[int] = None,
38-
**kwargs,
38+
client_session_args: Dict[str, Any] = {},
3939
) -> None:
4040
"""Initialize the transport with the given aiohttp parameters.
4141
@@ -44,15 +44,15 @@ def __init__(
4444
:param cookies: Dict of HTTP cookies.
4545
:param auth: BasicAuth object to enable Basic HTTP auth if needed
4646
:param ssl: ssl_context of the connection. Use ssl=False to disable encryption
47-
:param kwargs: Other parameters forwarded to aiohttp.ClientSession
47+
:param client_session_args: Dict of extra parameters passed to aiohttp.ClientSession
4848
"""
4949
self.url: str = url
5050
self.headers: Optional[LooseHeaders] = headers
5151
self.cookies: Optional[LooseCookies] = cookies
5252
self.auth: Optional[BasicAuth] = auth
5353
self.ssl: Union[SSLContext, bool, Fingerprint] = ssl
5454
self.timeout: Optional[int] = timeout
55-
self.kwargs = kwargs
55+
self.client_session_args = client_session_args
5656

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

@@ -78,7 +78,7 @@ async def connect(self) -> None:
7878
)
7979

8080
# Adding custom parameters passed from init
81-
client_session_args.update(self.kwargs)
81+
client_session_args.update(self.client_session_args)
8282

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

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

117-
# Pass kwargs to aiohttp post method
118-
post_args.update(kwargs)
117+
# Pass post_args to aiohttp post method
118+
post_args.update(extra_args)
119119

120120
if self.session is None:
121121
raise TransportClosed("Transport is not connected")

gql/transport/websockets.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def __init__(
9797
connect_timeout: int = 10,
9898
close_timeout: int = 10,
9999
ack_timeout: int = 10,
100+
connect_args: Dict[str, Any] = {},
100101
) -> None:
101102
"""Initialize the transport with the given request parameters.
102103
@@ -107,6 +108,7 @@ def __init__(
107108
:param connect_timeout: Timeout in seconds for the establishment of the websocket connection.
108109
:param close_timeout: Timeout in seconds for the close.
109110
:param ack_timeout: Timeout in seconds to wait for the connection_ack message from the server.
111+
:param connect_args: Other parameters forwarded to websockets.connect
110112
"""
111113
self.url: str = url
112114
self.ssl: Union[SSLContext, bool] = ssl
@@ -117,6 +119,8 @@ def __init__(
117119
self.close_timeout: int = close_timeout
118120
self.ack_timeout: int = ack_timeout
119121

122+
self.connect_args = connect_args
123+
120124
self.websocket: Optional[WebSocketClientProtocol] = None
121125
self.next_query_id: int = 1
122126
self.listeners: Dict[int, ListenerQueue] = {}
@@ -460,16 +464,27 @@ async def connect(self) -> None:
460464

461465
if self.websocket is None:
462466

467+
# If the ssl parameter is not provided, generate the ssl value depending on the url
468+
ssl: Optional[Union[SSLContext, bool]]
469+
if self.ssl:
470+
ssl = self.ssl
471+
else:
472+
ssl = True if self.url.startswith("wss") else None
473+
474+
# Set default arguments used in the websockets.connect call
475+
connect_args: Dict[str, Any] = {
476+
"ssl": ssl,
477+
"extra_headers": self.headers,
478+
"subprotocols": [GRAPHQLWS_SUBPROTOCOL],
479+
}
480+
481+
# Adding custom parameters passed from init
482+
connect_args.update(self.connect_args)
483+
463484
# Connection to the specified url
464485
# Generate a TimeoutError if taking more than connect_timeout seconds
465486
self.websocket = await asyncio.wait_for(
466-
websockets.connect(
467-
self.url,
468-
ssl=self.ssl if self.ssl else None,
469-
extra_headers=self.headers,
470-
subprotocols=[GRAPHQLWS_SUBPROTOCOL],
471-
),
472-
self.connect_timeout,
487+
websockets.connect(self.url, **connect_args,), self.connect_timeout,
473488
)
474489

475490
self.next_query_id = 1

tests_py36/conftest.py

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import json
33
import logging
44
import os
5+
import pathlib
6+
import ssl
57
import types
68

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

84+
def __init__(self, with_ssl: bool = False):
85+
self.with_ssl = with_ssl
86+
8287
async def start(self, handler):
8388

8489
print("Starting server")
8590

91+
extra_serve_args = {}
92+
93+
if self.with_ssl:
94+
# This is a copy of certificate from websockets tests folder
95+
#
96+
# Generate TLS certificate with:
97+
# $ openssl req -x509 -config test_localhost.cnf -days 15340 -newkey rsa:2048 \
98+
# -out test_localhost.crt -keyout test_localhost.key
99+
# $ cat test_localhost.key test_localhost.crt > test_localhost.pem
100+
# $ rm test_localhost.key test_localhost.crt
101+
self.testcert = bytes(
102+
pathlib.Path(__file__).with_name("test_localhost.pem")
103+
)
104+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
105+
ssl_context.load_cert_chain(self.testcert)
106+
107+
extra_serve_args["ssl"] = ssl_context
108+
86109
# Start a server with a random open port
87-
self.start_server = websockets.server.serve(handler, "localhost", 0)
110+
self.start_server = websockets.server.serve(
111+
handler, "localhost", 0, **extra_serve_args
112+
)
88113

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

139164

140-
@pytest.fixture
141-
async def server(request):
142-
"""server is a fixture used to start a dummy server to test the client behaviour.
143-
144-
It can take as argument either a handler function for the websocket server for complete control
145-
OR an array of answers to be sent by the default server handler
146-
"""
165+
def get_server_handler(request):
166+
""" Get the server handler provided from test or use the default
167+
server handler if the test provides only an array of answers"""
147168

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

180201
server_handler = default_server_handler
181202

203+
return server_handler
204+
205+
206+
@pytest.fixture
207+
async def ws_ssl_server(request):
208+
"""websockets server fixture using ssl
209+
210+
It can take as argument either a handler function for the websocket server for complete control
211+
OR an array of answers to be sent by the default server handler
212+
"""
213+
214+
server_handler = get_server_handler(request)
215+
216+
try:
217+
test_server = TestServer(with_ssl=True)
218+
219+
# Starting the server with the fixture param as the handler function
220+
await test_server.start(server_handler)
221+
222+
yield test_server
223+
except Exception as e:
224+
print("Exception received in server fixture: " + str(e))
225+
finally:
226+
await test_server.stop()
227+
228+
229+
@pytest.fixture
230+
async def server(request):
231+
"""server is a fixture used to start a dummy server to test the client behaviour.
232+
233+
It can take as argument either a handler function for the websocket server for complete control
234+
OR an array of answers to be sent by the default server handler
235+
"""
236+
237+
server_handler = get_server_handler(request)
238+
182239
try:
183240
test_server = TestServer()
184241

0 commit comments

Comments
 (0)