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-12-10 15:33:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-socketio (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-socketio.new.1939 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-socketio"

Wed Dec 10 15:33:47 2025 rev:15 rq:1321922 version:5.15.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-socketio/python-python-socketio.changes
    2025-10-31 16:29:46.022241642 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-python-socketio.new.1939/python-python-socketio.changes
  2025-12-10 15:34:30.768336427 +0100
@@ -1,0 +2,11 @@
+Wed Dec  3 21:26:55 UTC 2025 - Guang Yee <[email protected]>
+
+- Update 5.15.0
+  * Retry initial Redis connection #1534
+  * Correctly regenerate RabbitMQ binding after a connection failure #1516
+  * Support ext_type in the MsgPackPacket class #1521
+  * Support sending bytesarrays when using pub/sub managers
+  * Fix typos in documentation #1520
+  * Improvements to the logging documentation
+
+-------------------------------------------------------------------

Old:
----
  python_socketio-5.14.3.tar.gz

New:
----
  python_socketio-5.15.0.tar.gz

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

Other differences:
------------------
++++++ python-python-socketio.spec ++++++
--- /var/tmp/diff_new_pack.1jWF79/_old  2025-12-10 15:34:31.464365873 +0100
+++ /var/tmp/diff_new_pack.1jWF79/_new  2025-12-10 15:34:31.468366043 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-python-socketio
-Version:        5.14.3
+Version:        5.15.0
 Release:        0
 Summary:        SocketIO server
 License:        MIT
@@ -26,13 +26,13 @@
 Source:         
https://github.com/miguelgrinberg/python-socketio/archive/v%{version}.tar.gz#/python_socketio-%{version}.tar.gz
 BuildRequires:  %{python_module bidict >= 0.21.0}
 BuildRequires:  %{python_module pip}
-BuildRequires:  %{python_module python-engineio >= 4.8.0}
+BuildRequires:  %{python_module python-engineio >= 4.11.0}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-bidict >= 0.21.0
-Requires:       python-python-engineio >= 4.8.0
+Requires:       python-python-engineio >= 4.11.0
 Suggests:       python-aiohttp >= 3.4
 Suggests:       python-requests >= 2.21.0
 Suggests:       python-websocket-client >= 0.54.0

++++++ python_socketio-5.14.3.tar.gz -> python_socketio-5.15.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/CHANGES.md 
new/python-socketio-5.15.0/CHANGES.md
--- old/python-socketio-5.14.3/CHANGES.md       2025-10-29 10:42:20.000000000 
+0100
+++ new/python-socketio-5.15.0/CHANGES.md       2025-11-22 19:38:45.000000000 
+0100
@@ -1,5 +1,14 @@
 # python-socketio change log
 
+**Release 5.15.0** - 2025-11-22
+
+- Retry initial Redis connection 
[#1534](https://github.com/miguelgrinberg/python-socketio/issues/1536) ([commit 
#1](https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad)
 [commit 
#2](https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d))
+- Correctly regenerate RabbitMQ binding after a connection failure 
[#1516](https://github.com/miguelgrinberg/python-socketio/issues/1516) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913))
 (thanks **Gritty_dev**!)
+- Support `ext_type` in the `MsgPackPacket` class 
[#1521](https://github.com/miguelgrinberg/python-socketio/issues/1521) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311))
+- Support sending `bytesarray`s when using pub/sub managers 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098))
+- Fix typos in documentation 
[#1520](https://github.com/miguelgrinberg/python-socketio/issues/1520) 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd))
 (thanks **Lê Nam Khánh**!)
+- Improvements to the logging documentation 
([commit](https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c))
+
 **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))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/docs/client.rst 
new/python-socketio-5.15.0/docs/client.rst
--- old/python-socketio-5.14.3/docs/client.rst  2025-10-29 10:42:20.000000000 
+0100
+++ new/python-socketio-5.15.0/docs/client.rst  2025-11-22 19:38:45.000000000 
+0100
@@ -198,9 +198,14 @@
 
 The ``logger`` argument controls logging related to the Socket.IO protocol,
 while ``engineio_logger`` controls logs that originate in the low-level
-Engine.IO transport. These arguments can be set to ``True`` to output logs to
-``stderr``, or to an object compatible with Python's ``logging`` package
-where the logs should be emitted to. A value of ``False`` disables logging.
+Engine.IO transport. The value given to these arguments controls logging
+behavior:
+
+* ``True``: Enables log output to ``stderr`` at the ``INFO`` level.
+* ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is
+  the default.
+* A ``logging.Logger`` instance: Uses the provided logger without additional
+  configuration.
 
 Logging can help identify the cause of connection problems, unexpected
 disconnections and other issues.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/docs/server.rst 
new/python-socketio-5.15.0/docs/server.rst
--- old/python-socketio-5.14.3/docs/server.rst  2025-10-29 10:42:20.000000000 
+0100
+++ new/python-socketio-5.15.0/docs/server.rst  2025-11-22 19:38:45.000000000 
+0100
@@ -657,9 +657,14 @@
 
 The ``logger`` argument controls logging related to the Socket.IO protocol,
 while ``engineio_logger`` controls logs that originate in the low-level
-Engine.IO transport. These arguments can be set to ``True`` to output logs to
-``stderr``, or to an object compatible with Python's ``logging`` package
-where the logs should be emitted to. A value of ``False`` disables logging.
+Engine.IO transport. The value given to these arguments controls logging
+behavior:
+
+* ``True``: Enables log output to ``stderr`` at the ``INFO`` level.
+* ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is
+  the default.
+* A ``logging.Logger`` instance: Uses the provided logger without additional
+  configuration.
 
 Logging can help identify the cause of connection problems, 400 responses,
 bad performance and other issues.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/examples/server/wsgi/django_socketio/requirements.txt
 
new/python-socketio-5.15.0/examples/server/wsgi/django_socketio/requirements.txt
--- 
old/python-socketio-5.14.3/examples/server/wsgi/django_socketio/requirements.txt
    2025-10-29 10:42:20.000000000 +0100
+++ 
new/python-socketio-5.15.0/examples/server/wsgi/django_socketio/requirements.txt
    2025-11-22 19:38:45.000000000 +0100
@@ -1,6 +1,6 @@
 asgiref==3.6.0
 bidict==0.22.1
-Django==4.2.22
+Django==4.2.26
 gunicorn==23.0.0
 h11==0.16.0
 python-engineio
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/pyproject.toml 
new/python-socketio-5.15.0/pyproject.toml
--- old/python-socketio-5.14.3/pyproject.toml   2025-10-29 10:42:20.000000000 
+0100
+++ new/python-socketio-5.15.0/pyproject.toml   2025-11-22 19:38:45.000000000 
+0100
@@ -1,6 +1,6 @@
 [project]
 name = "python-socketio"
-version = "5.14.3"
+version = "5.15.0"
 license = {text = "MIT"}
 authors = [
     { name = "Miguel Grinberg", email = "[email protected]" },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/src/socketio/async_aiopika_manager.py 
new/python-socketio-5.15.0/src/socketio/async_aiopika_manager.py
--- old/python-socketio-5.14.3/src/socketio/async_aiopika_manager.py    
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/async_aiopika_manager.py    
2025-11-22 19:38:45.000000000 +0100
@@ -101,26 +101,26 @@
                 raise asyncio.CancelledError()
 
     async def _listen(self):
-        async with (await self._connection()) as connection:
-            channel = await self._channel(connection)
-            await channel.set_qos(prefetch_count=1)
-            exchange = await self._exchange(channel)
-            queue = await self._queue(channel, exchange)
+        retry_sleep = 1
+        while True:
+            try:
+                async with (await self._connection()) as connection:
+                    channel = await self._channel(connection)
+                    await channel.set_qos(prefetch_count=1)
+                    exchange = await self._exchange(channel)
+                    queue = await self._queue(channel, exchange)
 
-            retry_sleep = 1
-            while True:
-                try:
                     async with queue.iterator() as queue_iter:
                         async for message in queue_iter:
                             async with message.process():
                                 yield message.body
                                 retry_sleep = 1
-                except aio_pika.AMQPException:
-                    self._get_logger().error(
-                        'Cannot receive from rabbitmq... '
-                        'retrying in {} secs'.format(retry_sleep))
-                    await asyncio.sleep(retry_sleep)
-                    retry_sleep = min(retry_sleep * 2, 60)
-                except aio_pika.exceptions.ChannelInvalidStateError:
-                    # aio_pika raises this exception when the task is cancelled
-                    raise asyncio.CancelledError()
+            except aio_pika.AMQPException:
+                self._get_logger().error(
+                    'Cannot receive from rabbitmq... '
+                    'retrying in {} secs'.format(retry_sleep))
+                await asyncio.sleep(retry_sleep)
+                retry_sleep = min(retry_sleep * 2, 60)
+            except aio_pika.exceptions.ChannelInvalidStateError:
+                # aio_pika raises this exception when the task is cancelled
+                raise asyncio.CancelledError()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/src/socketio/async_client.py 
new/python-socketio-5.15.0/src/socketio/async_client.py
--- old/python-socketio-5.14.3/src/socketio/async_client.py     2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/async_client.py     2025-11-22 
19:38:45.000000000 +0100
@@ -489,7 +489,7 @@
                         raise
             return ret
 
-        # or else, forward the event to a namepsace handler if one exists
+        # or else, forward the event to a namespace handler if one exists
         handler, args = self._get_namespace_handler(namespace, args)
         if handler:
             return await handler.trigger_event(event, *args)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/src/socketio/async_redis_manager.py 
new/python-socketio-5.15.0/src/socketio/async_redis_manager.py
--- old/python-socketio-5.14.3/src/socketio/async_redis_manager.py      
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/async_redis_manager.py      
2025-11-22 19:38:45.000000000 +0100
@@ -61,7 +61,9 @@
         super().__init__(channel=channel, write_only=write_only, logger=logger)
         self.redis_url = url
         self.redis_options = redis_options or {}
-        self._redis_connect()
+        self.connected = False
+        self.redis = None
+        self.pubsub = None
 
     def _get_redis_module_and_error(self):
         parsed_url = urlparse(self.redis_url)
@@ -106,23 +108,23 @@
             self.redis = module.Redis.from_url(self.redis_url,
                                                **self.redis_options)
         self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
+        self.connected = True
 
     async def _publish(self, data):  # pragma: no cover
-        retry = True
         _, error = self._get_redis_module_and_error()
-        while True:
+        for retries_left in range(1, -1, -1):  # 2 attempts
             try:
-                if not retry:
+                if not self.connected:
                     self._redis_connect()
                 return await self.redis.publish(
                     self.channel, json.dumps(data))
             except error as exc:
-                if retry:
+                if retries_left > 0:
                     self._get_logger().error(
                         'Cannot publish to redis... '
                         'retrying',
                         extra={"redis_exception": str(exc)})
-                    retry = False
+                    self.connected = False
                 else:
                     self._get_logger().error(
                         'Cannot publish to redis... '
@@ -132,12 +134,12 @@
                     break
 
     async def _redis_listen_with_retries(self):  # pragma: no cover
-        retry_sleep = 1
-        connect = False
         _, error = self._get_redis_module_and_error()
+        retry_sleep = 1
+        subscribed = False
         while True:
             try:
-                if connect:
+                if not subscribed:
                     self._redis_connect()
                     await self.pubsub.subscribe(self.channel)
                     retry_sleep = 1
@@ -148,7 +150,7 @@
                                          'retrying in '
                                          f'{retry_sleep} secs',
                                          extra={"redis_exception": str(exc)})
-                connect = True
+                subscribed = False
                 await asyncio.sleep(retry_sleep)
                 retry_sleep *= 2
                 if retry_sleep > 60:
@@ -156,7 +158,6 @@
 
     async def _listen(self):  # pragma: no cover
         channel = self.channel.encode('utf-8')
-        await self.pubsub.subscribe(self.channel)
         async for message in self._redis_listen_with_retries():
             if message['channel'] == channel and \
                     message['type'] == 'message' and 'data' in message:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/src/socketio/kombu_manager.py 
new/python-socketio-5.15.0/src/socketio/kombu_manager.py
--- old/python-socketio-5.14.3/src/socketio/kombu_manager.py    2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/kombu_manager.py    2025-11-22 
19:38:45.000000000 +0100
@@ -115,10 +115,10 @@
                     break
 
     def _listen(self):
-        reader_queue = self._queue()
         retry_sleep = 1
         while True:
             try:
+                reader_queue = self._queue()
                 with self._connection() as connection:
                     with connection.SimpleQueue(reader_queue) as queue:
                         while True:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/src/socketio/msgpack_packet.py 
new/python-socketio-5.15.0/src/socketio/msgpack_packet.py
--- old/python-socketio-5.14.3/src/socketio/msgpack_packet.py   2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/msgpack_packet.py   2025-11-22 
19:38:45.000000000 +0100
@@ -4,14 +4,40 @@
 
 class MsgPackPacket(packet.Packet):
     uses_binary_events = False
+    dumps_default = None
+    ext_hook = msgpack.ExtType
+
+    @classmethod
+    def configure(cls, dumps_default=None, ext_hook=msgpack.ExtType):
+        """Change the default options for msgpack encoding and decoding.
+
+        :param dumps_default: a function called for objects that cannot be
+                              serialized by default msgpack. The function
+                              receives one argument, the object to serialize.
+                              It should return a serializable object or a
+                              ``msgpack.ExtType`` instance.
+        :param ext_hook: a function called when a ``msgpack.ExtType`` object is
+                         seen during decoding. The function receives two
+                         arguments, the code and the data. It should return the
+                         decoded object.
+        """
+        class CustomMsgPackPacket(MsgPackPacket):
+            dumps_default = None
+            ext_hook = None
+
+        CustomMsgPackPacket.dumps_default = dumps_default
+        CustomMsgPackPacket.ext_hook = ext_hook
+        return CustomMsgPackPacket
 
     def encode(self):
         """Encode the packet for transmission."""
-        return msgpack.dumps(self._to_dict())
+        return msgpack.dumps(self._to_dict(),
+                             default=self.__class__.dumps_default)
 
     def decode(self, encoded_packet):
         """Decode a transmitted package."""
-        decoded = msgpack.loads(encoded_packet)
+        decoded = msgpack.loads(encoded_packet,
+                                ext_hook=self.__class__.ext_hook)
         self.packet_type = decoded['type']
         self.data = decoded.get('data')
         self.id = decoded.get('id')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/src/socketio/packet.py 
new/python-socketio-5.15.0/src/socketio/packet.py
--- old/python-socketio-5.14.3/src/socketio/packet.py   2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/packet.py   2025-11-22 
19:38:45.000000000 +0100
@@ -154,7 +154,7 @@
 
     @classmethod
     def _deconstruct_binary_internal(cls, data, attachments):
-        if isinstance(data, bytes):
+        if isinstance(data, (bytes, bytearray)):
             attachments.append(data)
             return {'_placeholder': True, 'num': len(attachments) - 1}
         elif isinstance(data, list):
@@ -169,7 +169,7 @@
     @classmethod
     def data_is_binary(cls, data):
         """Check if the data contains binary components."""
-        if isinstance(data, bytes):
+        if isinstance(data, (bytes, bytearray)):
             return True
         elif isinstance(data, list):
             return functools.reduce(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/src/socketio/redis_manager.py 
new/python-socketio-5.15.0/src/socketio/redis_manager.py
--- old/python-socketio-5.14.3/src/socketio/redis_manager.py    2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/src/socketio/redis_manager.py    2025-11-22 
19:38:45.000000000 +0100
@@ -83,7 +83,9 @@
         super().__init__(channel=channel, write_only=write_only, logger=logger)
         self.redis_url = url
         self.redis_options = redis_options or {}
-        self._redis_connect()
+        self.connected = False
+        self.redis = None
+        self.pubsub = None
 
     def initialize(self):  # pragma: no cover
         super().initialize()
@@ -143,22 +145,22 @@
             self.redis = module.Redis.from_url(self.redis_url,
                                                **self.redis_options)
         self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
+        self.connected = True
 
     def _publish(self, data):  # pragma: no cover
-        retry = True
         _, error = self._get_redis_module_and_error()
-        while True:
+        for retries_left in range(1, -1, -1):  # 2 attempts
             try:
-                if not retry:
+                if not self.connected:
                     self._redis_connect()
                 return self.redis.publish(self.channel, json.dumps(data))
             except error as exc:
-                if retry:
+                if retries_left > 0:
                     logger.error(
                         'Cannot publish to redis... retrying',
                         extra={"redis_exception": str(exc)}
                     )
-                    retry = False
+                    self.connected = False
                 else:
                     logger.error(
                         'Cannot publish to redis... giving up',
@@ -167,12 +169,12 @@
                     break
 
     def _redis_listen_with_retries(self):  # pragma: no cover
-        retry_sleep = 1
-        connect = False
         _, error = self._get_redis_module_and_error()
+        retry_sleep = 1
+        subscribed = False
         while True:
             try:
-                if connect:
+                if not subscribed:
                     self._redis_connect()
                     self.pubsub.subscribe(self.channel)
                     retry_sleep = 1
@@ -181,7 +183,7 @@
                 logger.error('Cannot receive from redis... '
                              f'retrying in {retry_sleep} secs',
                              extra={"redis_exception": str(exc)})
-                connect = True
+                subscribed = False
                 time.sleep(retry_sleep)
                 retry_sleep *= 2
                 if retry_sleep > 60:
@@ -189,7 +191,6 @@
 
     def _listen(self):  # pragma: no cover
         channel = self.channel.encode('utf-8')
-        self.pubsub.subscribe(self.channel)
         for message in self._redis_listen_with_retries():
             if message['channel'] == channel and \
                     message['type'] == 'message' and 'data' in message:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/tests/async/test_client.py 
new/python-socketio-5.15.0/tests/async/test_client.py
--- old/python-socketio-5.14.3/tests/async/test_client.py       2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/async/test_client.py       2025-11-22 
19:38:45.000000000 +0100
@@ -1,5 +1,6 @@
 import asyncio
 from unittest import mock
+from datetime import datetime, timezone, timedelta
 
 import pytest
 
@@ -8,6 +9,7 @@
 from engineio import exceptions as engineio_exceptions
 from socketio import exceptions
 from socketio import packet
+from socketio.msgpack_packet import MsgPackPacket
 
 
 class TestAsyncClient:
@@ -1242,3 +1244,21 @@
         assert c.sid is None
         assert not c.connected
         c.start_background_task.assert_not_called()
+
+    def test_serializer_args_with_msgpack(self):
+        def default(o):
+            if isinstance(o, datetime):
+                return o.isoformat()
+            raise TypeError("Unknown type")
+
+        data = {"current": datetime.now(timezone(timedelta(0)))}
+        c = async_client.AsyncClient(
+            serializer=MsgPackPacket.configure(dumps_default=default))
+        p = c.packet_class(data=data)
+        p2 = c.packet_class(encoded_packet=p.encode())
+
+        assert p.data != p2.data
+        assert isinstance(p2.data, dict)
+        assert "current" in p2.data
+        assert isinstance(p2.data["current"], str)
+        assert default(data["current"]) == p2.data["current"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/tests/async/test_pubsub_manager.py 
new/python-socketio-5.15.0/tests/async/test_pubsub_manager.py
--- old/python-socketio-5.14.3/tests/async/test_pubsub_manager.py       
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/async/test_pubsub_manager.py       
2025-11-22 19:38:45.000000000 +0100
@@ -97,6 +97,36 @@
             }
         )
 
+    async def test_emit_bytearray(self):
+        await self.pm.emit('foo', bytearray(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': bytearray(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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/tests/async/test_redis_manager.py 
new/python-socketio-5.15.0/tests/async/test_redis_manager.py
--- old/python-socketio-5.14.3/tests/async/test_redis_manager.py        
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/async/test_redis_manager.py        
2025-11-22 19:38:45.000000000 +0100
@@ -12,7 +12,7 @@
         async_redis_manager.aioredis = None
 
         with pytest.raises(RuntimeError):
-            AsyncRedisManager('redis://')
+            AsyncRedisManager('redis://')._redis_connect()
         assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None
 
         async_redis_manager.aioredis = saved_redis
@@ -22,7 +22,7 @@
         async_redis_manager.aiovalkey = None
 
         with pytest.raises(RuntimeError):
-            AsyncRedisManager('valkey://')
+            AsyncRedisManager('valkey://')._redis_connect()
         assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None
 
         async_redis_manager.aiovalkey = saved_valkey
@@ -34,18 +34,18 @@
         async_redis_manager.aiovalkey = None
 
         with pytest.raises(RuntimeError):
-            AsyncRedisManager('redis://')
+            AsyncRedisManager('redis://')._redis_connect()
         with pytest.raises(RuntimeError):
-            AsyncRedisManager('valkey://')
+            AsyncRedisManager('valkey://')._redis_connect()
         with pytest.raises(RuntimeError):
-            AsyncRedisManager('unix:///var/sock/redis.sock')
+            AsyncRedisManager('unix:///var/sock/redis.sock')._redis_connect()
 
         async_redis_manager.aioredis = saved_redis
         async_redis_manager.aiovalkey = saved_valkey
 
     def test_bad_url(self):
         with pytest.raises(ValueError):
-            AsyncRedisManager('http://localhost:6379')
+            AsyncRedisManager('http://localhost:6379')._redis_connect()
 
     def test_redis_connect(self):
         urls = [
@@ -72,6 +72,8 @@
         ]
         for url in urls:
             c = AsyncRedisManager(url)
+            assert c.redis is None
+            c._redis_connect()
             assert isinstance(c.redis, redis.asyncio.Redis)
 
     def test_valkey_connect(self):
@@ -102,6 +104,8 @@
         ]
         for url in urls:
             c = AsyncRedisManager(url)
+            assert c.redis is None
+            c._redis_connect()
             assert isinstance(c.redis, valkey.asyncio.Valkey)
 
         async_redis_manager.aioredis = saved_redis
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/tests/async/test_server.py 
new/python-socketio-5.15.0/tests/async/test_server.py
--- old/python-socketio-5.14.3/tests/async/test_server.py       2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/async/test_server.py       2025-11-22 
19:38:45.000000000 +0100
@@ -1,6 +1,7 @@
 import asyncio
 import logging
 from unittest import mock
+from datetime import datetime, timezone, timedelta
 
 from engineio import json
 from engineio import packet as eio_packet
@@ -11,6 +12,7 @@
 from socketio import exceptions
 from socketio import namespace
 from socketio import packet
+from socketio.msgpack_packet import MsgPackPacket
 
 
 @mock.patch('socketio.server.engineio.AsyncServer', **{
@@ -1089,3 +1091,21 @@
         s = async_server.AsyncServer()
         await s.sleep(1.23)
         s.eio.sleep.assert_awaited_once_with(1.23)
+
+    def test_serializer_args_with_msgpack(self, eio):
+        def default(o):
+            if isinstance(o, datetime):
+                return o.isoformat()
+            raise TypeError("Unknown type")
+
+        data = {"current": datetime.now(timezone(timedelta(0)))}
+        s = async_server.AsyncServer(
+            serializer=MsgPackPacket.configure(dumps_default=default))
+        p = s.packet_class(data=data)
+        p2 = s.packet_class(encoded_packet=p.encode())
+
+        assert p.data != p2.data
+        assert isinstance(p2.data, dict)
+        assert "current" in p2.data
+        assert isinstance(p2.data["current"], str)
+        assert default(data["current"]) == p2.data["current"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/tests/common/test_client.py 
new/python-socketio-5.15.0/tests/common/test_client.py
--- old/python-socketio-5.14.3/tests/common/test_client.py      2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/common/test_client.py      2025-11-22 
19:38:45.000000000 +0100
@@ -1,6 +1,7 @@
 import logging
 import time
 from unittest import mock
+from datetime import datetime, timezone, timedelta
 
 from engineio import exceptions as engineio_exceptions
 from engineio import json
@@ -13,6 +14,7 @@
 from socketio import msgpack_packet
 from socketio import namespace
 from socketio import packet
+from socketio.msgpack_packet import MsgPackPacket
 
 
 class TestClient:
@@ -1386,3 +1388,21 @@
         assert c.sid is None
         assert not c.connected
         c.start_background_task.assert_not_called()
+
+    def test_serializer_args_with_msgpack(self):
+        def default(o):
+            if isinstance(o, datetime):
+                return o.isoformat()
+            raise TypeError("Unknown type")
+
+        data = {"current": datetime.now(timezone(timedelta(0)))}
+        c = client.Client(
+            serializer=MsgPackPacket.configure(dumps_default=default))
+        p = c.packet_class(data=data)
+        p2 = c.packet_class(encoded_packet=p.encode())
+
+        assert p.data != p2.data
+        assert isinstance(p2.data, dict)
+        assert "current" in p2.data
+        assert isinstance(p2.data["current"], str)
+        assert default(data["current"]) == p2.data["current"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/tests/common/test_msgpack_packet.py 
new/python-socketio-5.15.0/tests/common/test_msgpack_packet.py
--- old/python-socketio-5.14.3/tests/common/test_msgpack_packet.py      
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/common/test_msgpack_packet.py      
2025-11-22 19:38:45.000000000 +0100
@@ -1,3 +1,8 @@
+from datetime import datetime, timedelta, timezone
+
+import pytest
+import msgpack
+
 from socketio import msgpack_packet
 from socketio import packet
 
@@ -32,3 +37,102 @@
         assert p.packet_type == packet.ACK
         p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
         assert p2.data == {'foo': b'bar'}
+
+    def test_encode_with_dumps_default(self):
+        def default(obj):
+            if isinstance(obj, datetime):
+                return obj.isoformat()
+            raise TypeError('Unknown type')
+
+        data = {
+            'current': datetime.now(tz=timezone(timedelta(0))),
+            'key': 'value',
+        }
+        p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
+            data=data)
+        p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
+        assert p.packet_type == p2.packet_type
+        assert p.id == p2.id
+        assert p.namespace == p2.namespace
+        assert p.data != p2.data
+
+        assert isinstance(p2.data, dict)
+        assert 'current' in p2.data
+        assert isinstance(p2.data['current'], str)
+        assert default(data['current']) == p2.data['current']
+
+        data.pop('current')
+        p2_data_without_current = p2.data.copy()
+        p2_data_without_current.pop('current')
+        assert data == p2_data_without_current
+
+    def test_encode_without_dumps_default(self):
+        data = {
+            'current': datetime.now(tz=timezone(timedelta(0))),
+            'key': 'value',
+        }
+        p_without_default = msgpack_packet.MsgPackPacket(data=data)
+        with pytest.raises(TypeError):
+            p_without_default.encode()
+
+    def test_encode_decode_with_ext_hook(self):
+        class Custom:
+            def __init__(self, value):
+                self.value = value
+
+            def __eq__(self, value: object) -> bool:
+                return isinstance(value, Custom) and self.value == value.value
+
+        def default(obj):
+            if isinstance(obj, Custom):
+                return msgpack.ExtType(1, obj.value)
+            raise TypeError('Unknown type')
+
+        def ext_hook(code, data):
+            if code == 1:
+                return Custom(data)
+            raise TypeError('Unknown ext type')
+
+        data = {'custom': Custom(b'custom_data'), 'key': 'value'}
+        p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
+            data=data)
+        p2 = msgpack_packet.MsgPackPacket.configure(ext_hook=ext_hook)(
+            encoded_packet=p.encode()
+        )
+        assert p.packet_type == p2.packet_type
+        assert p.id == p2.id
+        assert p.data == p2.data
+        assert p.namespace == p2.namespace
+
+    def test_encode_decode_without_ext_hook(self):
+        class Custom:
+            def __init__(self, value):
+                self.value = value
+
+            def __eq__(self, value: object) -> bool:
+                return isinstance(value, Custom) and self.value == value.value
+
+        def default(obj):
+            if isinstance(obj, Custom):
+                return msgpack.ExtType(1, obj.value)
+            raise TypeError('Unknown type')
+
+        data = {'custom': Custom(b'custom_data'), 'key': 'value'}
+        p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
+            data=data)
+        p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
+        assert p.packet_type == p2.packet_type
+        assert p.id == p2.id
+        assert p.namespace == p2.namespace
+        assert p.data != p2.data
+
+        assert isinstance(p2.data, dict)
+        assert 'custom' in p2.data
+        assert isinstance(p2.data['custom'], msgpack.ExtType)
+        assert p2.data['custom'].code == 1
+        assert p2.data['custom'].data == b'custom_data'
+
+        data.pop('custom')
+        p2_data_without_custom = p2.data.copy()
+        p2_data_without_custom.pop('custom')
+        assert data == p2_data_without_custom
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/tests/common/test_packet.py 
new/python-socketio-5.15.0/tests/common/test_packet.py
--- old/python-socketio-5.14.3/tests/common/test_packet.py      2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/common/test_packet.py      2025-11-22 
19:38:45.000000000 +0100
@@ -279,11 +279,15 @@
         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([bytearray(b'foo')])
         assert pkt.data_is_binary(['foo', b'bar'])
+        assert pkt.data_is_binary(['foo', bytearray(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': bytearray(b'foo')})
         assert pkt.data_is_binary({'a': 'foo', 'b': b'bar'})
+        assert pkt.data_is_binary({'a': 'foo', 'b': bytearray(b'bar')})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/tests/common/test_pubsub_manager.py 
new/python-socketio-5.15.0/tests/common/test_pubsub_manager.py
--- old/python-socketio-5.14.3/tests/common/test_pubsub_manager.py      
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/common/test_pubsub_manager.py      
2025-11-22 19:38:45.000000000 +0100
@@ -109,6 +109,36 @@
             }
         )
 
+    def test_emit_bytearray(self):
+        self.pm.emit('foo', bytearray(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': bytearray(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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-socketio-5.14.3/tests/common/test_redis_manager.py 
new/python-socketio-5.15.0/tests/common/test_redis_manager.py
--- old/python-socketio-5.14.3/tests/common/test_redis_manager.py       
2025-10-29 10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/common/test_redis_manager.py       
2025-11-22 19:38:45.000000000 +0100
@@ -12,7 +12,7 @@
         redis_manager.redis = None
 
         with pytest.raises(RuntimeError):
-            RedisManager('redis://')
+            RedisManager('redis://')._redis_connect()
         assert RedisManager('unix:///var/sock/redis.sock') is not None
 
         redis_manager.redis = saved_redis
@@ -22,7 +22,7 @@
         redis_manager.valkey = None
 
         with pytest.raises(RuntimeError):
-            RedisManager('valkey://')
+            RedisManager('valkey://')._redis_connect()
         assert RedisManager('unix:///var/sock/redis.sock') is not None
 
         redis_manager.valkey = saved_valkey
@@ -34,18 +34,18 @@
         redis_manager.valkey = None
 
         with pytest.raises(RuntimeError):
-            RedisManager('redis://')
+            RedisManager('redis://')._redis_connect()
         with pytest.raises(RuntimeError):
-            RedisManager('valkey://')
+            RedisManager('valkey://')._redis_connect()
         with pytest.raises(RuntimeError):
-            RedisManager('unix:///var/sock/redis.sock')
+            RedisManager('unix:///var/sock/redis.sock')._redis_connect()
 
         redis_manager.redis = saved_redis
         redis_manager.valkey = saved_valkey
 
     def test_bad_url(self):
         with pytest.raises(ValueError):
-            RedisManager('http://localhost:6379')
+            RedisManager('http://localhost:6379')._redis_connect()
 
     def test_redis_connect(self):
         urls = [
@@ -72,6 +72,8 @@
         ]
         for url in urls:
             c = RedisManager(url)
+            assert c.redis is None
+            c._redis_connect()
             assert isinstance(c.redis, redis.Redis)
 
     def test_valkey_connect(self):
@@ -102,6 +104,8 @@
         ]
         for url in urls:
             c = RedisManager(url)
+            assert c.redis is None
+            c._redis_connect()
             assert isinstance(c.redis, valkey.Valkey)
 
         redis_manager.redis = saved_redis
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-socketio-5.14.3/tests/common/test_server.py 
new/python-socketio-5.15.0/tests/common/test_server.py
--- old/python-socketio-5.14.3/tests/common/test_server.py      2025-10-29 
10:42:20.000000000 +0100
+++ new/python-socketio-5.15.0/tests/common/test_server.py      2025-11-22 
19:38:45.000000000 +0100
@@ -1,5 +1,6 @@
 import logging
 from unittest import mock
+from datetime import datetime, timezone, timedelta
 
 from engineio import json
 from engineio import packet as eio_packet
@@ -10,6 +11,7 @@
 from socketio import namespace
 from socketio import packet
 from socketio import server
+from socketio.msgpack_packet import MsgPackPacket
 
 
 @mock.patch('socketio.server.engineio.Server', **{
@@ -1032,3 +1034,21 @@
         s = server.Server()
         s.sleep(1.23)
         s.eio.sleep.assert_called_once_with(1.23)
+
+    def test_serializer_args_with_msgpack(self, eio):
+        def default(o):
+            if isinstance(o, datetime):
+                return o.isoformat()
+            raise TypeError("Unknown type")
+
+        data = {"current": datetime.now(timezone(timedelta(0)))}
+        s = server.Server(
+            serializer=MsgPackPacket.configure(dumps_default=default))
+        p = s.packet_class(data=data)
+        p2 = s.packet_class(encoded_packet=p.encode())
+
+        assert p.data != p2.data
+        assert isinstance(p2.data, dict)
+        assert "current" in p2.data
+        assert isinstance(p2.data["current"], str)
+        assert default(data["current"]) == p2.data["current"]

Reply via email to