Skip to content

Commit cc6562e

Browse files
Copilotlawtancool
andcommitted
Use existing self.session as http_session instead of creating a new one
Co-authored-by: lawtancool <26829131+lawtancool@users.noreply.github.com>
1 parent 6cc7efc commit cc6562e

2 files changed

Lines changed: 17 additions & 55 deletions

File tree

pyControl4/websocket.py

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import aiohttp
66
import asyncio
7-
import ssl
87
import socketio_v4 as socketio
98
import logging
109

@@ -96,39 +95,35 @@ def __init__(
9695
session_no_verify_ssl: aiohttp.ClientSession = None,
9796
connect_callback=None,
9897
disconnect_callback=None,
99-
ssl_context: ssl.SSLContext = None,
10098
):
10199
"""Creates a Control4 Websocket object.
102100
103101
Parameters:
104102
`ip` - The IP address of the Control4 Director/Controller.
105103
106-
`session` - (Optional) Allows the use of an
104+
`session_no_verify_ssl` - (Optional) Allows the use of an
107105
`aiohttp.ClientSession` object
108106
for all network requests. This
109107
session will not be closed by the library.
110108
If not provided, the library will open and
111109
close its own `ClientSession`s as needed.
110+
This session is also passed to the underlying
111+
socketio/engineio client to avoid blocking
112+
`ssl.create_default_context()` calls inside
113+
the event loop.
112114
113115
`connect_callback` - (Optional) A callback to be called when the
114116
Websocket connection is opened or reconnected after a network
115117
error.
116118
117119
`disconnect_callback` - (Optional) A callback to be called when
118120
the Websocket connection is lost due to a network error.
119-
120-
`ssl_context` - (Optional) A pre-created `ssl.SSLContext` object
121-
to use for the WebSocket connection. Use this to avoid
122-
blocking calls to `ssl.create_default_context()` inside
123-
the event loop. If not provided, the library will create
124-
its own SSL context internally.
125121
"""
126122
self.base_url = "https://{}".format(ip)
127123
self.wss_url = "wss://{}".format(ip)
128124
self.session = session_no_verify_ssl
129125
self.connect_callback = connect_callback
130126
self.disconnect_callback = disconnect_callback
131-
self.ssl_context = ssl_context
132127

133128
self._item_callbacks = dict()
134129
# Initialize self._sio to None
@@ -202,13 +197,9 @@ async def sio_connect(self, director_bearer_token):
202197
# Disconnect previous sio object
203198
await self.sio_disconnect()
204199

205-
if self.ssl_context is not None:
206-
connector = aiohttp.TCPConnector(ssl=self.ssl_context)
207-
http_session = aiohttp.ClientSession(connector=connector)
208-
# http_session ownership is transferred to the engineio client,
209-
# which closes it in its _reset() method during disconnect.
200+
if self.session is not None:
210201
self._sio = socketio.AsyncClient(
211-
ssl_verify=False, http_session=http_session
202+
ssl_verify=False, http_session=self.session
212203
)
213204
else:
214205
self._sio = socketio.AsyncClient(ssl_verify=False)

tests/test_websocket_ssl.py

Lines changed: 10 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
11
"""Tests for SSL context passthrough in C4Websocket."""
22

3-
import ssl
4-
from unittest.mock import AsyncMock, MagicMock, patch, call
3+
from unittest.mock import AsyncMock, MagicMock, patch
54

6-
import aiohttp
75
import socketio_v4
86
import pytest
97

108
from pyControl4.websocket import C4Websocket
119

1210

13-
def test_default_no_ssl_context():
14-
"""Test that C4Websocket defaults to no ssl_context (backward compat)."""
15-
ws = C4Websocket("192.168.1.1")
16-
assert ws.ssl_context is None
17-
18-
19-
def test_ssl_context_stored():
20-
"""Test that a provided ssl_context is stored."""
21-
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
22-
ctx.check_hostname = False
23-
ctx.verify_mode = ssl.CERT_NONE
24-
ws = C4Websocket("192.168.1.1", ssl_context=ctx)
25-
assert ws.ssl_context is ctx
26-
27-
2811
@pytest.mark.asyncio
29-
async def test_sio_connect_without_ssl_context():
30-
"""Test that sio_connect uses ssl_verify=False when no ssl_context."""
12+
async def test_sio_connect_without_session():
13+
"""Test that sio_connect uses ssl_verify=False without http_session when
14+
no session is provided."""
3115
ws = C4Websocket("192.168.1.1")
3216
with patch.object(
3317
socketio_v4.AsyncClient, "__init__", return_value=None
@@ -41,29 +25,16 @@ async def test_sio_connect_without_ssl_context():
4125

4226

4327
@pytest.mark.asyncio
44-
async def test_sio_connect_with_ssl_context():
45-
"""Test that sio_connect uses ssl_verify=False and http_session when
46-
ssl_context is provided."""
47-
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
48-
ctx.check_hostname = False
49-
ctx.verify_mode = ssl.CERT_NONE
50-
ws = C4Websocket("192.168.1.1", ssl_context=ctx)
28+
async def test_sio_connect_with_session():
29+
"""Test that sio_connect passes the caller's session as http_session."""
30+
mock_session = MagicMock()
31+
ws = C4Websocket("192.168.1.1", session_no_verify_ssl=mock_session)
5132
with patch.object(
5233
socketio_v4.AsyncClient, "__init__", return_value=None
5334
) as mock_init, patch.object(
5435
socketio_v4.AsyncClient, "register_namespace"
5536
), patch.object(
5637
socketio_v4.AsyncClient, "connect", new_callable=AsyncMock
57-
), patch.object(
58-
aiohttp, "TCPConnector"
59-
) as mock_connector_cls, patch.object(
60-
aiohttp, "ClientSession"
61-
) as mock_session_cls:
62-
mock_conn = MagicMock()
63-
mock_connector_cls.return_value = mock_conn
64-
mock_sess = MagicMock()
65-
mock_session_cls.return_value = mock_sess
38+
):
6639
await ws.sio_connect("test-token")
67-
mock_connector_cls.assert_called_once_with(ssl=ctx)
68-
mock_session_cls.assert_called_once_with(connector=mock_conn)
69-
mock_init.assert_called_once_with(ssl_verify=False, http_session=mock_sess)
40+
mock_init.assert_called_once_with(ssl_verify=False, http_session=mock_session)

0 commit comments

Comments
 (0)