Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-socketio for 
openSUSE:Factory checked in at 2025-10-31 16:28:39
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-socketio (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-socketio.new.1980 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-socketio"

Fri Oct 31 16:28:39 2025 rev:14 rq:1314808 version:5.14.3

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-socketio/python-python-socketio.changes
    2025-10-08 18:18:44.977053691 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-python-socketio.new.1980/python-python-socketio.changes
  2025-10-31 16:29:46.022241642 +0100
@@ -1,0 +2,14 @@
+Fri Oct 31 11:40:10 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 5.14.3:
+  * Support Python's native `ConnectionRefusedError` exception to
+    reject a connection #1515 (commit)
+  * Push binary data to the aiopika client manager #1514 (commit)
+  * Restore binary message support in message queue setups #1509
+    (commit)
+  * Fix formatting of client connection error #1507 (commit)
+  * Add 3.14 and pypy-3.11 CI tasks (commit)
+  * Improve documentation of the `BaseManager.get_participants()`
+    method (commit)
+
+-------------------------------------------------------------------

Old:
----
  python_socketio-5.14.1.tar.gz

New:
----
  python_socketio-5.14.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-python-socketio.spec ++++++
--- /var/tmp/diff_new_pack.TMYpdo/_old  2025-10-31 16:29:46.590265777 +0100
+++ /var/tmp/diff_new_pack.TMYpdo/_new  2025-10-31 16:29:46.594265947 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-python-socketio
-Version:        5.14.1
+Version:        5.14.3
 Release:        0
 Summary:        SocketIO server
 License:        MIT

++++++ python_socketio-5.14.1.tar.gz -> python_socketio-5.14.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/.github/workflows/tests.yml 
new/python-socketio-5.14.3/.github/workflows/tests.yml
--- old/python-socketio-5.14.1/.github/workflows/tests.yml      2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/.github/workflows/tests.yml      2025-10-29 
10:42:20.000000000 +0100
@@ -16,11 +16,7 @@
     strategy:
       matrix:
         os: [windows-latest, macos-latest, ubuntu-latest]
-        python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10']
-        exclude:
-          # pypy3 currently fails to run on Windows
-          - os: windows-latest
-            python: pypy-3.10
+        python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 
'pypy-3.11']
       fail-fast: false
     runs-on: ${{ matrix.os }}
     steps:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/CHANGES.md 
new/python-socketio-5.14.3/CHANGES.md
--- old/python-socketio-5.14.1/CHANGES.md       2025-10-02 20:41:26.000000000 
+0200
+++ new/python-socketio-5.14.3/CHANGES.md       2025-10-29 10:42:20.000000000 
+0100
@@ -1,5 +1,17 @@
 # python-socketio change log
 
+**Release 5.14.3** - 2025-10-29
+
+- Support Python's native `ConnectionRefusedError` exception to reject a 
connection 
[#1515](https://github.com/miguelgrinberg/python-socketio/issues/1515) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3))
+- Push binary data to the aiopika client manager 
[#1514](https://github.com/miguelgrinberg/python-socketio/issues/1514) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac))
+
+**Release 5.14.2** - 2025-10-15
+
+- Restore binary message support in message queue setups 
[#1509](https://github.com/miguelgrinberg/python-socketio/issues/1509) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3))
+- Fix formatting of client connection error 
[#1507](https://github.com/miguelgrinberg/python-socketio/issues/1507) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd))
+- Add 3.14 and pypy-3.11 CI tasks 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403))
+- Improve documentation of the `BaseManager.get_participants()` method 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/33722a0d96036f005188b07b8b46a5ef091fe65f))
+
 **Release 5.14.1** - 2025-10-02
 
 - Restore support for `rediss://` URLs, and add support for `valkeys://` as 
well 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/6e2d0de12bb4e4a99fdfc30bed0706ded620822c))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/docs/server.rst 
new/python-socketio-5.14.3/docs/server.rst
--- old/python-socketio-5.14.1/docs/server.rst  2025-10-02 20:41:26.000000000 
+0200
+++ new/python-socketio-5.14.3/docs/server.rst  2025-10-29 10:42:20.000000000 
+0100
@@ -246,6 +246,8 @@
 and all of its arguments will be sent to the client with the rejection
 message::
 
+    from socketio.exceptions import ConnectionRefusedError
+
     @sio.event
     def connect(sid, environ, auth):
         raise ConnectionRefusedError('authentication failed')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/pyproject.toml 
new/python-socketio-5.14.3/pyproject.toml
--- old/python-socketio-5.14.1/pyproject.toml   2025-10-02 20:41:26.000000000 
+0200
+++ new/python-socketio-5.14.3/pyproject.toml   2025-10-29 10:42:20.000000000 
+0100
@@ -1,6 +1,6 @@
 [project]
 name = "python-socketio"
-version = "5.14.1"
+version = "5.14.3"
 license = {text = "MIT"}
 authors = [
     { name = "Miguel Grinberg", email = "[email protected]" },
@@ -34,6 +34,9 @@
 asyncio_client = [
     "aiohttp >= 3.4",
 ]
+dev = [
+    "tox",
+]
 docs = [
     "sphinx",
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.1/src/socketio/async_aiopika_manager.py 
new/python-socketio-5.14.3/src/socketio/async_aiopika_manager.py
--- old/python-socketio-5.14.1/src/socketio/async_aiopika_manager.py    
2025-10-02 20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/async_aiopika_manager.py    
2025-10-29 10:42:20.000000000 +0100
@@ -82,7 +82,7 @@
             try:
                 await self.publisher_exchange.publish(
                     aio_pika.Message(
-                        body=json.dumps(data),
+                        body=json.dumps(data).encode(),
                         delivery_mode=aio_pika.DeliveryMode.PERSISTENT
                     ), routing_key='*',
                 )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/src/socketio/async_client.py 
new/python-socketio-5.14.3/src/socketio/async_client.py
--- old/python-socketio-5.14.1/src/socketio/async_client.py     2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/async_client.py     2025-10-29 
10:42:20.000000000 +0100
@@ -175,8 +175,8 @@
             if set(self.namespaces) != set(self.connection_namespaces):
                 await self.disconnect()
                 raise exceptions.ConnectionError(
-                    'One or more namespaces failed to connect'
-                    ', '.join(self.failed_namespaces))
+                    'One or more namespaces failed to connect: '
+                    + ', '.join(self.failed_namespaces))
 
         self.connected = True
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.1/src/socketio/async_pubsub_manager.py 
new/python-socketio-5.14.3/src/socketio/async_pubsub_manager.py
--- old/python-socketio-5.14.1/src/socketio/async_pubsub_manager.py     
2025-10-02 20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/async_pubsub_manager.py     
2025-10-29 10:42:20.000000000 +0100
@@ -1,10 +1,12 @@
 import asyncio
+import base64
 from functools import partial
 import uuid
 
 from engineio import json
 
 from .async_manager import AsyncManager
+from .packet import Packet
 
 
 class AsyncPubSubManager(AsyncManager):
@@ -64,8 +66,12 @@
             callback = (room, namespace, id)
         else:
             callback = None
+        binary = Packet.data_is_binary(data)
+        if binary:
+            data, attachments = Packet.deconstruct_binary(data)
+            data = [data, *[base64.b64encode(a).decode() for a in attachments]]
         message = {'method': 'emit', 'event': event, 'data': data,
-                   'namespace': namespace, 'room': room,
+                   'binary': binary, 'namespace': namespace, 'room': room,
                    'skip_sid': skip_sid, 'callback': callback,
                    'host_id': self.host_id}
         await self._handle_emit(message)  # handle in this host
@@ -145,7 +151,11 @@
                                *remote_callback)
         else:
             callback = None
-        await super().emit(message['event'], message['data'],
+        data = message['data']
+        if message.get('binary'):
+            attachments = [base64.b64decode(a) for a in data[1:]]
+            data = Packet.reconstruct_binary(data[0], attachments)
+        await super().emit(message['event'], data,
                            namespace=message.get('namespace'),
                            room=message.get('room'),
                            skip_sid=message.get('skip_sid'),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/src/socketio/async_server.py 
new/python-socketio-5.14.3/src/socketio/async_server.py
--- old/python-socketio-5.14.1/src/socketio/async_server.py     2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/async_server.py     2025-10-29 
10:42:20.000000000 +0100
@@ -561,6 +561,9 @@
         except exceptions.ConnectionRefusedError as exc:
             fail_reason = exc.error_args
             success = False
+        except ConnectionRefusedError:
+            fail_reason = {"message": "Connection refused by server"}
+            success = False
 
         if success is False:
             if self.always_connect:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/src/socketio/base_manager.py 
new/python-socketio-5.14.3/src/socketio/base_manager.py
--- old/python-socketio-5.14.1/src/socketio/base_manager.py     2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/base_manager.py     2025-10-29 
10:42:20.000000000 +0100
@@ -29,7 +29,13 @@
         return self.rooms.keys()
 
     def get_participants(self, namespace, room):
-        """Return an iterable with the active participants in a room."""
+        """Return an iterable with the active participants in a room.
+
+        Note that in a multi-server scenario this method only returns the
+        participants connect to the server in which the method is called. There
+        is currently no functionality to assemble a complete list of users
+        across multiple servers.
+        """
         ns = self.rooms.get(namespace, {})
         if hasattr(room, '__len__') and not isinstance(room, str):
             participants = ns[room[0]]._fwdm.copy() if room[0] in ns else {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/src/socketio/client.py 
new/python-socketio-5.14.3/src/socketio/client.py
--- old/python-socketio-5.14.1/src/socketio/client.py   2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/client.py   2025-10-29 
10:42:20.000000000 +0100
@@ -169,7 +169,7 @@
                 self.disconnect()
                 raise exceptions.ConnectionError(
                     'One or more namespaces failed to connect: '
-                    ', '.join(self.failed_namespaces))
+                    + ', '.join(self.failed_namespaces))
 
         self.connected = True
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/src/socketio/packet.py 
new/python-socketio-5.14.3/src/socketio/packet.py
--- old/python-socketio-5.14.1/src/socketio/packet.py   2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/packet.py   2025-10-29 
10:42:20.000000000 +0100
@@ -29,7 +29,7 @@
         self.namespace = namespace
         self.id = id
         if self.uses_binary_events and \
-                (binary or (binary is None and self._data_is_binary(
+                (binary or (binary is None and self.data_is_binary(
                     self.data))):
             if self.packet_type == EVENT:
                 self.packet_type = BINARY_EVENT
@@ -51,7 +51,7 @@
         """
         encoded_packet = str(self.packet_type)
         if self.packet_type == BINARY_EVENT or self.packet_type == BINARY_ACK:
-            data, attachments = self._deconstruct_binary(self.data)
+            data, attachments = self.deconstruct_binary(self.data)
             encoded_packet += str(len(attachments)) + '-'
         else:
             data = self.data
@@ -119,61 +119,65 @@
             raise ValueError('Unexpected binary attachment')
         self.attachments.append(attachment)
         if self.attachment_count == len(self.attachments):
-            self.reconstruct_binary(self.attachments)
+            self.data = self.reconstruct_binary(self.data, self.attachments)
             return True
         return False
 
-    def reconstruct_binary(self, attachments):
+    @classmethod
+    def reconstruct_binary(cls, data, attachments):
         """Reconstruct a decoded packet using the given list of binary
         attachments.
         """
-        self.data = self._reconstruct_binary_internal(self.data,
-                                                      self.attachments)
+        return cls._reconstruct_binary_internal(data, attachments)
 
-    def _reconstruct_binary_internal(self, data, attachments):
+    @classmethod
+    def _reconstruct_binary_internal(cls, data, attachments):
         if isinstance(data, list):
-            return [self._reconstruct_binary_internal(item, attachments)
+            return [cls._reconstruct_binary_internal(item, attachments)
                     for item in data]
         elif isinstance(data, dict):
             if data.get('_placeholder') and 'num' in data:
                 return attachments[data['num']]
             else:
-                return {key: self._reconstruct_binary_internal(value,
-                                                               attachments)
+                return {key: cls._reconstruct_binary_internal(value,
+                                                              attachments)
                         for key, value in data.items()}
         else:
             return data
 
-    def _deconstruct_binary(self, data):
+    @classmethod
+    def deconstruct_binary(cls, data):
         """Extract binary components in the packet."""
         attachments = []
-        data = self._deconstruct_binary_internal(data, attachments)
+        data = cls._deconstruct_binary_internal(data, attachments)
         return data, attachments
 
-    def _deconstruct_binary_internal(self, data, attachments):
+    @classmethod
+    def _deconstruct_binary_internal(cls, data, attachments):
         if isinstance(data, bytes):
             attachments.append(data)
             return {'_placeholder': True, 'num': len(attachments) - 1}
         elif isinstance(data, list):
-            return [self._deconstruct_binary_internal(item, attachments)
+            return [cls._deconstruct_binary_internal(item, attachments)
                     for item in data]
         elif isinstance(data, dict):
-            return {key: self._deconstruct_binary_internal(value, attachments)
+            return {key: cls._deconstruct_binary_internal(value, attachments)
                     for key, value in data.items()}
         else:
             return data
 
-    def _data_is_binary(self, data):
+    @classmethod
+    def data_is_binary(cls, data):
         """Check if the data contains binary components."""
         if isinstance(data, bytes):
             return True
         elif isinstance(data, list):
             return functools.reduce(
-                lambda a, b: a or b, [self._data_is_binary(item)
+                lambda a, b: a or b, [cls.data_is_binary(item)
                                       for item in data], False)
         elif isinstance(data, dict):
             return functools.reduce(
-                lambda a, b: a or b, [self._data_is_binary(item)
+                lambda a, b: a or b, [cls.data_is_binary(item)
                                       for item in data.values()],
                 False)
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.1/src/socketio/pubsub_manager.py 
new/python-socketio-5.14.3/src/socketio/pubsub_manager.py
--- old/python-socketio-5.14.1/src/socketio/pubsub_manager.py   2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/pubsub_manager.py   2025-10-29 
10:42:20.000000000 +0100
@@ -1,9 +1,11 @@
+import base64
 from functools import partial
 import uuid
 
 from engineio import json
 
 from .manager import Manager
+from .packet import Packet
 
 
 class PubSubManager(Manager):
@@ -61,8 +63,12 @@
             callback = (room, namespace, id)
         else:
             callback = None
+        binary = Packet.data_is_binary(data)
+        if binary:
+            data, attachments = Packet.deconstruct_binary(data)
+            data = [data, *[base64.b64encode(a).decode() for a in attachments]]
         message = {'method': 'emit', 'event': event, 'data': data,
-                   'namespace': namespace, 'room': room,
+                   'binary': binary, 'namespace': namespace, 'room': room,
                    'skip_sid': skip_sid, 'callback': callback,
                    'host_id': self.host_id}
         self._handle_emit(message)  # handle in this host
@@ -141,7 +147,11 @@
                                *remote_callback)
         else:
             callback = None
-        super().emit(message['event'], message['data'],
+        data = message['data']
+        if message.get('binary'):
+            attachments = [base64.b64decode(a) for a in data[1:]]
+            data = Packet.reconstruct_binary(data[0], attachments)
+        super().emit(message['event'], data,
                      namespace=message.get('namespace'),
                      room=message.get('room'),
                      skip_sid=message.get('skip_sid'), callback=callback)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/src/socketio/server.py 
new/python-socketio-5.14.3/src/socketio/server.py
--- old/python-socketio-5.14.1/src/socketio/server.py   2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/src/socketio/server.py   2025-10-29 
10:42:20.000000000 +0100
@@ -543,6 +543,9 @@
         except exceptions.ConnectionRefusedError as exc:
             fail_reason = exc.error_args
             success = False
+        except ConnectionRefusedError:
+            fail_reason = {"message": "Connection refused by server"}
+            success = False
 
         if success is False:
             if self.always_connect:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/tests/async/test_client.py 
new/python-socketio-5.14.3/tests/async/test_client.py
--- old/python-socketio-5.14.1/tests/async/test_client.py       2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/async/test_client.py       2025-10-29 
10:42:20.000000000 +0100
@@ -203,6 +203,60 @@
         assert c.connected is True
         assert c.namespaces == {'/bar': '123', '/foo': '456'}
 
+    async def test_connect_wait_one_namespaces_error(self):
+        c = async_client.AsyncClient()
+        c.eio.connect = mock.AsyncMock()
+        c._connect_event = mock.MagicMock()
+
+        async def mock_connect():
+            if c.failed_namespaces == []:
+                c.failed_namespaces = ['/foo']
+                return True
+            return False
+
+        c._connect_event.wait = mock_connect
+        with pytest.raises(exceptions.ConnectionError,
+                           match='failed to connect: /foo'):
+            await c.connect(
+                'url',
+                namespaces=['/foo'],
+                wait=True,
+                wait_timeout=0.01,
+            )
+        assert c.connected is False
+        assert c.namespaces == {}
+        assert c.failed_namespaces == ['/foo']
+
+    async def test_connect_wait_three_namespaces_error(self):
+        c = async_client.AsyncClient()
+        c.eio.connect = mock.AsyncMock()
+        c._connect_event = mock.MagicMock()
+
+        async def mock_connect():
+            if c.namespaces == {}:
+                c.namespaces = {'/bar': '123'}
+                return True
+            elif c.namespaces == {'/bar': '123'} and c.failed_namespaces == []:
+                c.failed_namespaces = ['/baz']
+                return True
+            elif c.failed_namespaces == ['/baz']:
+                c.failed_namespaces = ['/baz', '/foo']
+                return True
+            return False
+
+        c._connect_event.wait = mock_connect
+        with pytest.raises(exceptions.ConnectionError,
+                           match='failed to connect: /baz, /foo'):
+            await c.connect(
+                'url',
+                namespaces=['/foo', '/bar', '/baz'],
+                wait=True,
+                wait_timeout=0.01,
+            )
+        assert c.connected is False
+        assert c.namespaces == {'/bar': '123'}
+        assert c.failed_namespaces == ['/baz', '/foo']
+
     async def test_connect_timeout(self):
         c = async_client.AsyncClient()
         c.eio.connect = mock.AsyncMock()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.1/tests/async/test_pubsub_manager.py 
new/python-socketio-5.14.3/tests/async/test_pubsub_manager.py
--- old/python-socketio-5.14.1/tests/async/test_pubsub_manager.py       
2025-10-02 20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/async/test_pubsub_manager.py       
2025-10-29 10:42:20.000000000 +0100
@@ -57,6 +57,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': None,
@@ -66,6 +67,36 @@
             }
         )
 
+    async def test_emit_binary(self):
+        await self.pm.emit('foo', b'bar')
+        self.pm._publish.assert_awaited_once_with(
+            {
+                'method': 'emit',
+                'event': 'foo',
+                'binary': True,
+                'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
+                'namespace': '/',
+                'room': None,
+                'skip_sid': None,
+                'callback': None,
+                'host_id': '123456',
+            }
+        )
+        await self.pm.emit('foo', {'foo': b'bar'})
+        self.pm._publish.assert_awaited_with(
+            {
+                'method': 'emit',
+                'event': 'foo',
+                'binary': True,
+                'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
+                'namespace': '/',
+                'room': None,
+                'skip_sid': None,
+                'callback': None,
+                'host_id': '123456',
+            }
+        )
+
     async def test_emit_with_to(self):
         sid = 'room-mate'
         await self.pm.emit('foo', 'bar', to=sid)
@@ -73,6 +104,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': sid,
@@ -88,6 +120,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/baz',
                 'room': None,
@@ -103,6 +136,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': 'baz',
@@ -118,6 +152,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': None,
@@ -136,6 +171,7 @@
                 {
                     'method': 'emit',
                     'event': 'foo',
+                    'binary': False,
                     'data': 'bar',
                     'namespace': '/',
                     'room': 'baz',
@@ -238,6 +274,37 @@
                 namespace=None,
                 room=None,
                 skip_sid=None,
+                callback=None,
+            )
+
+    async def test_handle_emit_binary(self):
+        with mock.patch.object(
+            async_manager.AsyncManager, 'emit'
+        ) as super_emit:
+            await self.pm._handle_emit({
+                'event': 'foo',
+                'binary': True,
+                'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
+            })
+            super_emit.assert_awaited_once_with(
+                'foo',
+                b'bar',
+                namespace=None,
+                room=None,
+                skip_sid=None,
+                callback=None,
+            )
+            await self.pm._handle_emit({
+                'event': 'foo',
+                'binary': True,
+                'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
+            })
+            super_emit.assert_awaited_with(
+                'foo',
+                {'foo': b'bar'},
+                namespace=None,
+                room=None,
+                skip_sid=None,
                 callback=None,
             )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/tests/async/test_server.py 
new/python-socketio-5.14.3/tests/async/test_server.py
--- old/python-socketio-5.14.1/tests/async/test_server.py       2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/async/test_server.py       2025-10-29 
10:42:20.000000000 +0100
@@ -482,6 +482,21 @@
             '123', '4{"message":"fail_reason"}')
         assert s.environ == {'123': 'environ'}
 
+    async def test_handle_connect_rejected_with_python_exception(self, eio):
+        eio.return_value.send = mock.AsyncMock()
+        s = async_server.AsyncServer()
+        handler = mock.MagicMock(
+            side_effect=ConnectionRefusedError()
+        )
+        s.on('connect', handler)
+        await s._handle_eio_connect('123', 'environ')
+        await s._handle_eio_message('123', '0')
+        assert not s.manager.is_connected('1', '/')
+        handler.assert_called_once_with('1', 'environ')
+        s.eio.send.assert_awaited_once_with(
+            '123', '4{"message":"Connection refused by server"}')
+        assert s.environ == {'123': 'environ'}
+
     async def test_handle_connect_rejected_with_empty_exception(self, eio):
         eio.return_value.send = mock.AsyncMock()
         s = async_server.AsyncServer()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/tests/common/test_client.py 
new/python-socketio-5.14.3/tests/common/test_client.py
--- old/python-socketio-5.14.1/tests/common/test_client.py      2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/common/test_client.py      2025-10-29 
10:42:20.000000000 +0100
@@ -350,6 +350,62 @@
         assert c.connected is True
         assert c.namespaces == {'/bar': '123', '/foo': '456'}
 
+    def test_connect_wait_one_namespaces_error(self):
+        c = client.Client()
+        c.eio.connect = mock.MagicMock()
+        c._connect_event = mock.MagicMock()
+
+        def mock_connect(timeout):
+            assert timeout == 0.01
+            if c.failed_namespaces == []:
+                c.failed_namespaces = ['/foo']
+                return True
+            return False
+
+        c._connect_event.wait = mock_connect
+        with pytest.raises(exceptions.ConnectionError,
+                           match='failed to connect: /foo'):
+            c.connect(
+                'url',
+                namespaces=['/foo'],
+                wait=True,
+                wait_timeout=0.01,
+            )
+        assert c.connected is False
+        assert c.namespaces == {}
+        assert c.failed_namespaces == ['/foo']
+
+    def test_connect_wait_three_namespaces_error(self):
+        c = client.Client()
+        c.eio.connect = mock.MagicMock()
+        c._connect_event = mock.MagicMock()
+
+        def mock_connect(timeout):
+            assert timeout == 0.01
+            if c.namespaces == {}:
+                c.namespaces = {'/bar': '123'}
+                return True
+            elif c.namespaces == {'/bar': '123'} and c.failed_namespaces == []:
+                c.failed_namespaces = ['/baz']
+                return True
+            elif c.failed_namespaces == ['/baz']:
+                c.failed_namespaces = ['/baz', '/foo']
+                return True
+            return False
+
+        c._connect_event.wait = mock_connect
+        with pytest.raises(exceptions.ConnectionError,
+                           match='failed to connect: /baz, /foo'):
+            c.connect(
+                'url',
+                namespaces=['/foo', '/bar', '/baz'],
+                wait=True,
+                wait_timeout=0.01,
+            )
+        assert c.connected is False
+        assert c.namespaces == {'/bar': '123'}
+        assert c.failed_namespaces == ['/baz', '/foo']
+
     def test_connect_timeout(self):
         c = client.Client()
         c.eio.connect = mock.MagicMock()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/tests/common/test_packet.py 
new/python-socketio-5.14.3/tests/common/test_packet.py
--- old/python-socketio-5.14.1/tests/common/test_packet.py      2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/common/test_packet.py      2025-10-29 
10:42:20.000000000 +0100
@@ -266,16 +266,24 @@
         assert pkt.data["a"] == "0123456789-"
         assert pkt.attachment_count == 0
 
+    def test_deconstruct_binary(self):
+        datas = [b'foo', [b'foo', b'bar'], ['foo', b'bar'], {'foo': b'bar'},
+                 {'foo': 'bar', 'baz': b'qux'}, {'foo': [b'bar']}]
+        for data in datas:
+            bdata, attachments = packet.Packet.deconstruct_binary(data)
+            rdata = packet.Packet.reconstruct_binary(bdata, attachments)
+            assert data == rdata
+
     def test_data_is_binary_list(self):
         pkt = packet.Packet()
-        assert not pkt._data_is_binary(['foo'])
-        assert not pkt._data_is_binary([])
-        assert pkt._data_is_binary([b'foo'])
-        assert pkt._data_is_binary(['foo', b'bar'])
+        assert not pkt.data_is_binary(['foo'])
+        assert not pkt.data_is_binary([])
+        assert pkt.data_is_binary([b'foo'])
+        assert pkt.data_is_binary(['foo', b'bar'])
 
     def test_data_is_binary_dict(self):
         pkt = packet.Packet()
-        assert not pkt._data_is_binary({'a': 'foo'})
-        assert not pkt._data_is_binary({})
-        assert pkt._data_is_binary({'a': b'foo'})
-        assert pkt._data_is_binary({'a': 'foo', 'b': b'bar'})
+        assert not pkt.data_is_binary({'a': 'foo'})
+        assert not pkt.data_is_binary({})
+        assert pkt.data_is_binary({'a': b'foo'})
+        assert pkt.data_is_binary({'a': 'foo', 'b': b'bar'})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.1/tests/common/test_pubsub_manager.py 
new/python-socketio-5.14.3/tests/common/test_pubsub_manager.py
--- old/python-socketio-5.14.1/tests/common/test_pubsub_manager.py      
2025-10-02 20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/common/test_pubsub_manager.py      
2025-10-29 10:42:20.000000000 +0100
@@ -69,6 +69,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': None,
@@ -78,6 +79,36 @@
             }
         )
 
+    def test_emit_binary(self):
+        self.pm.emit('foo', b'bar')
+        self.pm._publish.assert_called_once_with(
+            {
+                'method': 'emit',
+                'event': 'foo',
+                'binary': True,
+                'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
+                'namespace': '/',
+                'room': None,
+                'skip_sid': None,
+                'callback': None,
+                'host_id': '123456',
+            }
+        )
+        self.pm.emit('foo', {'foo': b'bar'})
+        self.pm._publish.assert_called_with(
+            {
+                'method': 'emit',
+                'event': 'foo',
+                'binary': True,
+                'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
+                'namespace': '/',
+                'room': None,
+                'skip_sid': None,
+                'callback': None,
+                'host_id': '123456',
+            }
+        )
+
     def test_emit_with_to(self):
         sid = "ferris"
         self.pm.emit('foo', 'bar', to=sid)
@@ -85,6 +116,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': sid,
@@ -100,6 +132,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/baz',
                 'room': None,
@@ -115,6 +148,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': 'baz',
@@ -130,6 +164,7 @@
             {
                 'method': 'emit',
                 'event': 'foo',
+                'binary': False,
                 'data': 'bar',
                 'namespace': '/',
                 'room': None,
@@ -148,6 +183,7 @@
                 {
                     'method': 'emit',
                     'event': 'foo',
+                    'binary': False,
                     'data': 'bar',
                     'namespace': '/',
                     'room': 'baz',
@@ -247,6 +283,35 @@
                 namespace=None,
                 room=None,
                 skip_sid=None,
+                callback=None,
+            )
+
+    def test_handle_emit_binary(self):
+        with mock.patch.object(manager.Manager, 'emit') as super_emit:
+            self.pm._handle_emit({
+                'event': 'foo',
+                'binary': True,
+                'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
+            })
+            super_emit.assert_called_once_with(
+                'foo',
+                b'bar',
+                namespace=None,
+                room=None,
+                skip_sid=None,
+                callback=None,
+            )
+            self.pm._handle_emit({
+                'event': 'foo',
+                'binary': True,
+                'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
+            })
+            super_emit.assert_called_with(
+                'foo',
+                {'foo': b'bar'},
+                namespace=None,
+                room=None,
+                skip_sid=None,
                 callback=None,
             )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/tests/common/test_server.py 
new/python-socketio-5.14.3/tests/common/test_server.py
--- old/python-socketio-5.14.1/tests/common/test_server.py      2025-10-02 
20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tests/common/test_server.py      2025-10-29 
10:42:20.000000000 +0100
@@ -462,6 +462,20 @@
         s.eio.send.assert_called_once_with('123', '4{"message":"fail_reason"}')
         assert s.environ == {'123': 'environ'}
 
+    def test_handle_connect_rejected_with_python_exception(self, eio):
+        s = server.Server()
+        handler = mock.MagicMock(
+            side_effect=ConnectionRefusedError()
+        )
+        s.on('connect', handler)
+        s._handle_eio_connect('123', 'environ')
+        s._handle_eio_message('123', '0')
+        assert not s.manager.is_connected('1', '/')
+        handler.assert_called_once_with('1', 'environ')
+        s.eio.send.assert_called_once_with(
+            '123', '4{"message":"Connection refused by server"}')
+        assert s.environ == {'123': 'environ'}
+
     def test_handle_connect_rejected_with_empty_exception(self, eio):
         s = server.Server()
         handler = mock.MagicMock(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.1/tox.ini 
new/python-socketio-5.14.3/tox.ini
--- old/python-socketio-5.14.1/tox.ini  2025-10-02 20:41:26.000000000 +0200
+++ new/python-socketio-5.14.3/tox.ini  2025-10-29 10:42:20.000000000 +0100
@@ -1,5 +1,5 @@
 [tox]
-envlist=flake8,py{38,39,310,311,312,313},docs
+envlist=flake8,py{38,39,310,311,312,313,314},docs
 skip_missing_interpreters=True
 
 [gh-actions]
@@ -10,6 +10,7 @@
     3.11: py311
     3.12: py312
     3.13: py313
+    3.14: py314
     pypy-3: pypy3
 
 [testenv]

Reply via email to