diff --git a/meta-python/conf/include/ptest-packagelists-meta-python.inc b/meta-python/conf/include/ptest-packagelists-meta-python.inc index 92183897c7..6e4af06aaa 100644 --- a/meta-python/conf/include/ptest-packagelists-meta-python.inc +++ b/meta-python/conf/include/ptest-packagelists-meta-python.inc @@ -103,6 +103,7 @@ PTESTS_FAST_META_PYTHON = "\ python3-serpent \ python3-service-identity \ python3-setproctitle \ + python3-simple-websocket \ python3-simpleeval \ python3-six \ python3-smpplib \ diff --git a/meta-python/recipes-devtools/python/python3-simple-websocket/0001-Async-test-improvements-for-Python-3.14-48.patch b/meta-python/recipes-devtools/python/python3-simple-websocket/0001-Async-test-improvements-for-Python-3.14-48.patch new file mode 100644 index 0000000000..2be661c29e --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-simple-websocket/0001-Async-test-improvements-for-Python-3.14-48.patch @@ -0,0 +1,422 @@ +From 03832db418f11a818051eefcd774495798b203af Mon Sep 17 00:00:00 2001 +From: Miguel Grinberg +Date: Sun, 12 Apr 2026 10:38:44 +0100 +Subject: [PATCH] Async test improvements for Python 3.14 (#48) + +Python 3.14 no longer creates an event loop in asyncio.get_event_loop(), +so the make_sync() test helper fails with "There is no current event +loop in thread 'MainThread'". Run the async tests with pytest-asyncio. + +Upstream-Status: Backport [https://github.com/miguelgrinberg/simple-websocket/commit/2005dbb96b5b42a4ebf309d10ac1d33daaddb99f] +Signed-off-by: Khem Raj +--- + tests/helpers.py | 25 ------------------------- + tests/test_aioclient.py | 39 ++++++++++++++------------------------- + tests/test_aioserver.py | 39 +++++++++++++-------------------------- + tests/test_server.py | 2 +- + 4 files changed, 28 insertions(+), 77 deletions(-) + delete mode 100644 tests/helpers.py + +diff --git a/tests/helpers.py b/tests/helpers.py +deleted file mode 100644 +index a7c1a46..0000000 +--- a/tests/helpers.py ++++ /dev/null +@@ -1,25 +0,0 @@ +-import asyncio +-from unittest import mock +- +- +-def AsyncMock(*args, **kwargs): +- """Return a mock asynchronous function.""" +- m = mock.MagicMock(*args, **kwargs) +- +- async def mock_coro(*args, **kwargs): +- return m(*args, **kwargs) +- +- mock_coro.mock = m +- return mock_coro +- +- +-def _run(coro): +- """Run the given coroutine.""" +- return asyncio.get_event_loop().run_until_complete(coro) +- +- +-def make_sync(coro): +- """Wrap a coroutine so that it can be executed by pytest.""" +- def wrapper(*args, **kwargs): +- return _run(coro(*args, **kwargs)) +- return wrapper +diff --git a/tests/test_aioclient.py b/tests/test_aioclient.py +index 01a772a..5ec12ff 100644 +--- a/tests/test_aioclient.py ++++ b/tests/test_aioclient.py +@@ -1,15 +1,14 @@ + import asyncio +-import unittest + from unittest import mock + import pytest # noqa: F401 + + from wsproto.events import AcceptConnection, CloseConnection, TextMessage, \ + BytesMessage, Ping + import simple_websocket +-from .helpers import make_sync, AsyncMock + + +-class AioSimpleWebSocketClientTestCase(unittest.TestCase): ++@pytest.mark.asyncio ++class TestAioSimpleWebSocketClient: + async def get_client(self, mock_wsconn, url, events=[], subprotocols=None, + headers=None): + mock_wsconn().events.side_effect = \ +@@ -19,11 +18,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + return await simple_websocket.AioClient.connect( + url, subprotocols=subprotocols, headers=headers) + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_make_client(self, mock_wsconn, mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1') +@@ -40,12 +38,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + assert client.port == 80 + assert client.path == '/ws?a=1' + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_make_client_subprotocol(self, mock_wsconn, + mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1', +@@ -55,12 +52,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + b"Request(host='example.com', target='/ws?a=1', extensions=[], " + b"extra_headers=[], subprotocols=['foo'])") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_make_client_subprotocols(self, mock_wsconn, + mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1', +@@ -70,12 +66,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + b"Request(host='example.com', target='/ws?a=1', extensions=[], " + b"extra_headers=[], subprotocols=['foo', 'bar'])") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_make_client_headers(self, mock_wsconn, + mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1', +@@ -84,12 +79,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + b"Request(host='example.com', target='/ws?a=1', extensions=[], " + b"extra_headers=[('Foo', 'Bar')], subprotocols=[])") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_make_client_headers2(self, mock_wsconn, + mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client( +@@ -100,11 +94,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + b"extra_headers=[('Foo', 'Bar'), ('Foo', 'Baz')], " + b"subprotocols=[])") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_send(self, mock_wsconn, mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client(mock_wsconn, 'ws://example.com/ws') +@@ -123,11 +116,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + b"Message(data=b'hello', frame_finished=True, " + b"message_finished=True)") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive(self, mock_wsconn, mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client( +@@ -142,12 +134,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + assert await client.receive() == b'hello' + assert await client.receive(timeout=0) is None + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_after_close(self, mock_wsconn, + mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client( +@@ -160,11 +151,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + with pytest.raises(simple_websocket.ConnectionClosed): + await client.receive() + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_ping(self, mock_wsconn, mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) +@@ -176,11 +166,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + await asyncio.sleep(0.01) + wsock.write.assert_any_call(b"Pong(payload=b'hello')") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_empty(self, mock_wsconn, mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(side_effect=[b'x', b'x', b''])) ++ rsock = mock.MagicMock(read=mock.AsyncMock( ++ side_effect=[b'x', b'x', b''])) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client( +@@ -193,11 +183,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): + assert await client.receive() == 'hello' + assert await client.receive(timeout=0) is None + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_close(self, mock_wsconn, mock_open_connection): +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + client = await self.get_client( +diff --git a/tests/test_aioserver.py b/tests/test_aioserver.py +index a274019..071e035 100644 +--- a/tests/test_aioserver.py ++++ b/tests/test_aioserver.py +@@ -1,15 +1,14 @@ + import asyncio +-import unittest + from unittest import mock + import pytest # noqa: F401 + + from wsproto.events import Request, CloseConnection, TextMessage, \ + BytesMessage, Ping, Pong + import simple_websocket +-from .helpers import make_sync, AsyncMock + + +-class AioSimpleWebSocketServerTestCase(unittest.TestCase): ++@pytest.mark.asyncio ++class TestAioSimpleWebSocketServer: + async def get_server(self, mock_wsconn, request, events=[], + client_subprotocols=None, server_subprotocols=None, + **kwargs): +@@ -29,12 +28,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + return await simple_websocket.AioServer.accept( + aiohttp=request, subprotocols=server_subprotocols, **kwargs) + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_aiohttp(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request) +@@ -53,19 +51,17 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + b'Sec-Websocket-Version: 13\r\n\r\n') + assert server.is_server + +- @make_sync + async def test_invalid_request(self): + with pytest.raises(ValueError): + await simple_websocket.AioServer.accept(aiohttp='foo', asgi='bar') + with pytest.raises(ValueError): + await simple_websocket.AioServer.accept(asgi='bar', sock='baz') + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_send(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request) +@@ -84,12 +80,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + b"Message(data=b'hello', frame_finished=True, " + b"message_finished=True)") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -103,13 +98,12 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + assert await server.receive() == b'hello' + assert await server.receive(timeout=0) is None + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_after_close(self, mock_wsconn, + mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -121,13 +115,12 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + with pytest.raises(simple_websocket.ConnectionClosed): + await server.receive() + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_split_messages(self, mock_wsconn, + mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -151,12 +144,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + assert await server.receive() == b'hello' + assert await server.receive(timeout=0) is None + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_ping(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -166,12 +158,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + await asyncio.sleep(0.01) + wsock.write.assert_any_call(b"Pong(payload=b'hello')") + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_empty(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -183,12 +174,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + assert await server.receive() == 'hello' + assert await server.receive(timeout=0) is None + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_receive_large(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -201,12 +191,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + assert await server.receive() == 'hello' + assert await server.receive(timeout=0) is None + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_close(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request) +@@ -222,7 +211,6 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + b'CloseConnection(code=, ' + b'reason=None)') + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + @mock.patch('simple_websocket.aiows.time') +@@ -230,7 +218,7 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + async def test_ping_pong(self, mock_wait_for, mock_time, mock_wsconn, + mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock()) ++ rsock = mock.MagicMock(read=mock.AsyncMock()) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + server = await self.get_server(mock_wsconn, mock_request, events=[ +@@ -246,12 +234,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): + assert wsock.write.call_args_list[2][0][0].startswith(b'Ping') + assert wsock.write.call_args_list[3][0][0].startswith(b'Close') + +- @make_sync + @mock.patch('simple_websocket.aiows.asyncio.open_connection') + @mock.patch('simple_websocket.aiows.WSConnection') + async def test_subprotocols(self, mock_wsconn, mock_open_connection): + mock_request = mock.MagicMock(headers={}) +- rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) ++ rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) + wsock = mock.MagicMock() + mock_open_connection.return_value = (rsock, wsock) + +diff --git a/tests/test_server.py b/tests/test_server.py +index 7be5d15..ca5fe99 100644 +--- a/tests/test_server.py ++++ b/tests/test_server.py +@@ -92,7 +92,7 @@ class SimpleWebSocketServerTestCase(unittest.TestCase): + b"message_finished=True)") + server.connected = True + server.send(b'hello') +- mock_socket.send.assert_called_with( ++ mock_socket.send.assert_any_call( + b"Message(data=b'hello', frame_finished=True, " + b"message_finished=True)") + diff --git a/meta-python/recipes-devtools/python/python3-simple-websocket_1.1.0.bb b/meta-python/recipes-devtools/python/python3-simple-websocket_1.1.0.bb new file mode 100644 index 0000000000..35c568cf7c --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-simple-websocket_1.1.0.bb @@ -0,0 +1,19 @@ +SUMMARY = "Simple WebSocket server and client for Python" +HOMEPAGE = "https://github.com/miguelgrinberg/simple-websocket" +LICENSE = "MIT" +LIC_FILES_CHKSUM = "file://LICENSE;md5=9d272c9fe2437531b5bbecf4fcc82e24" + +inherit pypi python_setuptools_build_meta ptest-python-pytest + +SRC_URI += "file://0001-Async-test-improvements-for-Python-3.14-48.patch" +SRC_URI[sha256sum] = "7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4" + +RDEPENDS:${PN} += " \ + python3-asyncio \ + python3-io \ + python3-wsproto \ +" + +RDEPENDS:${PN}-ptest += " \ + python3-pytest-asyncio \ +"