python-socketio 5.12.0__tar.gz → 5.13.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 (86) hide show
  1. {python_socketio-5.12.0/src/python_socketio.egg-info → python_socketio-5.13.0}/PKG-INFO +3 -2
  2. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/intro.rst +1 -1
  3. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/server.rst +1 -7
  4. {python_socketio-5.12.0 → python_socketio-5.13.0}/pyproject.toml +1 -1
  5. {python_socketio-5.12.0 → python_socketio-5.13.0/src/python_socketio.egg-info}/PKG-INFO +3 -2
  6. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/python_socketio.egg-info/SOURCES.txt +1 -0
  7. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/admin.py +40 -54
  8. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_admin.py +40 -54
  9. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_client.py +3 -2
  10. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_redis_manager.py +17 -5
  11. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_simple_client.py +4 -1
  12. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/client.py +2 -2
  13. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/redis_manager.py +43 -5
  14. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/simple_client.py +4 -1
  15. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_client.py +2 -2
  16. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_simple_client.py +33 -28
  17. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_client.py +2 -2
  18. python_socketio-5.13.0/tests/common/test_redis_manager.py +38 -0
  19. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_simple_client.py +27 -17
  20. {python_socketio-5.12.0 → python_socketio-5.13.0}/LICENSE +0 -0
  21. {python_socketio-5.12.0 → python_socketio-5.13.0}/MANIFEST.in +0 -0
  22. {python_socketio-5.12.0 → python_socketio-5.13.0}/README.md +0 -0
  23. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/Makefile +0 -0
  24. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/_static/README.md +0 -0
  25. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/_static/custom.css +0 -0
  26. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/api.rst +0 -0
  27. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/client.rst +0 -0
  28. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/conf.py +0 -0
  29. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/index.rst +0 -0
  30. {python_socketio-5.12.0 → python_socketio-5.13.0}/docs/make.bat +0 -0
  31. {python_socketio-5.12.0 → python_socketio-5.13.0}/setup.cfg +0 -0
  32. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/python_socketio.egg-info/dependency_links.txt +0 -0
  33. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/python_socketio.egg-info/not-zip-safe +0 -0
  34. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/python_socketio.egg-info/requires.txt +0 -0
  35. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/python_socketio.egg-info/top_level.txt +0 -0
  36. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/__init__.py +0 -0
  37. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/asgi.py +0 -0
  38. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_aiopika_manager.py +0 -0
  39. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_manager.py +0 -0
  40. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_namespace.py +0 -0
  41. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_pubsub_manager.py +0 -0
  42. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/async_server.py +0 -0
  43. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/base_client.py +0 -0
  44. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/base_manager.py +0 -0
  45. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/base_namespace.py +0 -0
  46. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/base_server.py +0 -0
  47. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/exceptions.py +0 -0
  48. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/kafka_manager.py +0 -0
  49. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/kombu_manager.py +0 -0
  50. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/manager.py +0 -0
  51. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/middleware.py +0 -0
  52. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/msgpack_packet.py +0 -0
  53. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/namespace.py +0 -0
  54. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/packet.py +0 -0
  55. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/pubsub_manager.py +0 -0
  56. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/server.py +0 -0
  57. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/tornado.py +0 -0
  58. {python_socketio-5.12.0 → python_socketio-5.13.0}/src/socketio/zmq_manager.py +0 -0
  59. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/__init__.py +0 -0
  60. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/__init__.py +0 -0
  61. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_admin.py +0 -0
  62. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_manager.py +0 -0
  63. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_namespace.py +0 -0
  64. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_pubsub_manager.py +0 -0
  65. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/async/test_server.py +0 -0
  66. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/asyncio_web_server.py +0 -0
  67. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/__init__.py +0 -0
  68. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_admin.py +0 -0
  69. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_manager.py +0 -0
  70. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_middleware.py +0 -0
  71. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_msgpack_packet.py +0 -0
  72. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_namespace.py +0 -0
  73. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_packet.py +0 -0
  74. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_pubsub_manager.py +0 -0
  75. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/common/test_server.py +0 -0
  76. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/README.md +0 -0
  77. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/binary_packet.py +0 -0
  78. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/json_packet.py +0 -0
  79. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/namespace_packet.py +0 -0
  80. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/run.sh +0 -0
  81. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/server_receive.py +0 -0
  82. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/server_send.py +0 -0
  83. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/server_send_broadcast.py +0 -0
  84. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/performance/text_packet.py +0 -0
  85. {python_socketio-5.12.0 → python_socketio-5.13.0}/tests/web_server.py +0 -0
  86. {python_socketio-5.12.0 → python_socketio-5.13.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-socketio
3
- Version: 5.12.0
3
+ Version: 5.13.0
4
4
  Summary: Socket.IO server and client for Python
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/miguelgrinberg/python-socketio
@@ -22,6 +22,7 @@ Provides-Extra: asyncio-client
22
22
  Requires-Dist: aiohttp>=3.4; extra == "asyncio-client"
23
23
  Provides-Extra: docs
24
24
  Requires-Dist: sphinx; extra == "docs"
25
+ Dynamic: license-file
25
26
 
26
27
  python-socketio
27
28
  ===============
@@ -189,7 +189,7 @@ Server Features
189
189
  - Can be hosted on any `WSGI <https://wsgi.readthedocs.io/en/latest/index.html>`_ or
190
190
  `ASGI <https://asgi.readthedocs.io/en/latest/>`_ web server including
191
191
  `Gunicorn <https://gunicorn.org/>`_, `Uvicorn <https://github.com/encode/uvicorn>`_,
192
- `eventlet <http://eventlet.net/>`_ and `gevent <http://gevent.org>`_.
192
+ `eventlet <http://eventlet.net/>`_ and `gevent <http://www.gevent.org>`_.
193
193
  - Can be integrated with WSGI applications written in frameworks such as Flask, Django,
194
194
  etc.
195
195
  - Can be integrated with `aiohttp <http://aiohttp.readthedocs.io/>`_,
@@ -19,12 +19,6 @@ command::
19
19
 
20
20
  pip install python-socketio
21
21
 
22
- If you plan to build an asynchronous web server based on the ``asyncio``
23
- package, then you can install this package and some additional dependencies
24
- that are needed with::
25
-
26
- pip install "python-socketio[asyncio]"
27
-
28
22
  Creating a Server Instance
29
23
  --------------------------
30
24
 
@@ -838,7 +832,7 @@ Gevent
838
832
 
839
833
  When a multi-threaded web server is unable to satisfy the concurrency and
840
834
  scalability requirements of the application, an option to try is
841
- `Gevent <http://gevent.org>`_. Gevent is a coroutine-based concurrency library
835
+ `Gevent <http://www.gevent.org>`_. Gevent is a coroutine-based concurrency library
842
836
  based on greenlets, which are significantly lighter than threads.
843
837
 
844
838
  Instances of class ``socketio.Server`` will automatically use Gevent if the
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-socketio"
3
- version = "5.12.0"
3
+ version = "5.13.0"
4
4
  authors = [
5
5
  { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },
6
6
  ]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: python-socketio
3
- Version: 5.12.0
3
+ Version: 5.13.0
4
4
  Summary: Socket.IO server and client for Python
5
5
  Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/miguelgrinberg/python-socketio
@@ -22,6 +22,7 @@ Provides-Extra: asyncio-client
22
22
  Requires-Dist: aiohttp>=3.4; extra == "asyncio-client"
23
23
  Provides-Extra: docs
24
24
  Requires-Dist: sphinx; extra == "docs"
25
+ Dynamic: license-file
25
26
 
26
27
  python-socketio
27
28
  ===============
@@ -70,6 +70,7 @@ tests/common/test_msgpack_packet.py
70
70
  tests/common/test_namespace.py
71
71
  tests/common/test_packet.py
72
72
  tests/common/test_pubsub_manager.py
73
+ tests/common/test_redis_manager.py
73
74
  tests/common/test_server.py
74
75
  tests/common/test_simple_client.py
75
76
  tests/performance/README.md
@@ -1,4 +1,4 @@
1
- from datetime import datetime
1
+ from datetime import datetime, timezone
2
2
  import functools
3
3
  import os
4
4
  import socket
@@ -77,13 +77,9 @@ class InstrumentedServer:
77
77
  # track socket connection times
78
78
  self.sio.manager._timestamps = {}
79
79
 
80
- # report socket.io connections
81
- self.sio.manager.__connect = self.sio.manager.connect
82
- self.sio.manager.connect = self._connect
83
-
84
- # report socket.io disconnection
85
- self.sio.manager.__disconnect = self.sio.manager.disconnect
86
- self.sio.manager.disconnect = self._disconnect
80
+ # report socket.io connections, disconnections and received events
81
+ self.sio.__trigger_event = self.sio._trigger_event
82
+ self.sio._trigger_event = self._trigger_event
87
83
 
88
84
  # report join rooms
89
85
  self.sio.manager.__basic_enter_room = \
@@ -99,10 +95,6 @@ class InstrumentedServer:
99
95
  self.sio.manager.__emit = self.sio.manager.emit
100
96
  self.sio.manager.emit = self._emit
101
97
 
102
- # report receive events
103
- self.sio.__handle_event_internal = self.sio._handle_event_internal
104
- self.sio._handle_event_internal = self._handle_event_internal
105
-
106
98
  # report engine.io connections
107
99
  self.sio.eio.on('connect', self._handle_eio_connect)
108
100
  self.sio.eio.on('disconnect', self._handle_eio_disconnect)
@@ -128,14 +120,12 @@ class InstrumentedServer:
128
120
 
129
121
  def uninstrument(self): # pragma: no cover
130
122
  if self.mode == 'development':
131
- self.sio.manager.connect = self.sio.manager.__connect
132
- self.sio.manager.disconnect = self.sio.manager.__disconnect
123
+ self.sio._trigger_event = self.sio.__trigger_event
133
124
  self.sio.manager.basic_enter_room = \
134
125
  self.sio.manager.__basic_enter_room
135
126
  self.sio.manager.basic_leave_room = \
136
127
  self.sio.manager.__basic_leave_room
137
128
  self.sio.manager.emit = self.sio.manager.__emit
138
- self.sio._handle_event_internal = self.sio.__handle_event_internal
139
129
  self.sio.eio._ok = self.sio.eio.__ok
140
130
 
141
131
  from engineio.socket import Socket
@@ -205,26 +195,34 @@ class InstrumentedServer:
205
195
  self.stop_stats_event.set()
206
196
  self.stats_task.join()
207
197
 
208
- def _connect(self, eio_sid, namespace):
209
- sid = self.sio.manager.__connect(eio_sid, namespace)
198
+ def _trigger_event(self, event, namespace, *args):
210
199
  t = time.time()
211
- self.sio.manager._timestamps[sid] = t
212
- serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
213
- self.sio.emit('socket_connected', (
214
- serialized_socket,
215
- datetime.utcfromtimestamp(t).isoformat() + 'Z',
216
- ), namespace=self.admin_namespace)
217
- return sid
218
-
219
- def _disconnect(self, sid, namespace, **kwargs):
220
- del self.sio.manager._timestamps[sid]
221
- self.sio.emit('socket_disconnected', (
222
- namespace,
223
- sid,
224
- 'N/A',
225
- datetime.utcnow().isoformat() + 'Z',
226
- ), namespace=self.admin_namespace)
227
- return self.sio.manager.__disconnect(sid, namespace, **kwargs)
200
+ sid = args[0]
201
+ if event == 'connect':
202
+ eio_sid = self.sio.manager.eio_sid_from_sid(sid, namespace)
203
+ self.sio.manager._timestamps[sid] = t
204
+ serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
205
+ self.sio.emit('socket_connected', (
206
+ serialized_socket,
207
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
208
+ ), namespace=self.admin_namespace)
209
+ elif event == 'disconnect':
210
+ del self.sio.manager._timestamps[sid]
211
+ reason = args[1]
212
+ self.sio.emit('socket_disconnected', (
213
+ namespace,
214
+ sid,
215
+ reason,
216
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
217
+ ), namespace=self.admin_namespace)
218
+ else:
219
+ self.sio.emit('event_received', (
220
+ namespace,
221
+ sid,
222
+ (event, *args[1:]),
223
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
224
+ ), namespace=self.admin_namespace)
225
+ return self.sio.__trigger_event(event, namespace, *args)
228
226
 
229
227
  def _check_for_upgrade(self, eio_sid, sid, namespace): # pragma: no cover
230
228
  for _ in range(5):
@@ -248,7 +246,7 @@ class InstrumentedServer:
248
246
  namespace,
249
247
  room,
250
248
  sid,
251
- datetime.utcnow().isoformat() + 'Z',
249
+ datetime.now(timezone.utc).isoformat(),
252
250
  ), namespace=self.admin_namespace)
253
251
  return ret
254
252
 
@@ -258,7 +256,7 @@ class InstrumentedServer:
258
256
  namespace,
259
257
  room,
260
258
  sid,
261
- datetime.utcnow().isoformat() + 'Z',
259
+ datetime.now(timezone.utc).isoformat(),
262
260
  ), namespace=self.admin_namespace)
263
261
  return self.sio.manager.__basic_leave_room(sid, namespace, room)
264
262
 
@@ -269,7 +267,7 @@ class InstrumentedServer:
269
267
  **kwargs)
270
268
  if namespace != self.admin_namespace:
271
269
  event_data = [event] + list(data) if isinstance(data, tuple) \
272
- else [data]
270
+ else [event, data]
273
271
  if not isinstance(skip_sid, list): # pragma: no branch
274
272
  skip_sid = [skip_sid]
275
273
  for sid, _ in self.sio.manager.get_participants(namespace, room):
@@ -278,22 +276,10 @@ class InstrumentedServer:
278
276
  namespace,
279
277
  sid,
280
278
  event_data,
281
- datetime.utcnow().isoformat() + 'Z',
279
+ datetime.now(timezone.utc).isoformat(),
282
280
  ), namespace=self.admin_namespace)
283
281
  return ret
284
282
 
285
- def _handle_event_internal(self, server, sid, eio_sid, data, namespace,
286
- id):
287
- ret = self.sio.__handle_event_internal(server, sid, eio_sid, data,
288
- namespace, id)
289
- self.sio.emit('event_received', (
290
- namespace,
291
- sid,
292
- data,
293
- datetime.utcnow().isoformat() + 'Z',
294
- ), namespace=self.admin_namespace)
295
- return ret
296
-
297
283
  def _handle_eio_connect(self, eio_sid, environ):
298
284
  if self.stop_stats_event is None:
299
285
  self.stop_stats_event = self.sio.eio.create_event()
@@ -303,9 +289,9 @@ class InstrumentedServer:
303
289
  self.event_buffer.push('rawConnection')
304
290
  return self.sio._handle_eio_connect(eio_sid, environ)
305
291
 
306
- def _handle_eio_disconnect(self, eio_sid):
292
+ def _handle_eio_disconnect(self, eio_sid, reason):
307
293
  self.event_buffer.push('rawDisconnection')
308
- return self.sio._handle_eio_disconnect(eio_sid)
294
+ return self.sio._handle_eio_disconnect(eio_sid, reason)
309
295
 
310
296
  def _eio_http_response(self, packets=None, headers=None, jsonp_index=None):
311
297
  ret = self.sio.eio.__ok(packets=packets, headers=headers,
@@ -349,7 +335,7 @@ class InstrumentedServer:
349
335
  eio_sid)
350
336
  self.sio.emit('socket_connected', (
351
337
  serialized_socket,
352
- datetime.utcfromtimestamp(t).isoformat() + 'Z',
338
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
353
339
  ), namespace=self.admin_namespace)
354
340
  return socket.__send_ping()
355
341
 
@@ -398,7 +384,7 @@ class InstrumentedServer:
398
384
  'secure': environ.get('wsgi.url_scheme', '') == 'https',
399
385
  'url': environ.get('PATH_INFO', ''),
400
386
  'issued': tm * 1000,
401
- 'time': datetime.utcfromtimestamp(tm).isoformat() + 'Z'
387
+ 'time': datetime.fromtimestamp(tm, timezone.utc).isoformat()
402
388
  if tm else '',
403
389
  },
404
390
  'rooms': self.sio.manager.get_rooms(sid, namespace),
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
3
  import functools
4
4
  import os
5
5
  import socket
@@ -58,13 +58,9 @@ class InstrumentedAsyncServer:
58
58
  # track socket connection times
59
59
  self.sio.manager._timestamps = {}
60
60
 
61
- # report socket.io connections
62
- self.sio.manager.__connect = self.sio.manager.connect
63
- self.sio.manager.connect = self._connect
64
-
65
- # report socket.io disconnection
66
- self.sio.manager.__disconnect = self.sio.manager.disconnect
67
- self.sio.manager.disconnect = self._disconnect
61
+ # report socket.io connections, disconnections and received events
62
+ self.sio.__trigger_event = self.sio._trigger_event
63
+ self.sio._trigger_event = self._trigger_event
68
64
 
69
65
  # report join rooms
70
66
  self.sio.manager.__basic_enter_room = \
@@ -80,10 +76,6 @@ class InstrumentedAsyncServer:
80
76
  self.sio.manager.__emit = self.sio.manager.emit
81
77
  self.sio.manager.emit = self._emit
82
78
 
83
- # report receive events
84
- self.sio.__handle_event_internal = self.sio._handle_event_internal
85
- self.sio._handle_event_internal = self._handle_event_internal
86
-
87
79
  # report engine.io connections
88
80
  self.sio.eio.on('connect', self._handle_eio_connect)
89
81
  self.sio.eio.on('disconnect', self._handle_eio_disconnect)
@@ -109,14 +101,12 @@ class InstrumentedAsyncServer:
109
101
 
110
102
  def uninstrument(self): # pragma: no cover
111
103
  if self.mode == 'development':
112
- self.sio.manager.connect = self.sio.manager.__connect
113
- self.sio.manager.disconnect = self.sio.manager.__disconnect
104
+ self.sio._trigger_event = self.sio.__trigger_event
114
105
  self.sio.manager.basic_enter_room = \
115
106
  self.sio.manager.__basic_enter_room
116
107
  self.sio.manager.basic_leave_room = \
117
108
  self.sio.manager.__basic_leave_room
118
109
  self.sio.manager.emit = self.sio.manager.__emit
119
- self.sio._handle_event_internal = self.sio.__handle_event_internal
120
110
  self.sio.eio._ok = self.sio.eio.__ok
121
111
 
122
112
  from engineio.async_socket import AsyncSocket
@@ -193,26 +183,34 @@ class InstrumentedAsyncServer:
193
183
  self.stop_stats_event.set()
194
184
  await asyncio.gather(self.stats_task)
195
185
 
196
- async def _connect(self, eio_sid, namespace):
197
- sid = await self.sio.manager.__connect(eio_sid, namespace)
186
+ async def _trigger_event(self, event, namespace, *args):
198
187
  t = time.time()
199
- self.sio.manager._timestamps[sid] = t
200
- serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
201
- await self.sio.emit('socket_connected', (
202
- serialized_socket,
203
- datetime.utcfromtimestamp(t).isoformat() + 'Z',
204
- ), namespace=self.admin_namespace)
205
- return sid
206
-
207
- async def _disconnect(self, sid, namespace, **kwargs):
208
- del self.sio.manager._timestamps[sid]
209
- await self.sio.emit('socket_disconnected', (
210
- namespace,
211
- sid,
212
- 'N/A',
213
- datetime.utcnow().isoformat() + 'Z',
214
- ), namespace=self.admin_namespace)
215
- return await self.sio.manager.__disconnect(sid, namespace, **kwargs)
188
+ sid = args[0]
189
+ if event == 'connect':
190
+ eio_sid = self.sio.manager.eio_sid_from_sid(sid, namespace)
191
+ self.sio.manager._timestamps[sid] = t
192
+ serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
193
+ await self.sio.emit('socket_connected', (
194
+ serialized_socket,
195
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
196
+ ), namespace=self.admin_namespace)
197
+ elif event == 'disconnect':
198
+ del self.sio.manager._timestamps[sid]
199
+ reason = args[1]
200
+ await self.sio.emit('socket_disconnected', (
201
+ namespace,
202
+ sid,
203
+ reason,
204
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
205
+ ), namespace=self.admin_namespace)
206
+ else:
207
+ await self.sio.emit('event_received', (
208
+ namespace,
209
+ sid,
210
+ (event, *args[1:]),
211
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
212
+ ), namespace=self.admin_namespace)
213
+ return await self.sio.__trigger_event(event, namespace, *args)
216
214
 
217
215
  async def _check_for_upgrade(self, eio_sid, sid,
218
216
  namespace): # pragma: no cover
@@ -237,7 +235,7 @@ class InstrumentedAsyncServer:
237
235
  namespace,
238
236
  room,
239
237
  sid,
240
- datetime.utcnow().isoformat() + 'Z',
238
+ datetime.now(timezone.utc).isoformat(),
241
239
  )))
242
240
  return ret
243
241
 
@@ -247,7 +245,7 @@ class InstrumentedAsyncServer:
247
245
  namespace,
248
246
  room,
249
247
  sid,
250
- datetime.utcnow().isoformat() + 'Z',
248
+ datetime.now(timezone.utc).isoformat(),
251
249
  )))
252
250
  return self.sio.manager.__basic_leave_room(sid, namespace, room)
253
251
 
@@ -258,7 +256,7 @@ class InstrumentedAsyncServer:
258
256
  callback=callback, **kwargs)
259
257
  if namespace != self.admin_namespace:
260
258
  event_data = [event] + list(data) if isinstance(data, tuple) \
261
- else [data]
259
+ else [event, data]
262
260
  if not isinstance(skip_sid, list): # pragma: no branch
263
261
  skip_sid = [skip_sid]
264
262
  for sid, _ in self.sio.manager.get_participants(namespace, room):
@@ -267,22 +265,10 @@ class InstrumentedAsyncServer:
267
265
  namespace,
268
266
  sid,
269
267
  event_data,
270
- datetime.utcnow().isoformat() + 'Z',
268
+ datetime.now(timezone.utc).isoformat(),
271
269
  ), namespace=self.admin_namespace)
272
270
  return ret
273
271
 
274
- async def _handle_event_internal(self, server, sid, eio_sid, data,
275
- namespace, id):
276
- ret = await self.sio.__handle_event_internal(server, sid, eio_sid,
277
- data, namespace, id)
278
- await self.sio.emit('event_received', (
279
- namespace,
280
- sid,
281
- data,
282
- datetime.utcnow().isoformat() + 'Z',
283
- ), namespace=self.admin_namespace)
284
- return ret
285
-
286
272
  async def _handle_eio_connect(self, eio_sid, environ):
287
273
  if self.stop_stats_event is None:
288
274
  self.stop_stats_event = self.sio.eio.create_event()
@@ -292,9 +278,9 @@ class InstrumentedAsyncServer:
292
278
  self.event_buffer.push('rawConnection')
293
279
  return await self.sio._handle_eio_connect(eio_sid, environ)
294
280
 
295
- async def _handle_eio_disconnect(self, eio_sid):
281
+ async def _handle_eio_disconnect(self, eio_sid, reason):
296
282
  self.event_buffer.push('rawDisconnection')
297
- return await self.sio._handle_eio_disconnect(eio_sid)
283
+ return await self.sio._handle_eio_disconnect(eio_sid, reason)
298
284
 
299
285
  def _eio_http_response(self, packets=None, headers=None, jsonp_index=None):
300
286
  ret = self.sio.eio.__ok(packets=packets, headers=headers,
@@ -338,7 +324,7 @@ class InstrumentedAsyncServer:
338
324
  eio_sid)
339
325
  await self.sio.emit('socket_connected', (
340
326
  serialized_socket,
341
- datetime.utcfromtimestamp(t).isoformat() + 'Z',
327
+ datetime.fromtimestamp(t, timezone.utc).isoformat(),
342
328
  ), namespace=self.admin_namespace)
343
329
  return await socket.__send_ping()
344
330
 
@@ -391,7 +377,7 @@ class InstrumentedAsyncServer:
391
377
  'secure': environ.get('wsgi.url_scheme', '') == 'https',
392
378
  'url': environ.get('PATH_INFO', ''),
393
379
  'issued': tm * 1000,
394
- 'time': datetime.utcfromtimestamp(tm).isoformat() + 'Z'
380
+ 'time': datetime.fromtimestamp(tm, timezone.utc).isoformat()
395
381
  if tm else '',
396
382
  },
397
383
  'rooms': self.sio.manager.get_rooms(sid, namespace),
@@ -158,7 +158,7 @@ class AsyncClient(base_client.BaseClient):
158
158
  await self._handle_reconnect()
159
159
  if self.eio.state == 'connected':
160
160
  return
161
- raise exceptions.ConnectionError(exc.args[0]) from None
161
+ raise exceptions.ConnectionError(exc.args[0]) from exc
162
162
 
163
163
  if wait:
164
164
  try:
@@ -191,6 +191,7 @@ class AsyncClient(base_client.BaseClient):
191
191
  if not self._reconnect_task:
192
192
  if self.eio.state == 'connected': # pragma: no cover
193
193
  # connected while sleeping above
194
+ print('oops')
194
195
  continue
195
196
  break
196
197
  await self._reconnect_task
@@ -324,7 +325,7 @@ class AsyncClient(base_client.BaseClient):
324
325
  for n in self.namespaces:
325
326
  await self._send_packet(self.packet_class(packet.DISCONNECT,
326
327
  namespace=n))
327
- await self.eio.disconnect(abort=True)
328
+ await self.eio.disconnect()
328
329
 
329
330
  async def shutdown(self):
330
331
  """Stop the client.
@@ -13,6 +13,7 @@ except ImportError: # pragma: no cover
13
13
  RedisError = None
14
14
 
15
15
  from .async_pubsub_manager import AsyncPubSubManager
16
+ from .redis_manager import parse_redis_sentinel_url
16
17
 
17
18
 
18
19
  class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
@@ -29,15 +30,18 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
29
30
  client_manager=socketio.AsyncRedisManager(url))
30
31
 
31
32
  :param url: The connection URL for the Redis server. For a default Redis
32
- store running on the same host, use ``redis://``. To use an
33
- SSL connection, use ``rediss://``.
33
+ store running on the same host, use ``redis://``. To use a
34
+ TLS connection, use ``rediss://``. To use Redis Sentinel, use
35
+ ``redis+sentinel://`` with a comma-separated list of hosts
36
+ and the service name after the db in the URL path. Example:
37
+ ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``.
34
38
  :param channel: The channel name on which the server sends and receives
35
39
  notifications. Must be the same in all the servers.
36
40
  :param write_only: If set to ``True``, only initialize to emit events. The
37
41
  default of ``False`` initializes the class for emitting
38
42
  and receiving.
39
43
  :param redis_options: additional keyword arguments to be passed to
40
- ``aioredis.from_url()``.
44
+ ``Redis.from_url()`` or ``Sentinel()``.
41
45
  """
42
46
  name = 'aioredis'
43
47
 
@@ -54,8 +58,16 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
54
58
  super().__init__(channel=channel, write_only=write_only, logger=logger)
55
59
 
56
60
  def _redis_connect(self):
57
- self.redis = aioredis.Redis.from_url(self.redis_url,
58
- **self.redis_options)
61
+ if not self.redis_url.startswith('redis+sentinel://'):
62
+ self.redis = aioredis.Redis.from_url(self.redis_url,
63
+ **self.redis_options)
64
+ else:
65
+ sentinels, service_name, connection_kwargs = \
66
+ parse_redis_sentinel_url(self.redis_url)
67
+ kwargs = self.redis_options
68
+ kwargs.update(connection_kwargs)
69
+ sentinel = aioredis.sentinel.Sentinel(sentinels, **kwargs)
70
+ self.redis = sentinel.master_for(service_name or self.channel)
59
71
  self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
60
72
 
61
73
  async def _publish(self, data):
@@ -12,6 +12,8 @@ class AsyncSimpleClient:
12
12
  The positional and keyword arguments given in the constructor are passed
13
13
  to the underlying :func:`socketio.AsyncClient` object.
14
14
  """
15
+ client_class = AsyncClient
16
+
15
17
  def __init__(self, *args, **kwargs):
16
18
  self.client_args = args
17
19
  self.client_kwargs = kwargs
@@ -60,7 +62,8 @@ class AsyncSimpleClient:
60
62
  self.namespace = namespace
61
63
  self.input_buffer = []
62
64
  self.input_event.clear()
63
- self.client = AsyncClient(*self.client_args, **self.client_kwargs)
65
+ self.client = self.client_class(
66
+ *self.client_args, **self.client_kwargs)
64
67
 
65
68
  @self.client.event(namespace=self.namespace)
66
69
  def connect(): # pragma: no cover
@@ -156,7 +156,7 @@ class Client(base_client.BaseClient):
156
156
  self._handle_reconnect()
157
157
  if self.eio.state == 'connected':
158
158
  return
159
- raise exceptions.ConnectionError(exc.args[0]) from None
159
+ raise exceptions.ConnectionError(exc.args[0]) from exc
160
160
 
161
161
  if wait:
162
162
  while self._connect_event.wait(timeout=wait_timeout):
@@ -306,7 +306,7 @@ class Client(base_client.BaseClient):
306
306
  for n in self.namespaces:
307
307
  self._send_packet(self.packet_class(
308
308
  packet.DISCONNECT, namespace=n))
309
- self.eio.disconnect(abort=True)
309
+ self.eio.disconnect()
310
310
 
311
311
  def shutdown(self):
312
312
  """Stop the client.
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import pickle
3
3
  import time
4
+ from urllib.parse import urlparse
4
5
 
5
6
  try:
6
7
  import redis
@@ -12,6 +13,32 @@ from .pubsub_manager import PubSubManager
12
13
  logger = logging.getLogger('socketio')
13
14
 
14
15
 
16
+ def parse_redis_sentinel_url(url):
17
+ """Parse a Redis Sentinel URL with the format:
18
+ redis+sentinel://[:password]@host1:port1,host2:port2,.../db/service_name
19
+ """
20
+ parsed_url = urlparse(url)
21
+ if parsed_url.scheme != 'redis+sentinel':
22
+ raise ValueError('Invalid Redis Sentinel URL')
23
+ sentinels = []
24
+ for host_port in parsed_url.netloc.split('@')[-1].split(','):
25
+ host, port = host_port.rsplit(':', 1)
26
+ sentinels.append((host, int(port)))
27
+ kwargs = {}
28
+ if parsed_url.username:
29
+ kwargs['username'] = parsed_url.username
30
+ if parsed_url.password:
31
+ kwargs['password'] = parsed_url.password
32
+ service_name = None
33
+ if parsed_url.path:
34
+ parts = parsed_url.path.split('/')
35
+ if len(parts) >= 2 and parts[1] != '':
36
+ kwargs['db'] = int(parts[1])
37
+ if len(parts) >= 3 and parts[2] != '':
38
+ service_name = parts[2]
39
+ return sentinels, service_name, kwargs
40
+
41
+
15
42
  class RedisManager(PubSubManager): # pragma: no cover
16
43
  """Redis based client manager.
17
44
 
@@ -27,15 +54,18 @@ class RedisManager(PubSubManager): # pragma: no cover
27
54
  server = socketio.Server(client_manager=socketio.RedisManager(url))
28
55
 
29
56
  :param url: The connection URL for the Redis server. For a default Redis
30
- store running on the same host, use ``redis://``. To use an
31
- SSL connection, use ``rediss://``.
57
+ store running on the same host, use ``redis://``. To use a
58
+ TLS connection, use ``rediss://``. To use Redis Sentinel, use
59
+ ``redis+sentinel://`` with a comma-separated list of hosts
60
+ and the service name after the db in the URL path. Example:
61
+ ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``.
32
62
  :param channel: The channel name on which the server sends and receives
33
63
  notifications. Must be the same in all the servers.
34
64
  :param write_only: If set to ``True``, only initialize to emit events. The
35
65
  default of ``False`` initializes the class for emitting
36
66
  and receiving.
37
67
  :param redis_options: additional keyword arguments to be passed to
38
- ``Redis.from_url()``.
68
+ ``Redis.from_url()`` or ``Sentinel()``.
39
69
  """
40
70
  name = 'redis'
41
71
 
@@ -66,8 +96,16 @@ class RedisManager(PubSubManager): # pragma: no cover
66
96
  'with ' + self.server.async_mode)
67
97
 
68
98
  def _redis_connect(self):
69
- self.redis = redis.Redis.from_url(self.redis_url,
70
- **self.redis_options)
99
+ if not self.redis_url.startswith('redis+sentinel://'):
100
+ self.redis = redis.Redis.from_url(self.redis_url,
101
+ **self.redis_options)
102
+ else:
103
+ sentinels, service_name, connection_kwargs = \
104
+ parse_redis_sentinel_url(self.redis_url)
105
+ kwargs = self.redis_options
106
+ kwargs.update(connection_kwargs)
107
+ sentinel = redis.sentinel.Sentinel(sentinels, **kwargs)
108
+ self.redis = sentinel.master_for(service_name or self.channel)
71
109
  self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
72
110
 
73
111
  def _publish(self, data):
@@ -12,6 +12,8 @@ class SimpleClient:
12
12
  The positional and keyword arguments given in the constructor are passed
13
13
  to the underlying :func:`socketio.Client` object.
14
14
  """
15
+ client_class = Client
16
+
15
17
  def __init__(self, *args, **kwargs):
16
18
  self.client_args = args
17
19
  self.client_kwargs = kwargs
@@ -58,7 +60,8 @@ class SimpleClient:
58
60
  self.namespace = namespace
59
61
  self.input_buffer = []
60
62
  self.input_event.clear()
61
- self.client = Client(*self.client_args, **self.client_kwargs)
63
+ self.client = self.client_class(
64
+ *self.client_args, **self.client_kwargs)
62
65
 
63
66
  @self.client.event(namespace=self.namespace)
64
67
  def connect(): # pragma: no cover
@@ -475,7 +475,7 @@ class TestAsyncClient:
475
475
  c._send_packet.await_args_list[0][0][0].encode()
476
476
  == expected_packet.encode()
477
477
  )
478
- c.eio.disconnect.assert_awaited_once_with(abort=True)
478
+ c.eio.disconnect.assert_awaited_once_with()
479
479
 
480
480
  async def test_disconnect_namespaces(self):
481
481
  c = async_client.AsyncClient()
@@ -993,7 +993,7 @@ class TestAsyncClient:
993
993
  c._send_packet.await_args_list[0][0][0].encode()
994
994
  == expected_packet.encode()
995
995
  )
996
- c.eio.disconnect.assert_awaited_once_with(abort=True)
996
+ c.eio.disconnect.assert_awaited_once_with()
997
997
 
998
998
  async def test_shutdown_disconnect_namespaces(self):
999
999
  c = async_client.AsyncClient()
@@ -16,46 +16,51 @@ class TestAsyncAsyncSimpleClient:
16
16
  assert not client.connected
17
17
 
18
18
  async def test_connect(self):
19
+ mock_client = mock.MagicMock()
20
+ original_client_class = AsyncSimpleClient.client_class
21
+ AsyncSimpleClient.client_class = mock_client
22
+
19
23
  client = AsyncSimpleClient(123, a='b')
20
- with mock.patch('socketio.async_simple_client.AsyncClient') \
21
- as mock_client:
24
+ mock_client.return_value.connect = mock.AsyncMock()
25
+
26
+ await client.connect('url', headers='h', auth='a', transports='t',
27
+ namespace='n', socketio_path='s',
28
+ wait_timeout='w')
29
+ mock_client.assert_called_once_with(123, a='b')
30
+ assert client.client == mock_client()
31
+ mock_client().connect.assert_awaited_once_with(
32
+ 'url', headers='h', auth='a', transports='t',
33
+ namespaces=['n'], socketio_path='s', wait_timeout='w')
34
+ mock_client().event.call_count == 3
35
+ mock_client().on.assert_called_once_with('*', namespace='n')
36
+ assert client.namespace == 'n'
37
+ assert not client.input_event.is_set()
38
+
39
+ AsyncSimpleClient.client_class = original_client_class
40
+
41
+ async def test_connect_context_manager(self):
42
+ mock_client = mock.MagicMock()
43
+ original_client_class = AsyncSimpleClient.client_class
44
+ AsyncSimpleClient.client_class = mock_client
45
+
46
+ async with AsyncSimpleClient(123, a='b') as client:
22
47
  mock_client.return_value.connect = mock.AsyncMock()
23
48
 
24
- await client.connect('url', headers='h', auth='a', transports='t',
25
- namespace='n', socketio_path='s',
26
- wait_timeout='w')
49
+ await client.connect('url', headers='h', auth='a',
50
+ transports='t', namespace='n',
51
+ socketio_path='s', wait_timeout='w')
27
52
  mock_client.assert_called_once_with(123, a='b')
28
53
  assert client.client == mock_client()
29
54
  mock_client().connect.assert_awaited_once_with(
30
55
  'url', headers='h', auth='a', transports='t',
31
56
  namespaces=['n'], socketio_path='s', wait_timeout='w')
32
57
  mock_client().event.call_count == 3
33
- mock_client().on.assert_called_once_with('*', namespace='n')
58
+ mock_client().on.assert_called_once_with(
59
+ '*', namespace='n')
34
60
  assert client.namespace == 'n'
35
61
  assert not client.input_event.is_set()
36
62
 
37
- async def test_connect_context_manager(self):
38
- async def _t():
39
- async with AsyncSimpleClient(123, a='b') as client:
40
- with mock.patch('socketio.async_simple_client.AsyncClient') \
41
- as mock_client:
42
- mock_client.return_value.connect = mock.AsyncMock()
43
-
44
- await client.connect('url', headers='h', auth='a',
45
- transports='t', namespace='n',
46
- socketio_path='s', wait_timeout='w')
47
- mock_client.assert_called_once_with(123, a='b')
48
- assert client.client == mock_client()
49
- mock_client().connect.assert_awaited_once_with(
50
- 'url', headers='h', auth='a', transports='t',
51
- namespaces=['n'], socketio_path='s', wait_timeout='w')
52
- mock_client().event.call_count == 3
53
- mock_client().on.assert_called_once_with(
54
- '*', namespace='n')
55
- assert client.namespace == 'n'
56
- assert not client.input_event.is_set()
57
-
58
- await _t()
63
+ AsyncSimpleClient.client_class = original_client_class
59
64
 
60
65
  async def test_connect_twice(self):
61
66
  client = AsyncSimpleClient(123, a='b')
@@ -633,7 +633,7 @@ class TestClient:
633
633
  c._send_packet.call_args_list[0][0][0].encode()
634
634
  == expected_packet.encode()
635
635
  )
636
- c.eio.disconnect.assert_called_once_with(abort=True)
636
+ c.eio.disconnect.assert_called_once_with()
637
637
 
638
638
  def test_disconnect_namespaces(self):
639
639
  c = client.Client()
@@ -1138,7 +1138,7 @@ class TestClient:
1138
1138
  c._send_packet.call_args_list[0][0][0].encode()
1139
1139
  == expected_packet.encode()
1140
1140
  )
1141
- c.eio.disconnect.assert_called_once_with(abort=True)
1141
+ c.eio.disconnect.assert_called_once_with()
1142
1142
 
1143
1143
  def test_shutdown_disconnect_namespaces(self):
1144
1144
  c = client.Client()
@@ -0,0 +1,38 @@
1
+ import pytest
2
+
3
+ from socketio.redis_manager import parse_redis_sentinel_url
4
+
5
+
6
+ class TestPubSubManager:
7
+ def test_sentinel_url_parser(self):
8
+ with pytest.raises(ValueError):
9
+ parse_redis_sentinel_url('redis://localhost:6379/0')
10
+
11
+ assert parse_redis_sentinel_url(
12
+ 'redis+sentinel://localhost:6379'
13
+ ) == (
14
+ [('localhost', 6379)],
15
+ None,
16
+ {}
17
+ )
18
+ assert parse_redis_sentinel_url(
19
+ 'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/'
20
+ ) == (
21
+ [('192.168.0.1', 6379), ('192.168.0.2', 6379)],
22
+ None,
23
+ {}
24
+ )
25
+ assert parse_redis_sentinel_url(
26
+ 'redis+sentinel://h1:6379,h2:6379/0'
27
+ ) == (
28
+ [('h1', 6379), ('h2', 6379)],
29
+ None,
30
+ {'db': 0}
31
+ )
32
+ assert parse_redis_sentinel_url(
33
+ 'redis+sentinel://user:password@h1:6379,h2:6379,h1:6380/0/myredis'
34
+ ) == (
35
+ [('h1', 6379), ('h2', 6379), ('h1', 6380)],
36
+ 'myredis',
37
+ {'username': 'user', 'password': 'password', 'db': 0}
38
+ )
@@ -14,10 +14,34 @@ class TestSimpleClient:
14
14
  assert not client.connected
15
15
 
16
16
  def test_connect(self):
17
+ mock_client = mock.MagicMock()
18
+ original_client_class = SimpleClient.client_class
19
+ SimpleClient.client_class = mock_client
20
+
17
21
  client = SimpleClient(123, a='b')
18
- with mock.patch('socketio.simple_client.Client') as mock_client:
22
+ client.connect('url', headers='h', auth='a', transports='t',
23
+ namespace='n', socketio_path='s', wait_timeout='w')
24
+ mock_client.assert_called_once_with(123, a='b')
25
+ assert client.client == mock_client()
26
+ mock_client().connect.assert_called_once_with(
27
+ 'url', headers='h', auth='a', transports='t',
28
+ namespaces=['n'], socketio_path='s', wait_timeout='w')
29
+ mock_client().event.call_count == 3
30
+ mock_client().on.assert_called_once_with('*', namespace='n')
31
+ assert client.namespace == 'n'
32
+ assert not client.input_event.is_set()
33
+
34
+ SimpleClient.client_class = original_client_class
35
+
36
+ def test_connect_context_manager(self):
37
+ mock_client = mock.MagicMock()
38
+ original_client_class = SimpleClient.client_class
39
+ SimpleClient.client_class = mock_client
40
+
41
+ with SimpleClient(123, a='b') as client:
19
42
  client.connect('url', headers='h', auth='a', transports='t',
20
- namespace='n', socketio_path='s', wait_timeout='w')
43
+ namespace='n', socketio_path='s',
44
+ wait_timeout='w')
21
45
  mock_client.assert_called_once_with(123, a='b')
22
46
  assert client.client == mock_client()
23
47
  mock_client().connect.assert_called_once_with(
@@ -28,21 +52,7 @@ class TestSimpleClient:
28
52
  assert client.namespace == 'n'
29
53
  assert not client.input_event.is_set()
30
54
 
31
- def test_connect_context_manager(self):
32
- with SimpleClient(123, a='b') as client:
33
- with mock.patch('socketio.simple_client.Client') as mock_client:
34
- client.connect('url', headers='h', auth='a', transports='t',
35
- namespace='n', socketio_path='s',
36
- wait_timeout='w')
37
- mock_client.assert_called_once_with(123, a='b')
38
- assert client.client == mock_client()
39
- mock_client().connect.assert_called_once_with(
40
- 'url', headers='h', auth='a', transports='t',
41
- namespaces=['n'], socketio_path='s', wait_timeout='w')
42
- mock_client().event.call_count == 3
43
- mock_client().on.assert_called_once_with('*', namespace='n')
44
- assert client.namespace == 'n'
45
- assert not client.input_event.is_set()
55
+ SimpleClient.client_class = original_client_class
46
56
 
47
57
  def test_connect_twice(self):
48
58
  client = SimpleClient(123, a='b')