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.
- {python_socketio-5.14.2/src/python_socketio.egg-info → python_socketio-5.15.0}/PKG-INFO +1 -1
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/client.rst +8 -3
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/server.rst +10 -3
- {python_socketio-5.14.2 → python_socketio-5.15.0}/pyproject.toml +1 -1
- {python_socketio-5.14.2 → python_socketio-5.15.0/src/python_socketio.egg-info}/PKG-INFO +1 -1
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_aiopika_manager.py +19 -19
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_client.py +1 -1
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_redis_manager.py +12 -11
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_server.py +3 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/kombu_manager.py +1 -1
- python_socketio-5.15.0/src/socketio/msgpack_packet.py +44 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/packet.py +2 -2
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/redis_manager.py +12 -11
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/server.py +3 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_client.py +20 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_pubsub_manager.py +30 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_redis_manager.py +10 -6
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_server.py +35 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_client.py +20 -0
- python_socketio-5.15.0/tests/common/test_msgpack_packet.py +138 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_packet.py +4 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_pubsub_manager.py +30 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_redis_manager.py +10 -6
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_server.py +34 -0
- python_socketio-5.14.2/src/socketio/msgpack_packet.py +0 -18
- python_socketio-5.14.2/tests/common/test_msgpack_packet.py +0 -34
- {python_socketio-5.14.2 → python_socketio-5.15.0}/LICENSE +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/MANIFEST.in +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/README.md +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/Makefile +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/_static/README.md +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/_static/custom.css +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/api.rst +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/conf.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/index.rst +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/intro.rst +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/docs/make.bat +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/setup.cfg +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/SOURCES.txt +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/dependency_links.txt +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/not-zip-safe +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/requires.txt +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/top_level.txt +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/__init__.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/admin.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/asgi.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_admin.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_namespace.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_pubsub_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/async_simple_client.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_client.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_namespace.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/base_server.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/client.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/exceptions.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/kafka_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/middleware.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/namespace.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/pubsub_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/simple_client.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/tornado.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/src/socketio/zmq_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/__init__.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/__init__.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_admin.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_namespace.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/async/test_simple_client.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/asyncio_web_server.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/__init__.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_admin.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_manager.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_middleware.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_namespace.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/common/test_simple_client.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/README.md +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/binary_packet.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/json_packet.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/namespace_packet.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/run.sh +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_receive.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_send.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_send_broadcast.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/text_packet.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tests/web_server.py +0 -0
- {python_socketio-5.14.2 → python_socketio-5.15.0}/tox.ini +0 -0
|
@@ -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.
|
|
202
|
-
|
|
203
|
-
|
|
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.
|
|
659
|
-
|
|
660
|
-
|
|
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.
|
|
@@ -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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
115
|
+
for retries_left in range(1, -1, -1): # 2 attempts
|
|
114
116
|
try:
|
|
115
|
-
if not
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
152
|
+
for retries_left in range(1, -1, -1): # 2 attempts
|
|
151
153
|
try:
|
|
152
|
-
if not
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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'}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_socketio-5.14.2 → python_socketio-5.15.0}/src/python_socketio.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_socketio-5.14.2 → python_socketio-5.15.0}/tests/performance/server_send_broadcast.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|