python-engineio 4.10.0__py3-none-any.whl → 4.11.0__py3-none-any.whl

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.
engineio/async_client.py CHANGED
@@ -37,7 +37,7 @@ def async_signal_handler():
37
37
  for task in tasks:
38
38
  task.cancel()
39
39
  await asyncio.gather(*tasks, return_exceptions=True)
40
- asyncio.get_event_loop().stop()
40
+ asyncio.get_running_loop().stop()
41
41
 
42
42
  asyncio.ensure_future(_handler())
43
43
 
@@ -75,6 +75,9 @@ class AsyncClient(base_client.BaseClient):
75
75
  :param websocket_extra_options: Dictionary containing additional keyword
76
76
  arguments passed to
77
77
  ``aiohttp.ws_connect()``.
78
+ :param timestamp_requests: If ``True`` a timestamp is added to the query
79
+ string of Socket.IO requests as a cache-busting
80
+ measure. Set to ``False`` to disable.
78
81
  """
79
82
  def is_asyncio_based(self):
80
83
  return True
@@ -106,7 +109,7 @@ class AsyncClient(base_client.BaseClient):
106
109
  if self.handle_sigint and not async_signal_handler_set and \
107
110
  threading.current_thread() == threading.main_thread():
108
111
  try:
109
- asyncio.get_event_loop().add_signal_handler(
112
+ asyncio.get_running_loop().add_signal_handler(
110
113
  signal.SIGINT, async_signal_handler)
111
114
  except NotImplementedError: # pragma: no cover
112
115
  self.logger.warning('Signal handler is unsupported')
@@ -149,7 +152,7 @@ class AsyncClient(base_client.BaseClient):
149
152
  """
150
153
  await self._send_packet(packet.Packet(packet.MESSAGE, data=data))
151
154
 
152
- async def disconnect(self, abort=False):
155
+ async def disconnect(self, abort=False, reason=None):
153
156
  """Disconnect from the server.
154
157
 
155
158
  :param abort: If set to ``True``, do not wait for background tasks
@@ -161,7 +164,9 @@ class AsyncClient(base_client.BaseClient):
161
164
  await self._send_packet(packet.Packet(packet.CLOSE))
162
165
  await self.queue.put(None)
163
166
  self.state = 'disconnecting'
164
- await self._trigger_event('disconnect', run_async=False)
167
+ await self._trigger_event('disconnect',
168
+ reason or self.reason.CLIENT_DISCONNECT,
169
+ run_async=False)
165
170
  if self.current_transport == 'websocket':
166
171
  await self.ws.close()
167
172
  if not abort:
@@ -409,7 +414,8 @@ class AsyncClient(base_client.BaseClient):
409
414
  elif pkt.packet_type == packet.PING:
410
415
  await self._send_packet(packet.Packet(packet.PONG, pkt.data))
411
416
  elif pkt.packet_type == packet.CLOSE:
412
- await self.disconnect(abort=True)
417
+ await self.disconnect(abort=True,
418
+ reason=self.reason.SERVER_DISCONNECT)
413
419
  elif pkt.packet_type == packet.NOOP:
414
420
  pass
415
421
  else:
@@ -462,7 +468,17 @@ class AsyncClient(base_client.BaseClient):
462
468
  return task
463
469
  else:
464
470
  try:
465
- ret = await self.handlers[event](*args)
471
+ try:
472
+ ret = await self.handlers[event](*args)
473
+ except TypeError:
474
+ print(args, len(args))
475
+ if event == 'disconnect' and \
476
+ len(args) == 1: # pragma: no branch
477
+ # legacy disconnect events do not have a reason
478
+ # argument
479
+ return await self.handlers[event]()
480
+ else: # pragma: no cover
481
+ raise
466
482
  except asyncio.CancelledError: # pragma: no cover
467
483
  pass
468
484
  except:
@@ -482,7 +498,17 @@ class AsyncClient(base_client.BaseClient):
482
498
  return task
483
499
  else:
484
500
  try:
485
- ret = self.handlers[event](*args)
501
+ try:
502
+ ret = self.handlers[event](*args)
503
+ except TypeError:
504
+ print(args, len(args))
505
+ if event == 'disconnect' and \
506
+ len(args) == 1: # pragma: no branch
507
+ # legacy disconnect events do not have a reason
508
+ # argument
509
+ ret = self.handlers[event]()
510
+ else: # pragma: no cover
511
+ raise
486
512
  except:
487
513
  self.logger.exception(event + ' handler error')
488
514
  if event == 'connect':
@@ -524,7 +550,8 @@ class AsyncClient(base_client.BaseClient):
524
550
  self.logger.info('Waiting for write loop task to end')
525
551
  await self.write_loop_task
526
552
  if self.state == 'connected':
527
- await self._trigger_event('disconnect', run_async=False)
553
+ await self._trigger_event(
554
+ 'disconnect', self.reason.TRANSPORT_ERROR, run_async=False)
528
555
  try:
529
556
  base_client.connected_clients.remove(self)
530
557
  except ValueError: # pragma: no cover
@@ -578,7 +605,8 @@ class AsyncClient(base_client.BaseClient):
578
605
  self.logger.info('Waiting for write loop task to end')
579
606
  await self.write_loop_task
580
607
  if self.state == 'connected':
581
- await self._trigger_event('disconnect', run_async=False)
608
+ await self._trigger_event(
609
+ 'disconnect', self.reason.TRANSPORT_ERROR, run_async=False)
582
610
  try:
583
611
  base_client.connected_clients.remove(self)
584
612
  except ValueError: # pragma: no cover
@@ -25,7 +25,7 @@ class SimpleWebSocketWSGI: # pragma: no cover
25
25
  try:
26
26
  return self.ws.send(message)
27
27
  except simple_websocket.ConnectionClosed:
28
- raise IOError()
28
+ raise OSError()
29
29
 
30
30
  def wait(self):
31
31
  try:
@@ -57,7 +57,7 @@ def translate_request(request):
57
57
 
58
58
  key = 'HTTP_%s' % hdr_name.replace('-', '_')
59
59
  if key in environ:
60
- hdr_value = '%s,%s' % (environ[key], hdr_value)
60
+ hdr_value = f'{environ[key]},{hdr_value}'
61
61
 
62
62
  environ[key] = hdr_value
63
63
 
@@ -79,7 +79,7 @@ def make_response(status, headers, payload, environ):
79
79
  headers=headers)
80
80
 
81
81
 
82
- class WebSocket(object): # pragma: no cover
82
+ class WebSocket: # pragma: no cover
83
83
  """
84
84
  This wrapper class provides a aiohttp WebSocket interface that is
85
85
  somewhat compatible with eventlet's implementation.
@@ -114,7 +114,7 @@ class WebSocket(object): # pragma: no cover
114
114
  msg = await self._sock.receive()
115
115
  if not isinstance(msg.data, bytes) and \
116
116
  not isinstance(msg.data, str):
117
- raise IOError()
117
+ raise OSError()
118
118
  return msg.data
119
119
 
120
120
 
@@ -135,7 +135,7 @@ class ASGIApp:
135
135
 
136
136
 
137
137
  async def translate_request(scope, receive, send):
138
- class AwaitablePayload(object): # pragma: no cover
138
+ class AwaitablePayload: # pragma: no cover
139
139
  def __init__(self, payload):
140
140
  self.payload = payload or b''
141
141
 
@@ -161,9 +161,15 @@ async def translate_request(scope, receive, send):
161
161
  else:
162
162
  return {}
163
163
 
164
- raw_uri = scope['path'].encode('utf-8')
164
+ raw_uri = scope['path']
165
+ query_string = ''
165
166
  if 'query_string' in scope and scope['query_string']:
166
- raw_uri += b'?' + scope['query_string']
167
+ try:
168
+ query_string = scope['query_string'].decode('utf-8')
169
+ except UnicodeDecodeError:
170
+ pass
171
+ else:
172
+ raw_uri += '?' + query_string
167
173
  environ = {
168
174
  'wsgi.input': AwaitablePayload(payload),
169
175
  'wsgi.errors': sys.stderr,
@@ -175,8 +181,8 @@ async def translate_request(scope, receive, send):
175
181
  'SERVER_SOFTWARE': 'asgi',
176
182
  'REQUEST_METHOD': scope.get('method', 'GET'),
177
183
  'PATH_INFO': scope['path'],
178
- 'QUERY_STRING': scope.get('query_string', b'').decode('utf-8'),
179
- 'RAW_URI': raw_uri.decode('utf-8'),
184
+ 'QUERY_STRING': query_string,
185
+ 'RAW_URI': raw_uri,
180
186
  'SCRIPT_NAME': '',
181
187
  'SERVER_PROTOCOL': 'HTTP/1.1',
182
188
  'REMOTE_ADDR': '127.0.0.1',
@@ -189,8 +195,12 @@ async def translate_request(scope, receive, send):
189
195
  }
190
196
 
191
197
  for hdr_name, hdr_value in scope['headers']:
192
- hdr_name = hdr_name.upper().decode('utf-8')
193
- hdr_value = hdr_value.decode('utf-8')
198
+ try:
199
+ hdr_name = hdr_name.upper().decode('utf-8')
200
+ hdr_value = hdr_value.decode('utf-8')
201
+ except UnicodeDecodeError:
202
+ # skip header if it cannot be decoded
203
+ continue
194
204
  if hdr_name == 'CONTENT-TYPE':
195
205
  environ['CONTENT_TYPE'] = hdr_value
196
206
  continue
@@ -200,7 +210,7 @@ async def translate_request(scope, receive, send):
200
210
 
201
211
  key = 'HTTP_%s' % hdr_name.replace('-', '_')
202
212
  if key in environ:
203
- hdr_value = '%s,%s' % (environ[key], hdr_value)
213
+ hdr_value = f'{environ[key]},{hdr_value}'
204
214
 
205
215
  environ[key] = hdr_value
206
216
 
@@ -231,7 +241,7 @@ async def make_response(status, headers, payload, environ):
231
241
  'body': payload})
232
242
 
233
243
 
234
- class WebSocket(object): # pragma: no cover
244
+ class WebSocket: # pragma: no cover
235
245
  """
236
246
  This wrapper class provides an asgi WebSocket interface that is
237
247
  somewhat compatible with eventlet's implementation.
@@ -269,7 +279,7 @@ class WebSocket(object): # pragma: no cover
269
279
  async def wait(self):
270
280
  event = await self.asgi_receive()
271
281
  if event['type'] != 'websocket.receive':
272
- raise IOError()
282
+ raise OSError()
273
283
  return event.get('bytes') or event.get('text')
274
284
 
275
285
 
@@ -1,5 +1,3 @@
1
- from __future__ import absolute_import
2
-
3
1
  from eventlet.green.threading import Event
4
2
  from eventlet import queue, sleep, spawn
5
3
  from eventlet.websocket import WebSocketWSGI as _WebSocketWSGI
@@ -18,7 +18,7 @@ class Thread(gevent.Greenlet): # pragma: no cover
18
18
  return self.run()
19
19
 
20
20
 
21
- class uWSGIWebSocket(object): # pragma: no cover
21
+ class uWSGIWebSocket: # pragma: no cover
22
22
  """
23
23
  This wrapper class provides a uWSGI WebSocket interface that is
24
24
  compatible with eventlet's implementation.
@@ -115,7 +115,7 @@ class uWSGIWebSocket(object): # pragma: no cover
115
115
  if self._req_ctx is not None:
116
116
  try:
117
117
  msg = uwsgi.websocket_recv(request_context=self._req_ctx)
118
- except IOError: # connection closed
118
+ except OSError: # connection closed
119
119
  self.close()
120
120
  return None
121
121
  return self._decode_received(msg)
@@ -138,7 +138,7 @@ class uWSGIWebSocket(object): # pragma: no cover
138
138
  for msg in msgs:
139
139
  try:
140
140
  self._send(msg)
141
- except IOError:
141
+ except OSError:
142
142
  self.close()
143
143
  return None
144
144
  # maybe there is something to receive, if not, at least
@@ -146,7 +146,7 @@ class uWSGIWebSocket(object): # pragma: no cover
146
146
  while True:
147
147
  try:
148
148
  msg = uwsgi.websocket_recv_nb()
149
- except IOError: # connection closed
149
+ except OSError: # connection closed
150
150
  self.close()
151
151
  return None
152
152
  if msg: # message available
@@ -33,7 +33,7 @@ def translate_request(request): # pragma: no cover
33
33
  """This function takes the arguments passed to the request handler and
34
34
  uses them to generate a WSGI compatible environ dictionary.
35
35
  """
36
- class AwaitablePayload(object):
36
+ class AwaitablePayload:
37
37
  def __init__(self, payload):
38
38
  self.payload = payload or b''
39
39
 
@@ -78,7 +78,7 @@ def translate_request(request): # pragma: no cover
78
78
 
79
79
  key = 'HTTP_%s' % hdr_name.replace('-', '_')
80
80
  if key in environ:
81
- hdr_value = '%s,%s' % (environ[key], hdr_value)
81
+ hdr_value = f'{environ[key]},{hdr_value}'
82
82
 
83
83
  environ[key] = hdr_value
84
84
 
@@ -107,7 +107,7 @@ def make_response(status, headers, payload, environ): # pragma: no cover
107
107
  status=int(status.split()[0]), headers=headers_dict)
108
108
 
109
109
 
110
- class WebSocket(object): # pragma: no cover
110
+ class WebSocket: # pragma: no cover
111
111
  """
112
112
  This wrapper class provides a sanic WebSocket interface that is
113
113
  somewhat compatible with eventlet's implementation.
@@ -136,7 +136,7 @@ class WebSocket(object): # pragma: no cover
136
136
  data = await self._sock.recv()
137
137
  if not isinstance(data, bytes) and \
138
138
  not isinstance(data, str):
139
- raise IOError()
139
+ raise OSError()
140
140
  return data
141
141
 
142
142
 
@@ -64,7 +64,7 @@ def translate_request(handler):
64
64
  """This function takes the arguments passed to the request handler and
65
65
  uses them to generate a WSGI compatible environ dictionary.
66
66
  """
67
- class AwaitablePayload(object):
67
+ class AwaitablePayload:
68
68
  def __init__(self, payload):
69
69
  self.payload = payload or b''
70
70
 
@@ -142,7 +142,7 @@ def make_response(status, headers, payload, environ):
142
142
  tornado_handler.finish()
143
143
 
144
144
 
145
- class WebSocket(object): # pragma: no cover
145
+ class WebSocket: # pragma: no cover
146
146
  """
147
147
  This wrapper class provides a tornado WebSocket interface that is
148
148
  somewhat compatible with eventlet's implementation.
@@ -170,7 +170,7 @@ class WebSocket(object): # pragma: no cover
170
170
  msg = await self.tornado_handler.get_next_message()
171
171
  if not isinstance(msg, bytes) and \
172
172
  not isinstance(msg, str):
173
- raise IOError()
173
+ raise OSError()
174
174
  return msg
175
175
 
176
176
 
engineio/async_server.py CHANGED
@@ -89,7 +89,7 @@ class AsyncServer(base_server.BaseServer):
89
89
  def attach(self, app, engineio_path='engine.io'):
90
90
  """Attach the Engine.IO server to an application."""
91
91
  engineio_path = engineio_path.strip('/')
92
- self._async['create_route'](app, self, '/{}/'.format(engineio_path))
92
+ self._async['create_route'](app, self, f'/{engineio_path}/')
93
93
 
94
94
  async def send(self, sid, data):
95
95
  """Send a message to a client.
@@ -162,7 +162,7 @@ class AsyncServer(base_server.BaseServer):
162
162
  async with eio.session(sid) as session:
163
163
  print('received message from ', session['username'])
164
164
  """
165
- class _session_context_manager(object):
165
+ class _session_context_manager:
166
166
  def __init__(self, server, sid):
167
167
  self.server = server
168
168
  self.sid = sid
@@ -192,12 +192,15 @@ class AsyncServer(base_server.BaseServer):
192
192
  # the socket was already closed or gone
193
193
  pass
194
194
  else:
195
- await socket.close()
195
+ await socket.close(reason=self.reason.SERVER_DISCONNECT)
196
196
  if sid in self.sockets: # pragma: no cover
197
197
  del self.sockets[sid]
198
198
  else:
199
- await asyncio.wait([asyncio.create_task(client.close())
200
- for client in self.sockets.values()])
199
+ await asyncio.wait([
200
+ asyncio.create_task(client.close(
201
+ reason=self.reason.SERVER_DISCONNECT))
202
+ for client in self.sockets.values()
203
+ ])
201
204
  self.sockets = {}
202
205
 
203
206
  async def handle_request(self, *args, **kwargs):
@@ -269,11 +272,11 @@ class AsyncServer(base_server.BaseServer):
269
272
  'bad-jsonp-index')
270
273
  r = self._bad_request('Invalid JSONP index number')
271
274
  elif method == 'GET':
275
+ upgrade_header = environ.get('HTTP_UPGRADE').lower() \
276
+ if 'HTTP_UPGRADE' in environ else None
272
277
  if sid is None:
273
278
  # transport must be one of 'polling' or 'websocket'.
274
279
  # if 'websocket', the HTTP_UPGRADE header must match.
275
- upgrade_header = environ.get('HTTP_UPGRADE').lower() \
276
- if 'HTTP_UPGRADE' in environ else None
277
280
  if transport == 'polling' \
278
281
  or transport == upgrade_header == 'websocket':
279
282
  r = await self._handle_connect(environ, transport,
@@ -284,32 +287,41 @@ class AsyncServer(base_server.BaseServer):
284
287
  r = self._bad_request('Invalid websocket upgrade')
285
288
  else:
286
289
  if sid not in self.sockets:
287
- self._log_error_once('Invalid session ' + sid, 'bad-sid')
288
- r = self._bad_request('Invalid session ' + sid)
290
+ self._log_error_once(f'Invalid session {sid}', 'bad-sid')
291
+ r = self._bad_request(f'Invalid session {sid}')
289
292
  else:
290
- socket = self._get_socket(sid)
291
- if self.transport(sid) != transport:
292
- self._log_error_once(
293
- 'Invalid transport for session ' + sid,
294
- 'bad-transport')
295
- r = self._bad_request('Invalid transport')
293
+ try:
294
+ socket = self._get_socket(sid)
295
+ except KeyError as e: # pragma: no cover
296
+ self._log_error_once(f'{e} {sid}', 'bad-sid')
297
+ r = self._bad_request(f'{e} {sid}')
296
298
  else:
297
- try:
298
- packets = await socket.handle_get_request(environ)
299
- if isinstance(packets, list):
300
- r = self._ok(packets, jsonp_index=jsonp_index)
301
- else:
302
- r = packets
303
- except exceptions.EngineIOError:
304
- if sid in self.sockets: # pragma: no cover
305
- await self.disconnect(sid)
306
- r = self._bad_request()
307
- if sid in self.sockets and self.sockets[sid].closed:
308
- del self.sockets[sid]
299
+ if self.transport(sid) != transport and \
300
+ transport != upgrade_header:
301
+ self._log_error_once(
302
+ f'Invalid transport for session {sid}',
303
+ 'bad-transport')
304
+ r = self._bad_request('Invalid transport')
305
+ else:
306
+ try:
307
+ packets = await socket.handle_get_request(
308
+ environ)
309
+ if isinstance(packets, list):
310
+ r = self._ok(packets,
311
+ jsonp_index=jsonp_index)
312
+ else:
313
+ r = packets
314
+ except exceptions.EngineIOError:
315
+ if sid in self.sockets: # pragma: no cover
316
+ await self.disconnect(sid)
317
+ r = self._bad_request()
318
+ if sid in self.sockets and \
319
+ self.sockets[sid].closed:
320
+ del self.sockets[sid]
309
321
  elif method == 'POST':
310
322
  if sid is None or sid not in self.sockets:
311
- self._log_error_once('Invalid session ' + sid, 'bad-sid')
312
- r = self._bad_request('Invalid session ' + sid)
323
+ self._log_error_once(f'Invalid session {sid}', 'bad-sid')
324
+ r = self._bad_request(f'Invalid session {sid}')
313
325
  else:
314
326
  socket = self._get_socket(sid)
315
327
  try:
@@ -439,11 +451,14 @@ class AsyncServer(base_server.BaseServer):
439
451
  s = async_socket.AsyncSocket(self, sid)
440
452
  self.sockets[sid] = s
441
453
 
442
- pkt = packet.Packet(
443
- packet.OPEN, {'sid': sid,
444
- 'upgrades': self._upgrades(sid, transport),
445
- 'pingTimeout': int(self.ping_timeout * 1000),
446
- 'pingInterval': int(self.ping_interval * 1000)})
454
+ pkt = packet.Packet(packet.OPEN, {
455
+ 'sid': sid,
456
+ 'upgrades': self._upgrades(sid, transport),
457
+ 'pingTimeout': int(self.ping_timeout * 1000),
458
+ 'pingInterval': int(
459
+ self.ping_interval + self.ping_interval_grace_period) * 1000,
460
+ 'maxPayload': self.max_http_buffer_size,
461
+ })
447
462
  await s.send(pkt)
448
463
  s.schedule_ping()
449
464
 
@@ -490,7 +505,16 @@ class AsyncServer(base_server.BaseServer):
490
505
  if asyncio.iscoroutinefunction(self.handlers[event]):
491
506
  async def run_async_handler():
492
507
  try:
493
- return await self.handlers[event](*args)
508
+ try:
509
+ return await self.handlers[event](*args)
510
+ except TypeError:
511
+ if event == 'disconnect' and \
512
+ len(args) == 2: # pragma: no branch
513
+ # legacy disconnect events do not have a reason
514
+ # argument
515
+ return await self.handlers[event](args[0])
516
+ else: # pragma: no cover
517
+ raise
494
518
  except asyncio.CancelledError: # pragma: no cover
495
519
  pass
496
520
  except:
@@ -509,7 +533,16 @@ class AsyncServer(base_server.BaseServer):
509
533
  else:
510
534
  async def run_sync_handler():
511
535
  try:
512
- return self.handlers[event](*args)
536
+ try:
537
+ return self.handlers[event](*args)
538
+ except TypeError:
539
+ if event == 'disconnect' and \
540
+ len(args) == 2: # pragma: no branch
541
+ # legacy disconnect events do not have a reason
542
+ # argument
543
+ return self.handlers[event](args[0])
544
+ else: # pragma: no cover
545
+ raise
513
546
  except:
514
547
  self.logger.exception(event + ' handler error')
515
548
  if event == 'connect':
@@ -527,6 +560,7 @@ class AsyncServer(base_server.BaseServer):
527
560
 
528
561
  async def _service_task(self): # pragma: no cover
529
562
  """Monitor connected clients and clean up those that time out."""
563
+ loop = asyncio.get_running_loop()
530
564
  self.service_task_event = self.create_event()
531
565
  while not self.service_task_event.is_set():
532
566
  if len(self.sockets) == 0:
@@ -568,7 +602,7 @@ class AsyncServer(base_server.BaseServer):
568
602
  self.logger.info('service task canceled')
569
603
  break
570
604
  except:
571
- if asyncio.get_event_loop().is_closed():
605
+ if loop.is_closed():
572
606
  self.logger.info('event loop is closed, exiting service '
573
607
  'task')
574
608
  break
engineio/async_socket.py CHANGED
@@ -47,7 +47,8 @@ class AsyncSocket(base_socket.BaseSocket):
47
47
  elif pkt.packet_type == packet.UPGRADE:
48
48
  await self.send(packet.Packet(packet.NOOP))
49
49
  elif pkt.packet_type == packet.CLOSE:
50
- await self.close(wait=False, abort=True)
50
+ await self.close(wait=False, abort=True,
51
+ reason=self.server.reason.CLIENT_DISCONNECT)
51
52
  else:
52
53
  raise exceptions.UnknownPacketError()
53
54
 
@@ -62,7 +63,8 @@ class AsyncSocket(base_socket.BaseSocket):
62
63
  # Passing abort=False here will cause close() to write a
63
64
  # CLOSE packet. This has the effect of updating half-open sockets
64
65
  # to their correct state of disconnected
65
- await self.close(wait=False, abort=False)
66
+ await self.close(wait=False, abort=False,
67
+ reason=self.server.reason.PING_TIMEOUT)
66
68
  return False
67
69
  return True
68
70
 
@@ -95,7 +97,8 @@ class AsyncSocket(base_socket.BaseSocket):
95
97
  packets = await self.poll()
96
98
  except exceptions.QueueEmpty:
97
99
  exc = sys.exc_info()
98
- await self.close(wait=False)
100
+ await self.close(wait=False,
101
+ reason=self.server.reason.TRANSPORT_ERROR)
99
102
  raise exc[1].with_traceback(exc[2])
100
103
  return packets
101
104
 
@@ -110,11 +113,14 @@ class AsyncSocket(base_socket.BaseSocket):
110
113
  for pkt in p.packets:
111
114
  await self.receive(pkt)
112
115
 
113
- async def close(self, wait=True, abort=False):
116
+ async def close(self, wait=True, abort=False, reason=None):
114
117
  """Close the socket connection."""
115
118
  if not self.closed and not self.closing:
116
119
  self.closing = True
117
- await self.server._trigger_event('disconnect', self.sid)
120
+ await self.server._trigger_event(
121
+ 'disconnect', self.sid,
122
+ reason or self.server.reason.SERVER_DISCONNECT,
123
+ run_async=False)
118
124
  if not abort:
119
125
  await self.send(packet.Packet(packet.CLOSE))
120
126
  self.closed = True
@@ -134,7 +140,7 @@ class AsyncSocket(base_socket.BaseSocket):
134
140
  async def _upgrade_websocket(self, environ):
135
141
  """Upgrade the connection from polling to websocket."""
136
142
  if self.upgraded:
137
- raise IOError('Socket has been upgraded already')
143
+ raise OSError('Socket has been upgraded already')
138
144
  if self.server._async['websocket'] is None:
139
145
  # the selected async mode does not support websocket
140
146
  return self.server._bad_request()
@@ -156,7 +162,7 @@ class AsyncSocket(base_socket.BaseSocket):
156
162
 
157
163
  try:
158
164
  pkt = await websocket_wait()
159
- except IOError: # pragma: no cover
165
+ except OSError: # pragma: no cover
160
166
  return
161
167
  decoded_pkt = packet.Packet(encoded_packet=pkt)
162
168
  if decoded_pkt.packet_type != packet.PING or \
@@ -170,7 +176,7 @@ class AsyncSocket(base_socket.BaseSocket):
170
176
 
171
177
  try:
172
178
  pkt = await websocket_wait()
173
- except IOError: # pragma: no cover
179
+ except OSError: # pragma: no cover
174
180
  self.upgrading = False
175
181
  return
176
182
  decoded_pkt = packet.Packet(encoded_packet=pkt)
@@ -251,4 +257,5 @@ class AsyncSocket(base_socket.BaseSocket):
251
257
 
252
258
  await self.queue.put(None) # unlock the writer task so it can exit
253
259
  await asyncio.wait_for(writer_task, timeout=None)
254
- await self.close(wait=False, abort=True)
260
+ await self.close(wait=False, abort=True,
261
+ reason=self.server.reason.TRANSPORT_CLOSE)
engineio/base_client.py CHANGED
@@ -30,9 +30,18 @@ original_signal_handler = None
30
30
  class BaseClient:
31
31
  event_names = ['connect', 'disconnect', 'message']
32
32
 
33
+ class reason:
34
+ """Disconnection reasons."""
35
+ #: Client-initiated disconnection.
36
+ CLIENT_DISCONNECT = 'client disconnect'
37
+ #: Server-initiated disconnection.
38
+ SERVER_DISCONNECT = 'server disconnect'
39
+ #: Transport error.
40
+ TRANSPORT_ERROR = 'transport error'
41
+
33
42
  def __init__(self, logger=False, json=None, request_timeout=5,
34
43
  http_session=None, ssl_verify=True, handle_sigint=True,
35
- websocket_extra_options=None):
44
+ websocket_extra_options=None, timestamp_requests=True):
36
45
  global original_signal_handler
37
46
  if handle_sigint and original_signal_handler is None and \
38
47
  threading.current_thread() == threading.main_thread():
@@ -56,6 +65,7 @@ class BaseClient:
56
65
  self.state = 'disconnected'
57
66
  self.ssl_verify = ssl_verify
58
67
  self.websocket_extra_options = websocket_extra_options or {}
68
+ self.timestamp_requests = timestamp_requests
59
69
 
60
70
  if json is not None:
61
71
  packet.Packet.json = json
@@ -143,4 +153,6 @@ class BaseClient:
143
153
 
144
154
  def _get_url_timestamp(self):
145
155
  """Generate the Engine.IO query string timestamp."""
156
+ if not self.timestamp_requests:
157
+ return ''
146
158
  return '&t=' + str(time.time())
engineio/base_server.py CHANGED
@@ -19,6 +19,19 @@ class BaseServer:
19
19
  _default_monitor_clients = True
20
20
  sequence_number = 0
21
21
 
22
+ class reason:
23
+ """Disconnection reasons."""
24
+ #: Server-initiated disconnection.
25
+ SERVER_DISCONNECT = 'server disconnect'
26
+ #: Client-initiated disconnection.
27
+ CLIENT_DISCONNECT = 'client disconnect'
28
+ #: Ping timeout.
29
+ PING_TIMEOUT = 'ping timeout'
30
+ #: Transport close.
31
+ TRANSPORT_CLOSE = 'transport close'
32
+ #: Transport error.
33
+ TRANSPORT_ERROR = 'transport error'
34
+
22
35
  def __init__(self, async_mode=None, ping_interval=25, ping_timeout=20,
23
36
  max_http_buffer_size=1000000, allow_upgrades=True,
24
37
  http_compression=True, compression_threshold=1024,
engineio/base_socket.py CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  class BaseSocket:
3
2
  upgrade_protocols = ['websocket']
4
3
 
engineio/client.py CHANGED
@@ -55,6 +55,9 @@ class Client(base_client.BaseClient):
55
55
  :param websocket_extra_options: Dictionary containing additional keyword
56
56
  arguments passed to
57
57
  ``websocket.create_connection()``.
58
+ :param timestamp_requests: If ``True`` a timestamp is added to the query
59
+ string of Socket.IO requests as a cache-busting
60
+ measure. Set to ``False`` to disable.
58
61
  """
59
62
  def connect(self, url, headers=None, transports=None,
60
63
  engineio_path='engine.io'):
@@ -110,7 +113,7 @@ class Client(base_client.BaseClient):
110
113
  """
111
114
  self._send_packet(packet.Packet(packet.MESSAGE, data=data))
112
115
 
113
- def disconnect(self, abort=False):
116
+ def disconnect(self, abort=False, reason=None):
114
117
  """Disconnect from the server.
115
118
 
116
119
  :param abort: If set to ``True``, do not wait for background tasks
@@ -120,7 +123,9 @@ class Client(base_client.BaseClient):
120
123
  self._send_packet(packet.Packet(packet.CLOSE))
121
124
  self.queue.put(None)
122
125
  self.state = 'disconnecting'
123
- self._trigger_event('disconnect', run_async=False)
126
+ self._trigger_event('disconnect',
127
+ reason or self.reason.CLIENT_DISCONNECT,
128
+ run_async=False)
124
129
  if self.current_transport == 'websocket':
125
130
  self.ws.close()
126
131
  if not abort:
@@ -251,7 +256,7 @@ class Client(base_client.BaseClient):
251
256
  extra_options = {}
252
257
  if self.http:
253
258
  # cookies
254
- cookies = '; '.join(["{}={}".format(cookie.name, cookie.value)
259
+ cookies = '; '.join([f"{cookie.name}={cookie.value}"
255
260
  for cookie in self.http.cookies])
256
261
  for header, value in headers.items():
257
262
  if header.lower() == 'cookie':
@@ -325,7 +330,7 @@ class Client(base_client.BaseClient):
325
330
  try:
326
331
  ws = websocket.create_connection(
327
332
  websocket_url + self._get_url_timestamp(), **extra_options)
328
- except (ConnectionError, IOError, websocket.WebSocketException):
333
+ except (ConnectionError, OSError, websocket.WebSocketException):
329
334
  if upgrade:
330
335
  self.logger.warning(
331
336
  'WebSocket upgrade failed: connection error')
@@ -404,7 +409,7 @@ class Client(base_client.BaseClient):
404
409
  elif pkt.packet_type == packet.PING:
405
410
  self._send_packet(packet.Packet(packet.PONG, pkt.data))
406
411
  elif pkt.packet_type == packet.CLOSE:
407
- self.disconnect(abort=True)
412
+ self.disconnect(abort=True, reason=self.reason.SERVER_DISCONNECT)
408
413
  elif pkt.packet_type == packet.NOOP:
409
414
  pass
410
415
  else:
@@ -444,7 +449,16 @@ class Client(base_client.BaseClient):
444
449
  return self.start_background_task(self.handlers[event], *args)
445
450
  else:
446
451
  try:
447
- return self.handlers[event](*args)
452
+ try:
453
+ return self.handlers[event](*args)
454
+ except TypeError:
455
+ if event == 'disconnect' and \
456
+ len(args) == 1: # pragma: no branch
457
+ # legacy disconnect events do not have a reason
458
+ # argument
459
+ return self.handlers[event]()
460
+ else: # pragma: no cover
461
+ raise
448
462
  except:
449
463
  self.logger.exception(event + ' handler error')
450
464
 
@@ -480,7 +494,8 @@ class Client(base_client.BaseClient):
480
494
  self.logger.info('Waiting for write loop task to end')
481
495
  self.write_loop_task.join()
482
496
  if self.state == 'connected':
483
- self._trigger_event('disconnect', run_async=False)
497
+ self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR,
498
+ run_async=False)
484
499
  try:
485
500
  base_client.connected_clients.remove(self)
486
501
  except ValueError: # pragma: no cover
@@ -530,7 +545,8 @@ class Client(base_client.BaseClient):
530
545
  self.logger.info('Waiting for write loop task to end')
531
546
  self.write_loop_task.join()
532
547
  if self.state == 'connected':
533
- self._trigger_event('disconnect', run_async=False)
548
+ self._trigger_event('disconnect', self.reason.TRANSPORT_ERROR,
549
+ run_async=False)
534
550
  try:
535
551
  base_client.connected_clients.remove(self)
536
552
  except ValueError: # pragma: no cover
engineio/middleware.py CHANGED
@@ -2,7 +2,7 @@ import os
2
2
  from engineio.static_files import get_static_file
3
3
 
4
4
 
5
- class WSGIApp(object):
5
+ class WSGIApp:
6
6
  """WSGI application middleware for Engine.IO.
7
7
 
8
8
  This middleware dispatches traffic to an Engine.IO application. It can
@@ -50,7 +50,7 @@ class WSGIApp(object):
50
50
  # directly into the environment. To give eventlet's WebSocket
51
51
  # module access to this socket when running under gunicorn, here we
52
52
  # copy the socket to the eventlet format.
53
- class Input(object):
53
+ class Input:
54
54
  def __init__(self, socket):
55
55
  self.socket = socket
56
56
 
engineio/packet.py CHANGED
@@ -7,7 +7,7 @@ packet_names = ['OPEN', 'CLOSE', 'PING', 'PONG', 'MESSAGE', 'UPGRADE', 'NOOP']
7
7
  binary_types = (bytes, bytearray)
8
8
 
9
9
 
10
- class Packet(object):
10
+ class Packet:
11
11
  """Engine.IO packet."""
12
12
 
13
13
  json = _json
engineio/payload.py CHANGED
@@ -3,7 +3,7 @@ import urllib
3
3
  from . import packet
4
4
 
5
5
 
6
- class Payload(object):
6
+ class Payload:
7
7
  """Engine.IO payload."""
8
8
  max_decode_packets = 16
9
9
 
engineio/server.py CHANGED
@@ -148,7 +148,7 @@ class Server(base_server.BaseServer):
148
148
  with eio.session(sid) as session:
149
149
  print('received message from ', session['username'])
150
150
  """
151
- class _session_context_manager(object):
151
+ class _session_context_manager:
152
152
  def __init__(self, server, sid):
153
153
  self.server = server
154
154
  self.sid = sid
@@ -176,12 +176,12 @@ class Server(base_server.BaseServer):
176
176
  # the socket was already closed or gone
177
177
  pass
178
178
  else:
179
- socket.close()
179
+ socket.close(reason=self.reason.SERVER_DISCONNECT)
180
180
  if sid in self.sockets: # pragma: no cover
181
181
  del self.sockets[sid]
182
182
  else:
183
183
  for client in self.sockets.copy().values():
184
- client.close()
184
+ client.close(reason=self.reason.SERVER_DISCONNECT)
185
185
  self.sockets = {}
186
186
 
187
187
  def handle_request(self, environ, start_response):
@@ -251,11 +251,11 @@ class Server(base_server.BaseServer):
251
251
  'bad-jsonp-index')
252
252
  r = self._bad_request('Invalid JSONP index number')
253
253
  elif method == 'GET':
254
+ upgrade_header = environ.get('HTTP_UPGRADE').lower() \
255
+ if 'HTTP_UPGRADE' in environ else None
254
256
  if sid is None:
255
257
  # transport must be one of 'polling' or 'websocket'.
256
258
  # if 'websocket', the HTTP_UPGRADE header must match.
257
- upgrade_header = environ.get('HTTP_UPGRADE').lower() \
258
- if 'HTTP_UPGRADE' in environ else None
259
259
  if transport == 'polling' \
260
260
  or transport == upgrade_header == 'websocket':
261
261
  r = self._handle_connect(environ, start_response,
@@ -266,34 +266,41 @@ class Server(base_server.BaseServer):
266
266
  r = self._bad_request('Invalid websocket upgrade')
267
267
  else:
268
268
  if sid not in self.sockets:
269
- self._log_error_once('Invalid session ' + sid, 'bad-sid')
270
- r = self._bad_request('Invalid session')
269
+ self._log_error_once(f'Invalid session {sid}', 'bad-sid')
270
+ r = self._bad_request(f'Invalid session {sid}')
271
271
  else:
272
- socket = self._get_socket(sid)
273
- if self.transport(sid) != transport:
274
- self._log_error_once(
275
- 'Invalid transport for session ' + sid,
276
- 'bad-transport')
277
- r = self._bad_request('Invalid transport')
272
+ try:
273
+ socket = self._get_socket(sid)
274
+ except KeyError as e: # pragma: no cover
275
+ self._log_error_once(f'{e} {sid}', 'bad-sid')
276
+ r = self._bad_request(f'{e} {sid}')
278
277
  else:
279
- try:
280
- packets = socket.handle_get_request(
281
- environ, start_response)
282
- if isinstance(packets, list):
283
- r = self._ok(packets, jsonp_index=jsonp_index)
284
- else:
285
- r = packets
286
- except exceptions.EngineIOError:
287
- if sid in self.sockets: # pragma: no cover
288
- self.disconnect(sid)
289
- r = self._bad_request()
290
- if sid in self.sockets and self.sockets[sid].closed:
291
- del self.sockets[sid]
278
+ if self.transport(sid) != transport and \
279
+ transport != upgrade_header:
280
+ self._log_error_once(
281
+ f'Invalid transport for session {sid}',
282
+ 'bad-transport')
283
+ r = self._bad_request('Invalid transport')
284
+ else:
285
+ try:
286
+ packets = socket.handle_get_request(
287
+ environ, start_response)
288
+ if isinstance(packets, list):
289
+ r = self._ok(packets,
290
+ jsonp_index=jsonp_index)
291
+ else:
292
+ r = packets
293
+ except exceptions.EngineIOError:
294
+ if sid in self.sockets: # pragma: no cover
295
+ self.disconnect(sid)
296
+ r = self._bad_request()
297
+ if sid in self.sockets and \
298
+ self.sockets[sid].closed:
299
+ del self.sockets[sid]
292
300
  elif method == 'POST':
293
301
  if sid is None or sid not in self.sockets:
294
- self._log_error_once(
295
- 'Invalid session ' + (sid or 'None'), 'bad-sid')
296
- r = self._bad_request('Invalid session')
302
+ self._log_error_once(f'Invalid session {sid}', 'bad-sid')
303
+ r = self._bad_request(f'Invalid session {sid}')
297
304
  else:
298
305
  socket = self._get_socket(sid)
299
306
  try:
@@ -389,7 +396,9 @@ class Server(base_server.BaseServer):
389
396
  'upgrades': self._upgrades(sid, transport),
390
397
  'pingTimeout': int(self.ping_timeout * 1000),
391
398
  'pingInterval': int(
392
- self.ping_interval + self.ping_interval_grace_period) * 1000})
399
+ self.ping_interval + self.ping_interval_grace_period) * 1000,
400
+ 'maxPayload': self.max_http_buffer_size,
401
+ })
393
402
  s.send(pkt)
394
403
  s.schedule_ping()
395
404
 
@@ -437,7 +446,16 @@ class Server(base_server.BaseServer):
437
446
  if event in self.handlers:
438
447
  def run_handler():
439
448
  try:
440
- return self.handlers[event](*args)
449
+ try:
450
+ return self.handlers[event](*args)
451
+ except TypeError:
452
+ if event == 'disconnect' and \
453
+ len(args) == 2: # pragma: no branch
454
+ # legacy disconnect events do not have a reason
455
+ # argument
456
+ return self.handlers[event](args[0])
457
+ else: # pragma: no cover
458
+ raise
441
459
  except:
442
460
  self.logger.exception(event + ' handler error')
443
461
  if event == 'connect':
engineio/socket.py CHANGED
@@ -48,7 +48,8 @@ class Socket(base_socket.BaseSocket):
48
48
  elif pkt.packet_type == packet.UPGRADE:
49
49
  self.send(packet.Packet(packet.NOOP))
50
50
  elif pkt.packet_type == packet.CLOSE:
51
- self.close(wait=False, abort=True)
51
+ self.close(wait=False, abort=True,
52
+ reason=self.server.reason.CLIENT_DISCONNECT)
52
53
  else:
53
54
  raise exceptions.UnknownPacketError()
54
55
 
@@ -63,7 +64,8 @@ class Socket(base_socket.BaseSocket):
63
64
  # Passing abort=False here will cause close() to write a
64
65
  # CLOSE packet. This has the effect of updating half-open sockets
65
66
  # to their correct state of disconnected
66
- self.close(wait=False, abort=False)
67
+ self.close(wait=False, abort=False,
68
+ reason=self.server.reason.PING_TIMEOUT)
67
69
  return False
68
70
  return True
69
71
 
@@ -97,7 +99,7 @@ class Socket(base_socket.BaseSocket):
97
99
  packets = self.poll()
98
100
  except exceptions.QueueEmpty:
99
101
  exc = sys.exc_info()
100
- self.close(wait=False)
102
+ self.close(wait=False, reason=self.server.reason.TRANSPORT_ERROR)
101
103
  raise exc[1].with_traceback(exc[2])
102
104
  return packets
103
105
 
@@ -112,11 +114,14 @@ class Socket(base_socket.BaseSocket):
112
114
  for pkt in p.packets:
113
115
  self.receive(pkt)
114
116
 
115
- def close(self, wait=True, abort=False):
117
+ def close(self, wait=True, abort=False, reason=None):
116
118
  """Close the socket connection."""
117
119
  if not self.closed and not self.closing:
118
120
  self.closing = True
119
- self.server._trigger_event('disconnect', self.sid, run_async=False)
121
+ self.server._trigger_event(
122
+ 'disconnect', self.sid,
123
+ reason or self.server.reason.SERVER_DISCONNECT,
124
+ run_async=False)
120
125
  if not abort:
121
126
  self.send(packet.Packet(packet.CLOSE))
122
127
  self.closed = True
@@ -137,7 +142,7 @@ class Socket(base_socket.BaseSocket):
137
142
  def _upgrade_websocket(self, environ, start_response):
138
143
  """Upgrade the connection from polling to websocket."""
139
144
  if self.upgraded:
140
- raise IOError('Socket has been upgraded already')
145
+ raise OSError('Socket has been upgraded already')
141
146
  if self.server._async['websocket'] is None:
142
147
  # the selected async mode does not support websocket
143
148
  return self.server._bad_request()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-engineio
3
- Version: 4.10.0
3
+ Version: 4.11.0
4
4
  Summary: Engine.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-engineio
@@ -13,14 +13,14 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.6
14
14
  Description-Content-Type: text/markdown
15
15
  License-File: LICENSE
16
- Requires-Dist: simple-websocket >=0.10.0
17
- Provides-Extra: asyncio_client
18
- Requires-Dist: aiohttp >=3.4 ; extra == 'asyncio_client'
16
+ Requires-Dist: simple-websocket>=0.10.0
19
17
  Provides-Extra: client
20
- Requires-Dist: requests >=2.21.0 ; extra == 'client'
21
- Requires-Dist: websocket-client >=0.54.0 ; extra == 'client'
18
+ Requires-Dist: requests>=2.21.0; extra == "client"
19
+ Requires-Dist: websocket-client>=0.54.0; extra == "client"
20
+ Provides-Extra: asyncio-client
21
+ Requires-Dist: aiohttp>=3.4; extra == "asyncio-client"
22
22
  Provides-Extra: docs
23
- Requires-Dist: sphinx ; extra == 'docs'
23
+ Requires-Dist: sphinx; extra == "docs"
24
24
 
25
25
  python-engineio
26
26
  ===============
@@ -0,0 +1,31 @@
1
+ engineio/__init__.py,sha256=0R2PY1EXu3sicP7mkA0_QxEVGRlFlgvsxfhByqREE1A,481
2
+ engineio/async_client.py,sha256=jBy7ytWawPpP3b_52U7YU7G85wbWjMsdw_29y3e_BqA,29548
3
+ engineio/async_server.py,sha256=8Af_uwf8mKOCJGqVKa3QN0kz7_B2K1cdpSmsvkAoBR4,27412
4
+ engineio/async_socket.py,sha256=nHY0DPPk0FtI9djUnQWtzZ3ce2OD184Tu-Dop7JLg9I,10715
5
+ engineio/base_client.py,sha256=uEI6OglvhdIGD1DAZuptbn0KXQMk_o3w0AH4wJvPwZ0,5322
6
+ engineio/base_server.py,sha256=H-aKQ9hMQjiIrjDaqbw_edw9yUVIbCQMzyIy9egLv-g,14458
7
+ engineio/base_socket.py,sha256=sQqbNSfGhMQG3xzwar6IXMal28C7Q5TIAQRGp74Wt2o,399
8
+ engineio/client.py,sha256=wfgB_MrpEh-v4dBt6W2eW6i0CRNEP0CIJ6d4Ap4CnZE,27227
9
+ engineio/exceptions.py,sha256=FyuMb5qhX9CUYP3fEoe1m-faU96ApdQTSbblaaoo8LA,292
10
+ engineio/json.py,sha256=SG5FTojqd1ix6u0dKXJsZVqqdYioZLO4S2GPL7BKl3U,405
11
+ engineio/middleware.py,sha256=5NKBXz-ftuFErUB_V9IDvRHaSOsjhtW-NnuJtquB1nc,3750
12
+ engineio/packet.py,sha256=Tejm9U5JcYs5LwZ_n_Xh0PIRv-U_JbHwGEivNXQN4eg,3181
13
+ engineio/payload.py,sha256=GIWu0Vnay4WNZlDxHqVgP34tKTBXX58OArJ-mO5zD3E,1539
14
+ engineio/server.py,sha256=4_xdtH0tyusigQurwOeDFrHtodthFTOIbOVZ-_ckh5U,22957
15
+ engineio/socket.py,sha256=K5gSY5PTi6rZy4meP9CtTYsNlEU2JmsonYDDHfJVFI4,10280
16
+ engineio/static_files.py,sha256=pwez9LQFaSQXMbtI0vLyD6UDiokQ4rNfmRYgVLKOthc,2064
17
+ engineio/async_drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ engineio/async_drivers/_websocket_wsgi.py,sha256=LuOEfKhbAw8SplB5PMpYKIUqfCPEadQEpqeiq_leOIA,949
19
+ engineio/async_drivers/aiohttp.py,sha256=OBDGhaNXWHxQkwhzZT2vlTAOqWReGS6Sjk9u3BEh_Mc,3754
20
+ engineio/async_drivers/asgi.py,sha256=mBu109j7R6esH-wI62jvTnfVAEjWvw2YNp0dZ74NyHg,11210
21
+ engineio/async_drivers/eventlet.py,sha256=n1y4OjPdj4J2GIep5N56O29oa5NQgFJVcTBjyO1C-Gs,1735
22
+ engineio/async_drivers/gevent.py,sha256=hnJHeWdDQE2jfoLCP5DnwVPzsQlcTLJUMA5EVf1UL-k,2962
23
+ engineio/async_drivers/gevent_uwsgi.py,sha256=m6ay5dov9FDQl0fbeiKeE-Orh5LiF6zLlYQ64Oa3T5g,5954
24
+ engineio/async_drivers/sanic.py,sha256=VvHZlkOBdHPkA620-Z9EdzpPp-O-KhMgS-h7RCuw_a4,4516
25
+ engineio/async_drivers/threading.py,sha256=ywmG59d4H6OHZjKarBN97-9BHEsRxFEz9YN-E9QAu_I,463
26
+ engineio/async_drivers/tornado.py,sha256=mbVHs1mECfzFSNv33uigkpTBtNPT0u49k5zaybewdIo,5893
27
+ python_engineio-4.11.0.dist-info/LICENSE,sha256=yel9Pbwfu82094CLKCzWRtuIev9PUxP-a76NTDFAWpw,1082
28
+ python_engineio-4.11.0.dist-info/METADATA,sha256=ptID9L-diLLf_PVNRydyfripogmQvTtlqerR9yURhMA,2237
29
+ python_engineio-4.11.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
30
+ python_engineio-4.11.0.dist-info/top_level.txt,sha256=u8PmNisCZLwRYcWrNLe9wutQ2tt4zNi8IH362c-HWuA,9
31
+ python_engineio-4.11.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,31 +0,0 @@
1
- engineio/__init__.py,sha256=0R2PY1EXu3sicP7mkA0_QxEVGRlFlgvsxfhByqREE1A,481
2
- engineio/async_client.py,sha256=jC7T6r_ya9LV46q4OdIQ041-3uLlRr6aUt__YmHNPEM,27950
3
- engineio/async_server.py,sha256=q29JCvIpCriUXZoqHwjJUwV2bK2MRypMQptROGw6imI,25738
4
- engineio/async_socket.py,sha256=P8OZW1N5y7jr2hW1qLLK2y_Clt7aHdv2ucYW69mbU4Q,10305
5
- engineio/base_client.py,sha256=Q_w0Stvy89wPHwWk7411dRxpGjxNPypm4xy_4XMPZ9M,4872
6
- engineio/base_server.py,sha256=Em2RRpbohulKjmYTWHnf_lALKvTT_jHr442Rr2g_BEQ,14013
7
- engineio/base_socket.py,sha256=Bw6TWv1pnlXcDdqT59C5CyTaBi7l-_mkykIUuZLjz-g,400
8
- engineio/client.py,sha256=m_eLSes3b-mE5MYajELquSktkAWceCNGZOtUU78as50,26285
9
- engineio/exceptions.py,sha256=FyuMb5qhX9CUYP3fEoe1m-faU96ApdQTSbblaaoo8LA,292
10
- engineio/json.py,sha256=SG5FTojqd1ix6u0dKXJsZVqqdYioZLO4S2GPL7BKl3U,405
11
- engineio/middleware.py,sha256=BF_qHAIZZnIbfiP256SD1CX3kzNWbSuto1cpih8oIFg,3766
12
- engineio/packet.py,sha256=ETMeLgdpZghXK9fth93IZO8pIft6Sg3d1QGpyTx4xBE,3189
13
- engineio/payload.py,sha256=2iLIFgIweTWkLok_UZ5zCgELmRSGyUUI5eeYcEerFSs,1547
14
- engineio/server.py,sha256=v0QnoLCD1ReVAhzQRn5keHmjZ61jEJXNQeikfXaELl0,21926
15
- engineio/socket.py,sha256=dasec3jXoV2eR1jmIrhEIfHzyUuuv8FwvxLB3DpyjeY,9996
16
- engineio/static_files.py,sha256=pwez9LQFaSQXMbtI0vLyD6UDiokQ4rNfmRYgVLKOthc,2064
17
- engineio/async_drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- engineio/async_drivers/_websocket_wsgi.py,sha256=FGoRBGOUsEtlfklYNylR0o3oLOGmXIR8QzaCsGLgr3I,949
19
- engineio/async_drivers/aiohttp.py,sha256=zJdujjO6dSL_oKDDr4xfO4ID_Vd8faEp1AOIW4ogKME,3768
20
- engineio/async_drivers/asgi.py,sha256=mvCErNpYw1T7XE_1Ixb0YQN0YqJkKMpWqNV5_tZQYMg,11011
21
- engineio/async_drivers/eventlet.py,sha256=_ZgPg0HTx_yhSm5iBcd3ez01yMg3KRFmb2ycKD8o8pY,1775
22
- engineio/async_drivers/gevent.py,sha256=hnJHeWdDQE2jfoLCP5DnwVPzsQlcTLJUMA5EVf1UL-k,2962
23
- engineio/async_drivers/gevent_uwsgi.py,sha256=cnjCsnDHTa6rKgwDKD6rLvIw1Yun-g4c1QbujOC_bMY,5962
24
- engineio/async_drivers/sanic.py,sha256=SY0HIp5DUHF7B55tJCBB_8qDjrRTD_FyNQ2cIwRIGR8,4538
25
- engineio/async_drivers/threading.py,sha256=ywmG59d4H6OHZjKarBN97-9BHEsRxFEz9YN-E9QAu_I,463
26
- engineio/async_drivers/tornado.py,sha256=9bB7FvY47Snx_h4rsNwRk5wIINf2ju7hXWTAqF3intA,5909
27
- python_engineio-4.10.0.dist-info/LICENSE,sha256=yel9Pbwfu82094CLKCzWRtuIev9PUxP-a76NTDFAWpw,1082
28
- python_engineio-4.10.0.dist-info/METADATA,sha256=SaBVFlF_cqRa2i_sQ3Fdiaz6GdxT1qASxZpSugLD57A,2245
29
- python_engineio-4.10.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
30
- python_engineio-4.10.0.dist-info/top_level.txt,sha256=u8PmNisCZLwRYcWrNLe9wutQ2tt4zNi8IH362c-HWuA,9
31
- python_engineio-4.10.0.dist-info/RECORD,,