python-socketio 5.14.2__tar.gz → 5.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {python_socketio-5.14.2/src/python_socketio.egg-info → python_socketio-5.15.0}/PKG-INFO +1 -1
  2. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/client.rst +8 -3
  3. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/server.rst +10 -3
  4. {python_socketio-5.14.2 → python_socketio-5.15.0}/pyproject.toml +1 -1
  5. {python_socketio-5.14.2 → python_socketio-5.15.0/src/python_socketio.egg-info}/PKG-INFO +1 -1
  6. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_aiopika_manager.py +19 -19
  7. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_client.py +1 -1
  8. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_redis_manager.py +12 -11
  9. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_server.py +3 -0
  10. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/kombu_manager.py +1 -1
  11. python_socketio-5.15.0/src/socketio/msgpack_packet.py +44 -0
  12. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/packet.py +2 -2
  13. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/redis_manager.py +12 -11
  14. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/server.py +3 -0
  15. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_client.py +20 -0
  16. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_pubsub_manager.py +30 -0
  17. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_redis_manager.py +10 -6
  18. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_server.py +35 -0
  19. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_client.py +20 -0
  20. python_socketio-5.15.0/tests/common/test_msgpack_packet.py +138 -0
  21. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_packet.py +4 -0
  22. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_pubsub_manager.py +30 -0
  23. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_redis_manager.py +10 -6
  24. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_server.py +34 -0
  25. python_socketio-5.14.2/src/socketio/msgpack_packet.py +0 -18
  26. python_socketio-5.14.2/tests/common/test_msgpack_packet.py +0 -34
  27. {python_socketio-5.14.2 → python_socketio-5.15.0}/LICENSE +0 -0
  28. {python_socketio-5.14.2 → python_socketio-5.15.0}/MANIFEST.in +0 -0
  29. {python_socketio-5.14.2 → python_socketio-5.15.0}/README.md +0 -0
  30. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/Makefile +0 -0
  31. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/_static/README.md +0 -0
  32. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/_static/custom.css +0 -0
  33. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/api.rst +0 -0
  34. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/conf.py +0 -0
  35. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/index.rst +0 -0
  36. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/intro.rst +0 -0
  37. {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/make.bat +0 -0
  38. {python_socketio-5.14.2 → python_socketio-5.15.0}/setup.cfg +0 -0
  39. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/SOURCES.txt +0 -0
  40. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/dependency_links.txt +0 -0
  41. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/not-zip-safe +0 -0
  42. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/requires.txt +0 -0
  43. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/top_level.txt +0 -0
  44. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/__init__.py +0 -0
  45. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/admin.py +0 -0
  46. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/asgi.py +0 -0
  47. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_admin.py +0 -0
  48. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_manager.py +0 -0
  49. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_namespace.py +0 -0
  50. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_pubsub_manager.py +0 -0
  51. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_simple_client.py +0 -0
  52. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_client.py +0 -0
  53. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_manager.py +0 -0
  54. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_namespace.py +0 -0
  55. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_server.py +0 -0
  56. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/client.py +0 -0
  57. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/exceptions.py +0 -0
  58. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/kafka_manager.py +0 -0
  59. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/manager.py +0 -0
  60. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/middleware.py +0 -0
  61. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/namespace.py +0 -0
  62. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/pubsub_manager.py +0 -0
  63. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/simple_client.py +0 -0
  64. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/tornado.py +0 -0
  65. {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/zmq_manager.py +0 -0
  66. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/__init__.py +0 -0
  67. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/__init__.py +0 -0
  68. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_admin.py +0 -0
  69. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_manager.py +0 -0
  70. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_namespace.py +0 -0
  71. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_simple_client.py +0 -0
  72. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/asyncio_web_server.py +0 -0
  73. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/__init__.py +0 -0
  74. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_admin.py +0 -0
  75. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_manager.py +0 -0
  76. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_middleware.py +0 -0
  77. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_namespace.py +0 -0
  78. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_simple_client.py +0 -0
  79. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/README.md +0 -0
  80. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/binary_packet.py +0 -0
  81. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/json_packet.py +0 -0
  82. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/namespace_packet.py +0 -0
  83. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/run.sh +0 -0
  84. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_receive.py +0 -0
  85. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_send.py +0 -0
  86. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_send_broadcast.py +0 -0
  87. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/text_packet.py +0 -0
  88. {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/web_server.py +0 -0
  89. {python_socketio-5.14.2 → python_socketio-5.15.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-socketio
3
- Version: 5.14.2
3
+ Version: 5.15.0
4
4
  Summary: Socket.IO server and client for Python
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
6
  License: MIT
@@ -198,9 +198,14 @@ terminal::
198
198
 
199
199
  The ``logger`` argument controls logging related to the Socket.IO protocol,
200
200
  while ``engineio_logger`` controls logs that originate in the low-level
201
- Engine.IO transport. These arguments can be set to ``True`` to output logs to
202
- ``stderr``, or to an object compatible with Python's ``logging`` package
203
- where the logs should be emitted to. A value of ``False`` disables logging.
201
+ Engine.IO transport. The value given to these arguments controls logging
202
+ behavior:
203
+
204
+ * ``True``: Enables log output to ``stderr`` at the ``INFO`` level.
205
+ * ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is
206
+ the default.
207
+ * A ``logging.Logger`` instance: Uses the provided logger without additional
208
+ configuration.
204
209
 
205
210
  Logging can help identify the cause of connection problems, unexpected
206
211
  disconnections and other issues.
@@ -246,6 +246,8 @@ a :class:`socketio.exceptions.ConnectionRefusedError` exception can be raised,
246
246
  and all of its arguments will be sent to the client with the rejection
247
247
  message::
248
248
 
249
+ from socketio.exceptions import ConnectionRefusedError
250
+
249
251
  @sio.event
250
252
  def connect(sid, environ, auth):
251
253
  raise ConnectionRefusedError('authentication failed')
@@ -655,9 +657,14 @@ terminal::
655
657
 
656
658
  The ``logger`` argument controls logging related to the Socket.IO protocol,
657
659
  while ``engineio_logger`` controls logs that originate in the low-level
658
- Engine.IO transport. These arguments can be set to ``True`` to output logs to
659
- ``stderr``, or to an object compatible with Python's ``logging`` package
660
- where the logs should be emitted to. A value of ``False`` disables logging.
660
+ Engine.IO transport. The value given to these arguments controls logging
661
+ behavior:
662
+
663
+ * ``True``: Enables log output to ``stderr`` at the ``INFO`` level.
664
+ * ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is
665
+ the default.
666
+ * A ``logging.Logger`` instance: Uses the provided logger without additional
667
+ configuration.
661
668
 
662
669
  Logging can help identify the cause of connection problems, 400 responses,
663
670
  bad performance and other issues.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-socketio"
3
- version = "5.14.2"
3
+ version = "5.15.0"
4
4
  license = {text = "MIT"}
5
5
  authors = [
6
6
  { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-socketio
3
- Version: 5.14.2
3
+ Version: 5.15.0
4
4
  Summary: Socket.IO server and client for Python
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
6
  License: MIT
@@ -82,7 +82,7 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover
82
82
  try:
83
83
  await self.publisher_exchange.publish(
84
84
  aio_pika.Message(
85
- body=json.dumps(data),
85
+ body=json.dumps(data).encode(),
86
86
  delivery_mode=aio_pika.DeliveryMode.PERSISTENT
87
87
  ), routing_key='*',
88
88
  )
@@ -101,26 +101,26 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover
101
101
  raise asyncio.CancelledError()
102
102
 
103
103
  async def _listen(self):
104
- async with (await self._connection()) as connection:
105
- channel = await self._channel(connection)
106
- await channel.set_qos(prefetch_count=1)
107
- exchange = await self._exchange(channel)
108
- queue = await self._queue(channel, exchange)
109
-
110
- retry_sleep = 1
111
- while True:
112
- try:
104
+ retry_sleep = 1
105
+ while True:
106
+ try:
107
+ async with (await self._connection()) as connection:
108
+ channel = await self._channel(connection)
109
+ await channel.set_qos(prefetch_count=1)
110
+ exchange = await self._exchange(channel)
111
+ queue = await self._queue(channel, exchange)
112
+
113
113
  async with queue.iterator() as queue_iter:
114
114
  async for message in queue_iter:
115
115
  async with message.process():
116
116
  yield message.body
117
117
  retry_sleep = 1
118
- except aio_pika.AMQPException:
119
- self._get_logger().error(
120
- 'Cannot receive from rabbitmq... '
121
- 'retrying in {} secs'.format(retry_sleep))
122
- await asyncio.sleep(retry_sleep)
123
- retry_sleep = min(retry_sleep * 2, 60)
124
- except aio_pika.exceptions.ChannelInvalidStateError:
125
- # aio_pika raises this exception when the task is cancelled
126
- raise asyncio.CancelledError()
118
+ except aio_pika.AMQPException:
119
+ self._get_logger().error(
120
+ 'Cannot receive from rabbitmq... '
121
+ 'retrying in {} secs'.format(retry_sleep))
122
+ await asyncio.sleep(retry_sleep)
123
+ retry_sleep = min(retry_sleep * 2, 60)
124
+ except aio_pika.exceptions.ChannelInvalidStateError:
125
+ # aio_pika raises this exception when the task is cancelled
126
+ raise asyncio.CancelledError()
@@ -489,7 +489,7 @@ class AsyncClient(base_client.BaseClient):
489
489
  raise
490
490
  return ret
491
491
 
492
- # or else, forward the event to a namepsace handler if one exists
492
+ # or else, forward the event to a namespace handler if one exists
493
493
  handler, args = self._get_namespace_handler(namespace, args)
494
494
  if handler:
495
495
  return await handler.trigger_event(event, *args)
@@ -61,7 +61,9 @@ class AsyncRedisManager(AsyncPubSubManager):
61
61
  super().__init__(channel=channel, write_only=write_only, logger=logger)
62
62
  self.redis_url = url
63
63
  self.redis_options = redis_options or {}
64
- self._redis_connect()
64
+ self.connected = False
65
+ self.redis = None
66
+ self.pubsub = None
65
67
 
66
68
  def _get_redis_module_and_error(self):
67
69
  parsed_url = urlparse(self.redis_url)
@@ -106,23 +108,23 @@ class AsyncRedisManager(AsyncPubSubManager):
106
108
  self.redis = module.Redis.from_url(self.redis_url,
107
109
  **self.redis_options)
108
110
  self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
111
+ self.connected = True
109
112
 
110
113
  async def _publish(self, data): # pragma: no cover
111
- retry = True
112
114
  _, error = self._get_redis_module_and_error()
113
- while True:
115
+ for retries_left in range(1, -1, -1): # 2 attempts
114
116
  try:
115
- if not retry:
117
+ if not self.connected:
116
118
  self._redis_connect()
117
119
  return await self.redis.publish(
118
120
  self.channel, json.dumps(data))
119
121
  except error as exc:
120
- if retry:
122
+ if retries_left > 0:
121
123
  self._get_logger().error(
122
124
  'Cannot publish to redis... '
123
125
  'retrying',
124
126
  extra={"redis_exception": str(exc)})
125
- retry = False
127
+ self.connected = False
126
128
  else:
127
129
  self._get_logger().error(
128
130
  'Cannot publish to redis... '
@@ -132,12 +134,12 @@ class AsyncRedisManager(AsyncPubSubManager):
132
134
  break
133
135
 
134
136
  async def _redis_listen_with_retries(self): # pragma: no cover
135
- retry_sleep = 1
136
- connect = False
137
137
  _, error = self._get_redis_module_and_error()
138
+ retry_sleep = 1
139
+ subscribed = False
138
140
  while True:
139
141
  try:
140
- if connect:
142
+ if not subscribed:
141
143
  self._redis_connect()
142
144
  await self.pubsub.subscribe(self.channel)
143
145
  retry_sleep = 1
@@ -148,7 +150,7 @@ class AsyncRedisManager(AsyncPubSubManager):
148
150
  'retrying in '
149
151
  f'{retry_sleep} secs',
150
152
  extra={"redis_exception": str(exc)})
151
- connect = True
153
+ subscribed = False
152
154
  await asyncio.sleep(retry_sleep)
153
155
  retry_sleep *= 2
154
156
  if retry_sleep > 60:
@@ -156,7 +158,6 @@ class AsyncRedisManager(AsyncPubSubManager):
156
158
 
157
159
  async def _listen(self): # pragma: no cover
158
160
  channel = self.channel.encode('utf-8')
159
- await self.pubsub.subscribe(self.channel)
160
161
  async for message in self._redis_listen_with_retries():
161
162
  if message['channel'] == channel and \
162
163
  message['type'] == 'message' and 'data' in message:
@@ -561,6 +561,9 @@ class AsyncServer(base_server.BaseServer):
561
561
  except exceptions.ConnectionRefusedError as exc:
562
562
  fail_reason = exc.error_args
563
563
  success = False
564
+ except ConnectionRefusedError:
565
+ fail_reason = {"message": "Connection refused by server"}
566
+ success = False
564
567
 
565
568
  if success is False:
566
569
  if self.always_connect:
@@ -115,10 +115,10 @@ class KombuManager(PubSubManager): # pragma: no cover
115
115
  break
116
116
 
117
117
  def _listen(self):
118
- reader_queue = self._queue()
119
118
  retry_sleep = 1
120
119
  while True:
121
120
  try:
121
+ reader_queue = self._queue()
122
122
  with self._connection() as connection:
123
123
  with connection.SimpleQueue(reader_queue) as queue:
124
124
  while True:
@@ -0,0 +1,44 @@
1
+ import msgpack
2
+ from . import packet
3
+
4
+
5
+ class MsgPackPacket(packet.Packet):
6
+ uses_binary_events = False
7
+ dumps_default = None
8
+ ext_hook = msgpack.ExtType
9
+
10
+ @classmethod
11
+ def configure(cls, dumps_default=None, ext_hook=msgpack.ExtType):
12
+ """Change the default options for msgpack encoding and decoding.
13
+
14
+ :param dumps_default: a function called for objects that cannot be
15
+ serialized by default msgpack. The function
16
+ receives one argument, the object to serialize.
17
+ It should return a serializable object or a
18
+ ``msgpack.ExtType`` instance.
19
+ :param ext_hook: a function called when a ``msgpack.ExtType`` object is
20
+ seen during decoding. The function receives two
21
+ arguments, the code and the data. It should return the
22
+ decoded object.
23
+ """
24
+ class CustomMsgPackPacket(MsgPackPacket):
25
+ dumps_default = None
26
+ ext_hook = None
27
+
28
+ CustomMsgPackPacket.dumps_default = dumps_default
29
+ CustomMsgPackPacket.ext_hook = ext_hook
30
+ return CustomMsgPackPacket
31
+
32
+ def encode(self):
33
+ """Encode the packet for transmission."""
34
+ return msgpack.dumps(self._to_dict(),
35
+ default=self.__class__.dumps_default)
36
+
37
+ def decode(self, encoded_packet):
38
+ """Decode a transmitted package."""
39
+ decoded = msgpack.loads(encoded_packet,
40
+ ext_hook=self.__class__.ext_hook)
41
+ self.packet_type = decoded['type']
42
+ self.data = decoded.get('data')
43
+ self.id = decoded.get('id')
44
+ self.namespace = decoded['nsp']
@@ -154,7 +154,7 @@ class Packet:
154
154
 
155
155
  @classmethod
156
156
  def _deconstruct_binary_internal(cls, data, attachments):
157
- if isinstance(data, bytes):
157
+ if isinstance(data, (bytes, bytearray)):
158
158
  attachments.append(data)
159
159
  return {'_placeholder': True, 'num': len(attachments) - 1}
160
160
  elif isinstance(data, list):
@@ -169,7 +169,7 @@ class Packet:
169
169
  @classmethod
170
170
  def data_is_binary(cls, data):
171
171
  """Check if the data contains binary components."""
172
- if isinstance(data, bytes):
172
+ if isinstance(data, (bytes, bytearray)):
173
173
  return True
174
174
  elif isinstance(data, list):
175
175
  return functools.reduce(
@@ -83,7 +83,9 @@ class RedisManager(PubSubManager):
83
83
  super().__init__(channel=channel, write_only=write_only, logger=logger)
84
84
  self.redis_url = url
85
85
  self.redis_options = redis_options or {}
86
- self._redis_connect()
86
+ self.connected = False
87
+ self.redis = None
88
+ self.pubsub = None
87
89
 
88
90
  def initialize(self): # pragma: no cover
89
91
  super().initialize()
@@ -143,22 +145,22 @@ class RedisManager(PubSubManager):
143
145
  self.redis = module.Redis.from_url(self.redis_url,
144
146
  **self.redis_options)
145
147
  self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
148
+ self.connected = True
146
149
 
147
150
  def _publish(self, data): # pragma: no cover
148
- retry = True
149
151
  _, error = self._get_redis_module_and_error()
150
- while True:
152
+ for retries_left in range(1, -1, -1): # 2 attempts
151
153
  try:
152
- if not retry:
154
+ if not self.connected:
153
155
  self._redis_connect()
154
156
  return self.redis.publish(self.channel, json.dumps(data))
155
157
  except error as exc:
156
- if retry:
158
+ if retries_left > 0:
157
159
  logger.error(
158
160
  'Cannot publish to redis... retrying',
159
161
  extra={"redis_exception": str(exc)}
160
162
  )
161
- retry = False
163
+ self.connected = False
162
164
  else:
163
165
  logger.error(
164
166
  'Cannot publish to redis... giving up',
@@ -167,12 +169,12 @@ class RedisManager(PubSubManager):
167
169
  break
168
170
 
169
171
  def _redis_listen_with_retries(self): # pragma: no cover
170
- retry_sleep = 1
171
- connect = False
172
172
  _, error = self._get_redis_module_and_error()
173
+ retry_sleep = 1
174
+ subscribed = False
173
175
  while True:
174
176
  try:
175
- if connect:
177
+ if not subscribed:
176
178
  self._redis_connect()
177
179
  self.pubsub.subscribe(self.channel)
178
180
  retry_sleep = 1
@@ -181,7 +183,7 @@ class RedisManager(PubSubManager):
181
183
  logger.error('Cannot receive from redis... '
182
184
  f'retrying in {retry_sleep} secs',
183
185
  extra={"redis_exception": str(exc)})
184
- connect = True
186
+ subscribed = False
185
187
  time.sleep(retry_sleep)
186
188
  retry_sleep *= 2
187
189
  if retry_sleep > 60:
@@ -189,7 +191,6 @@ class RedisManager(PubSubManager):
189
191
 
190
192
  def _listen(self): # pragma: no cover
191
193
  channel = self.channel.encode('utf-8')
192
- self.pubsub.subscribe(self.channel)
193
194
  for message in self._redis_listen_with_retries():
194
195
  if message['channel'] == channel and \
195
196
  message['type'] == 'message' and 'data' in message:
@@ -543,6 +543,9 @@ class Server(base_server.BaseServer):
543
543
  except exceptions.ConnectionRefusedError as exc:
544
544
  fail_reason = exc.error_args
545
545
  success = False
546
+ except ConnectionRefusedError:
547
+ fail_reason = {"message": "Connection refused by server"}
548
+ success = False
546
549
 
547
550
  if success is False:
548
551
  if self.always_connect:
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  from unittest import mock
3
+ from datetime import datetime, timezone, timedelta
3
4
 
4
5
  import pytest
5
6
 
@@ -8,6 +9,7 @@ from socketio import async_namespace
8
9
  from engineio import exceptions as engineio_exceptions
9
10
  from socketio import exceptions
10
11
  from socketio import packet
12
+ from socketio.msgpack_packet import MsgPackPacket
11
13
 
12
14
 
13
15
  class TestAsyncClient:
@@ -1242,3 +1244,21 @@ class TestAsyncClient:
1242
1244
  assert c.sid is None
1243
1245
  assert not c.connected
1244
1246
  c.start_background_task.assert_not_called()
1247
+
1248
+ def test_serializer_args_with_msgpack(self):
1249
+ def default(o):
1250
+ if isinstance(o, datetime):
1251
+ return o.isoformat()
1252
+ raise TypeError("Unknown type")
1253
+
1254
+ data = {"current": datetime.now(timezone(timedelta(0)))}
1255
+ c = async_client.AsyncClient(
1256
+ serializer=MsgPackPacket.configure(dumps_default=default))
1257
+ p = c.packet_class(data=data)
1258
+ p2 = c.packet_class(encoded_packet=p.encode())
1259
+
1260
+ assert p.data != p2.data
1261
+ assert isinstance(p2.data, dict)
1262
+ assert "current" in p2.data
1263
+ assert isinstance(p2.data["current"], str)
1264
+ assert default(data["current"]) == p2.data["current"]
@@ -97,6 +97,36 @@ class TestAsyncPubSubManager:
97
97
  }
98
98
  )
99
99
 
100
+ async def test_emit_bytearray(self):
101
+ await self.pm.emit('foo', bytearray(b'bar'))
102
+ self.pm._publish.assert_awaited_once_with(
103
+ {
104
+ 'method': 'emit',
105
+ 'event': 'foo',
106
+ 'binary': True,
107
+ 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
108
+ 'namespace': '/',
109
+ 'room': None,
110
+ 'skip_sid': None,
111
+ 'callback': None,
112
+ 'host_id': '123456',
113
+ }
114
+ )
115
+ await self.pm.emit('foo', {'foo': bytearray(b'bar')})
116
+ self.pm._publish.assert_awaited_with(
117
+ {
118
+ 'method': 'emit',
119
+ 'event': 'foo',
120
+ 'binary': True,
121
+ 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
122
+ 'namespace': '/',
123
+ 'room': None,
124
+ 'skip_sid': None,
125
+ 'callback': None,
126
+ 'host_id': '123456',
127
+ }
128
+ )
129
+
100
130
  async def test_emit_with_to(self):
101
131
  sid = 'room-mate'
102
132
  await self.pm.emit('foo', 'bar', to=sid)
@@ -12,7 +12,7 @@ class TestAsyncRedisManager:
12
12
  async_redis_manager.aioredis = None
13
13
 
14
14
  with pytest.raises(RuntimeError):
15
- AsyncRedisManager('redis://')
15
+ AsyncRedisManager('redis://')._redis_connect()
16
16
  assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None
17
17
 
18
18
  async_redis_manager.aioredis = saved_redis
@@ -22,7 +22,7 @@ class TestAsyncRedisManager:
22
22
  async_redis_manager.aiovalkey = None
23
23
 
24
24
  with pytest.raises(RuntimeError):
25
- AsyncRedisManager('valkey://')
25
+ AsyncRedisManager('valkey://')._redis_connect()
26
26
  assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None
27
27
 
28
28
  async_redis_manager.aiovalkey = saved_valkey
@@ -34,18 +34,18 @@ class TestAsyncRedisManager:
34
34
  async_redis_manager.aiovalkey = None
35
35
 
36
36
  with pytest.raises(RuntimeError):
37
- AsyncRedisManager('redis://')
37
+ AsyncRedisManager('redis://')._redis_connect()
38
38
  with pytest.raises(RuntimeError):
39
- AsyncRedisManager('valkey://')
39
+ AsyncRedisManager('valkey://')._redis_connect()
40
40
  with pytest.raises(RuntimeError):
41
- AsyncRedisManager('unix:///var/sock/redis.sock')
41
+ AsyncRedisManager('unix:///var/sock/redis.sock')._redis_connect()
42
42
 
43
43
  async_redis_manager.aioredis = saved_redis
44
44
  async_redis_manager.aiovalkey = saved_valkey
45
45
 
46
46
  def test_bad_url(self):
47
47
  with pytest.raises(ValueError):
48
- AsyncRedisManager('http://localhost:6379')
48
+ AsyncRedisManager('http://localhost:6379')._redis_connect()
49
49
 
50
50
  def test_redis_connect(self):
51
51
  urls = [
@@ -72,6 +72,8 @@ class TestAsyncRedisManager:
72
72
  ]
73
73
  for url in urls:
74
74
  c = AsyncRedisManager(url)
75
+ assert c.redis is None
76
+ c._redis_connect()
75
77
  assert isinstance(c.redis, redis.asyncio.Redis)
76
78
 
77
79
  def test_valkey_connect(self):
@@ -102,6 +104,8 @@ class TestAsyncRedisManager:
102
104
  ]
103
105
  for url in urls:
104
106
  c = AsyncRedisManager(url)
107
+ assert c.redis is None
108
+ c._redis_connect()
105
109
  assert isinstance(c.redis, valkey.asyncio.Valkey)
106
110
 
107
111
  async_redis_manager.aioredis = saved_redis
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from unittest import mock
4
+ from datetime import datetime, timezone, timedelta
4
5
 
5
6
  from engineio import json
6
7
  from engineio import packet as eio_packet
@@ -11,6 +12,7 @@ from socketio import async_namespace
11
12
  from socketio import exceptions
12
13
  from socketio import namespace
13
14
  from socketio import packet
15
+ from socketio.msgpack_packet import MsgPackPacket
14
16
 
15
17
 
16
18
  @mock.patch('socketio.server.engineio.AsyncServer', **{
@@ -482,6 +484,21 @@ class TestAsyncServer:
482
484
  '123', '4{"message":"fail_reason"}')
483
485
  assert s.environ == {'123': 'environ'}
484
486
 
487
+ async def test_handle_connect_rejected_with_python_exception(self, eio):
488
+ eio.return_value.send = mock.AsyncMock()
489
+ s = async_server.AsyncServer()
490
+ handler = mock.MagicMock(
491
+ side_effect=ConnectionRefusedError()
492
+ )
493
+ s.on('connect', handler)
494
+ await s._handle_eio_connect('123', 'environ')
495
+ await s._handle_eio_message('123', '0')
496
+ assert not s.manager.is_connected('1', '/')
497
+ handler.assert_called_once_with('1', 'environ')
498
+ s.eio.send.assert_awaited_once_with(
499
+ '123', '4{"message":"Connection refused by server"}')
500
+ assert s.environ == {'123': 'environ'}
501
+
485
502
  async def test_handle_connect_rejected_with_empty_exception(self, eio):
486
503
  eio.return_value.send = mock.AsyncMock()
487
504
  s = async_server.AsyncServer()
@@ -1074,3 +1091,21 @@ class TestAsyncServer:
1074
1091
  s = async_server.AsyncServer()
1075
1092
  await s.sleep(1.23)
1076
1093
  s.eio.sleep.assert_awaited_once_with(1.23)
1094
+
1095
+ def test_serializer_args_with_msgpack(self, eio):
1096
+ def default(o):
1097
+ if isinstance(o, datetime):
1098
+ return o.isoformat()
1099
+ raise TypeError("Unknown type")
1100
+
1101
+ data = {"current": datetime.now(timezone(timedelta(0)))}
1102
+ s = async_server.AsyncServer(
1103
+ serializer=MsgPackPacket.configure(dumps_default=default))
1104
+ p = s.packet_class(data=data)
1105
+ p2 = s.packet_class(encoded_packet=p.encode())
1106
+
1107
+ assert p.data != p2.data
1108
+ assert isinstance(p2.data, dict)
1109
+ assert "current" in p2.data
1110
+ assert isinstance(p2.data["current"], str)
1111
+ assert default(data["current"]) == p2.data["current"]
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import time
3
3
  from unittest import mock
4
+ from datetime import datetime, timezone, timedelta
4
5
 
5
6
  from engineio import exceptions as engineio_exceptions
6
7
  from engineio import json
@@ -13,6 +14,7 @@ from socketio import exceptions
13
14
  from socketio import msgpack_packet
14
15
  from socketio import namespace
15
16
  from socketio import packet
17
+ from socketio.msgpack_packet import MsgPackPacket
16
18
 
17
19
 
18
20
  class TestClient:
@@ -1386,3 +1388,21 @@ class TestClient:
1386
1388
  assert c.sid is None
1387
1389
  assert not c.connected
1388
1390
  c.start_background_task.assert_not_called()
1391
+
1392
+ def test_serializer_args_with_msgpack(self):
1393
+ def default(o):
1394
+ if isinstance(o, datetime):
1395
+ return o.isoformat()
1396
+ raise TypeError("Unknown type")
1397
+
1398
+ data = {"current": datetime.now(timezone(timedelta(0)))}
1399
+ c = client.Client(
1400
+ serializer=MsgPackPacket.configure(dumps_default=default))
1401
+ p = c.packet_class(data=data)
1402
+ p2 = c.packet_class(encoded_packet=p.encode())
1403
+
1404
+ assert p.data != p2.data
1405
+ assert isinstance(p2.data, dict)
1406
+ assert "current" in p2.data
1407
+ assert isinstance(p2.data["current"], str)
1408
+ assert default(data["current"]) == p2.data["current"]
@@ -0,0 +1,138 @@
1
+ from datetime import datetime, timedelta, timezone
2
+
3
+ import pytest
4
+ import msgpack
5
+
6
+ from socketio import msgpack_packet
7
+ from socketio import packet
8
+
9
+
10
+ class TestMsgPackPacket:
11
+ def test_encode_decode(self):
12
+ p = msgpack_packet.MsgPackPacket(
13
+ packet.CONNECT, data={'auth': {'token': '123'}}, namespace='/foo')
14
+ p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
15
+ assert p.packet_type == p2.packet_type
16
+ assert p.data == p2.data
17
+ assert p.id == p2.id
18
+ assert p.namespace == p2.namespace
19
+
20
+ def test_encode_decode_with_id(self):
21
+ p = msgpack_packet.MsgPackPacket(
22
+ packet.EVENT, data=['ev', 42], id=123, namespace='/foo')
23
+ p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
24
+ assert p.packet_type == p2.packet_type
25
+ assert p.data == p2.data
26
+ assert p.id == p2.id
27
+ assert p.namespace == p2.namespace
28
+
29
+ def test_encode_binary_event_packet(self):
30
+ p = msgpack_packet.MsgPackPacket(packet.EVENT, data={'foo': b'bar'})
31
+ assert p.packet_type == packet.EVENT
32
+ p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
33
+ assert p2.data == {'foo': b'bar'}
34
+
35
+ def test_encode_binary_ack_packet(self):
36
+ p = msgpack_packet.MsgPackPacket(packet.ACK, data={'foo': b'bar'})
37
+ assert p.packet_type == packet.ACK
38
+ p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
39
+ assert p2.data == {'foo': b'bar'}
40
+
41
+ def test_encode_with_dumps_default(self):
42
+ def default(obj):
43
+ if isinstance(obj, datetime):
44
+ return obj.isoformat()
45
+ raise TypeError('Unknown type')
46
+
47
+ data = {
48
+ 'current': datetime.now(tz=timezone(timedelta(0))),
49
+ 'key': 'value',
50
+ }
51
+ p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
52
+ data=data)
53
+ p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
54
+ assert p.packet_type == p2.packet_type
55
+ assert p.id == p2.id
56
+ assert p.namespace == p2.namespace
57
+ assert p.data != p2.data
58
+
59
+ assert isinstance(p2.data, dict)
60
+ assert 'current' in p2.data
61
+ assert isinstance(p2.data['current'], str)
62
+ assert default(data['current']) == p2.data['current']
63
+
64
+ data.pop('current')
65
+ p2_data_without_current = p2.data.copy()
66
+ p2_data_without_current.pop('current')
67
+ assert data == p2_data_without_current
68
+
69
+ def test_encode_without_dumps_default(self):
70
+ data = {
71
+ 'current': datetime.now(tz=timezone(timedelta(0))),
72
+ 'key': 'value',
73
+ }
74
+ p_without_default = msgpack_packet.MsgPackPacket(data=data)
75
+ with pytest.raises(TypeError):
76
+ p_without_default.encode()
77
+
78
+ def test_encode_decode_with_ext_hook(self):
79
+ class Custom:
80
+ def __init__(self, value):
81
+ self.value = value
82
+
83
+ def __eq__(self, value: object) -> bool:
84
+ return isinstance(value, Custom) and self.value == value.value
85
+
86
+ def default(obj):
87
+ if isinstance(obj, Custom):
88
+ return msgpack.ExtType(1, obj.value)
89
+ raise TypeError('Unknown type')
90
+
91
+ def ext_hook(code, data):
92
+ if code == 1:
93
+ return Custom(data)
94
+ raise TypeError('Unknown ext type')
95
+
96
+ data = {'custom': Custom(b'custom_data'), 'key': 'value'}
97
+ p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
98
+ data=data)
99
+ p2 = msgpack_packet.MsgPackPacket.configure(ext_hook=ext_hook)(
100
+ encoded_packet=p.encode()
101
+ )
102
+ assert p.packet_type == p2.packet_type
103
+ assert p.id == p2.id
104
+ assert p.data == p2.data
105
+ assert p.namespace == p2.namespace
106
+
107
+ def test_encode_decode_without_ext_hook(self):
108
+ class Custom:
109
+ def __init__(self, value):
110
+ self.value = value
111
+
112
+ def __eq__(self, value: object) -> bool:
113
+ return isinstance(value, Custom) and self.value == value.value
114
+
115
+ def default(obj):
116
+ if isinstance(obj, Custom):
117
+ return msgpack.ExtType(1, obj.value)
118
+ raise TypeError('Unknown type')
119
+
120
+ data = {'custom': Custom(b'custom_data'), 'key': 'value'}
121
+ p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
122
+ data=data)
123
+ p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
124
+ assert p.packet_type == p2.packet_type
125
+ assert p.id == p2.id
126
+ assert p.namespace == p2.namespace
127
+ assert p.data != p2.data
128
+
129
+ assert isinstance(p2.data, dict)
130
+ assert 'custom' in p2.data
131
+ assert isinstance(p2.data['custom'], msgpack.ExtType)
132
+ assert p2.data['custom'].code == 1
133
+ assert p2.data['custom'].data == b'custom_data'
134
+
135
+ data.pop('custom')
136
+ p2_data_without_custom = p2.data.copy()
137
+ p2_data_without_custom.pop('custom')
138
+ assert data == p2_data_without_custom
@@ -279,11 +279,15 @@ class TestPacket:
279
279
  assert not pkt.data_is_binary(['foo'])
280
280
  assert not pkt.data_is_binary([])
281
281
  assert pkt.data_is_binary([b'foo'])
282
+ assert pkt.data_is_binary([bytearray(b'foo')])
282
283
  assert pkt.data_is_binary(['foo', b'bar'])
284
+ assert pkt.data_is_binary(['foo', bytearray(b'bar')])
283
285
 
284
286
  def test_data_is_binary_dict(self):
285
287
  pkt = packet.Packet()
286
288
  assert not pkt.data_is_binary({'a': 'foo'})
287
289
  assert not pkt.data_is_binary({})
288
290
  assert pkt.data_is_binary({'a': b'foo'})
291
+ assert pkt.data_is_binary({'a': bytearray(b'foo')})
289
292
  assert pkt.data_is_binary({'a': 'foo', 'b': b'bar'})
293
+ assert pkt.data_is_binary({'a': 'foo', 'b': bytearray(b'bar')})
@@ -109,6 +109,36 @@ class TestPubSubManager:
109
109
  }
110
110
  )
111
111
 
112
+ def test_emit_bytearray(self):
113
+ self.pm.emit('foo', bytearray(b'bar'))
114
+ self.pm._publish.assert_called_once_with(
115
+ {
116
+ 'method': 'emit',
117
+ 'event': 'foo',
118
+ 'binary': True,
119
+ 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
120
+ 'namespace': '/',
121
+ 'room': None,
122
+ 'skip_sid': None,
123
+ 'callback': None,
124
+ 'host_id': '123456',
125
+ }
126
+ )
127
+ self.pm.emit('foo', {'foo': bytearray(b'bar')})
128
+ self.pm._publish.assert_called_with(
129
+ {
130
+ 'method': 'emit',
131
+ 'event': 'foo',
132
+ 'binary': True,
133
+ 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
134
+ 'namespace': '/',
135
+ 'room': None,
136
+ 'skip_sid': None,
137
+ 'callback': None,
138
+ 'host_id': '123456',
139
+ }
140
+ )
141
+
112
142
  def test_emit_with_to(self):
113
143
  sid = "ferris"
114
144
  self.pm.emit('foo', 'bar', to=sid)
@@ -12,7 +12,7 @@ class TestPubSubManager:
12
12
  redis_manager.redis = None
13
13
 
14
14
  with pytest.raises(RuntimeError):
15
- RedisManager('redis://')
15
+ RedisManager('redis://')._redis_connect()
16
16
  assert RedisManager('unix:///var/sock/redis.sock') is not None
17
17
 
18
18
  redis_manager.redis = saved_redis
@@ -22,7 +22,7 @@ class TestPubSubManager:
22
22
  redis_manager.valkey = None
23
23
 
24
24
  with pytest.raises(RuntimeError):
25
- RedisManager('valkey://')
25
+ RedisManager('valkey://')._redis_connect()
26
26
  assert RedisManager('unix:///var/sock/redis.sock') is not None
27
27
 
28
28
  redis_manager.valkey = saved_valkey
@@ -34,18 +34,18 @@ class TestPubSubManager:
34
34
  redis_manager.valkey = None
35
35
 
36
36
  with pytest.raises(RuntimeError):
37
- RedisManager('redis://')
37
+ RedisManager('redis://')._redis_connect()
38
38
  with pytest.raises(RuntimeError):
39
- RedisManager('valkey://')
39
+ RedisManager('valkey://')._redis_connect()
40
40
  with pytest.raises(RuntimeError):
41
- RedisManager('unix:///var/sock/redis.sock')
41
+ RedisManager('unix:///var/sock/redis.sock')._redis_connect()
42
42
 
43
43
  redis_manager.redis = saved_redis
44
44
  redis_manager.valkey = saved_valkey
45
45
 
46
46
  def test_bad_url(self):
47
47
  with pytest.raises(ValueError):
48
- RedisManager('http://localhost:6379')
48
+ RedisManager('http://localhost:6379')._redis_connect()
49
49
 
50
50
  def test_redis_connect(self):
51
51
  urls = [
@@ -72,6 +72,8 @@ class TestPubSubManager:
72
72
  ]
73
73
  for url in urls:
74
74
  c = RedisManager(url)
75
+ assert c.redis is None
76
+ c._redis_connect()
75
77
  assert isinstance(c.redis, redis.Redis)
76
78
 
77
79
  def test_valkey_connect(self):
@@ -102,6 +104,8 @@ class TestPubSubManager:
102
104
  ]
103
105
  for url in urls:
104
106
  c = RedisManager(url)
107
+ assert c.redis is None
108
+ c._redis_connect()
105
109
  assert isinstance(c.redis, valkey.Valkey)
106
110
 
107
111
  redis_manager.redis = saved_redis
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from unittest import mock
3
+ from datetime import datetime, timezone, timedelta
3
4
 
4
5
  from engineio import json
5
6
  from engineio import packet as eio_packet
@@ -10,6 +11,7 @@ from socketio import msgpack_packet
10
11
  from socketio import namespace
11
12
  from socketio import packet
12
13
  from socketio import server
14
+ from socketio.msgpack_packet import MsgPackPacket
13
15
 
14
16
 
15
17
  @mock.patch('socketio.server.engineio.Server', **{
@@ -462,6 +464,20 @@ class TestServer:
462
464
  s.eio.send.assert_called_once_with('123', '4{"message":"fail_reason"}')
463
465
  assert s.environ == {'123': 'environ'}
464
466
 
467
+ def test_handle_connect_rejected_with_python_exception(self, eio):
468
+ s = server.Server()
469
+ handler = mock.MagicMock(
470
+ side_effect=ConnectionRefusedError()
471
+ )
472
+ s.on('connect', handler)
473
+ s._handle_eio_connect('123', 'environ')
474
+ s._handle_eio_message('123', '0')
475
+ assert not s.manager.is_connected('1', '/')
476
+ handler.assert_called_once_with('1', 'environ')
477
+ s.eio.send.assert_called_once_with(
478
+ '123', '4{"message":"Connection refused by server"}')
479
+ assert s.environ == {'123': 'environ'}
480
+
465
481
  def test_handle_connect_rejected_with_empty_exception(self, eio):
466
482
  s = server.Server()
467
483
  handler = mock.MagicMock(
@@ -1018,3 +1034,21 @@ class TestServer:
1018
1034
  s = server.Server()
1019
1035
  s.sleep(1.23)
1020
1036
  s.eio.sleep.assert_called_once_with(1.23)
1037
+
1038
+ def test_serializer_args_with_msgpack(self, eio):
1039
+ def default(o):
1040
+ if isinstance(o, datetime):
1041
+ return o.isoformat()
1042
+ raise TypeError("Unknown type")
1043
+
1044
+ data = {"current": datetime.now(timezone(timedelta(0)))}
1045
+ s = server.Server(
1046
+ serializer=MsgPackPacket.configure(dumps_default=default))
1047
+ p = s.packet_class(data=data)
1048
+ p2 = s.packet_class(encoded_packet=p.encode())
1049
+
1050
+ assert p.data != p2.data
1051
+ assert isinstance(p2.data, dict)
1052
+ assert "current" in p2.data
1053
+ assert isinstance(p2.data["current"], str)
1054
+ assert default(data["current"]) == p2.data["current"]
@@ -1,18 +0,0 @@
1
- import msgpack
2
- from . import packet
3
-
4
-
5
- class MsgPackPacket(packet.Packet):
6
- uses_binary_events = False
7
-
8
- def encode(self):
9
- """Encode the packet for transmission."""
10
- return msgpack.dumps(self._to_dict())
11
-
12
- def decode(self, encoded_packet):
13
- """Decode a transmitted package."""
14
- decoded = msgpack.loads(encoded_packet)
15
- self.packet_type = decoded['type']
16
- self.data = decoded.get('data')
17
- self.id = decoded.get('id')
18
- self.namespace = decoded['nsp']
@@ -1,34 +0,0 @@
1
- from socketio import msgpack_packet
2
- from socketio import packet
3
-
4
-
5
- class TestMsgPackPacket:
6
- def test_encode_decode(self):
7
- p = msgpack_packet.MsgPackPacket(
8
- packet.CONNECT, data={'auth': {'token': '123'}}, namespace='/foo')
9
- p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
10
- assert p.packet_type == p2.packet_type
11
- assert p.data == p2.data
12
- assert p.id == p2.id
13
- assert p.namespace == p2.namespace
14
-
15
- def test_encode_decode_with_id(self):
16
- p = msgpack_packet.MsgPackPacket(
17
- packet.EVENT, data=['ev', 42], id=123, namespace='/foo')
18
- p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
19
- assert p.packet_type == p2.packet_type
20
- assert p.data == p2.data
21
- assert p.id == p2.id
22
- assert p.namespace == p2.namespace
23
-
24
- def test_encode_binary_event_packet(self):
25
- p = msgpack_packet.MsgPackPacket(packet.EVENT, data={'foo': b'bar'})
26
- assert p.packet_type == packet.EVENT
27
- p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
28
- assert p2.data == {'foo': b'bar'}
29
-
30
- def test_encode_binary_ack_packet(self):
31
- p = msgpack_packet.MsgPackPacket(packet.ACK, data={'foo': b'bar'})
32
- assert p.packet_type == packet.ACK
33
- p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
34
- assert p2.data == {'foo': b'bar'}