python-engineio 4.8.2__tar.gz → 4.9.1__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-engineio-4.8.2/src/python_engineio.egg-info → python_engineio-4.9.1}/PKG-INFO +1 -1
- {python-engineio-4.8.2 → python_engineio-4.9.1}/pyproject.toml +1 -1
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_client.py +12 -8
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/asgi.py +17 -10
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_server.py +4 -3
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/client.py +20 -10
- {python-engineio-4.8.2 → python_engineio-4.9.1/src/python_engineio.egg-info}/PKG-INFO +1 -1
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_asgi.py +27 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_client.py +2 -1
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_client.py +2 -1
- {python-engineio-4.8.2 → python_engineio-4.9.1}/LICENSE +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/MANIFEST.in +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/README.md +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/Makefile +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/_static/README.md +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/api.rst +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/client.rst +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/conf.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/index.rst +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/intro.rst +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/make.bat +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/docs/server.rst +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/setup.cfg +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/__init__.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/__init__.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/_websocket_wsgi.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/aiohttp.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/eventlet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/gevent.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/gevent_uwsgi.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/sanic.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/threading.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/tornado.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_socket.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/base_client.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/base_server.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/base_socket.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/exceptions.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/json.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/middleware.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/packet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/payload.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/server.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/socket.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/static_files.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/python_engineio.egg-info/SOURCES.txt +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/python_engineio.egg-info/dependency_links.txt +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/python_engineio.egg-info/not-zip-safe +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/python_engineio.egg-info/requires.txt +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/src/python_engineio.egg-info/top_level.txt +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/__init__.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/__init__.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/index.html +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_aiohttp.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_sanic.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_server.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_socket.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/async/test_tornado.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/__init__.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/index.html +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_async_eventlet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_middleware.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_packet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_payload.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_server.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/common/test_socket.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/README.md +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/binary_b64_packet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/binary_packet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/json_packet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/payload.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/run.sh +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/server_receive.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tests/performance/text_packet.py +0 -0
- {python-engineio-4.8.2 → python_engineio-4.9.1}/tox.ini +0 -0
|
@@ -493,7 +493,7 @@ class AsyncClient(base_client.BaseClient):
|
|
|
493
493
|
|
|
494
494
|
async def _read_loop_polling(self):
|
|
495
495
|
"""Read packets by polling the Engine.IO server."""
|
|
496
|
-
while self.state == 'connected':
|
|
496
|
+
while self.state == 'connected' and self.write_loop_task:
|
|
497
497
|
self.logger.info(
|
|
498
498
|
'Sending polling GET request to ' + self.base_url)
|
|
499
499
|
r = await self._send_request(
|
|
@@ -520,8 +520,9 @@ class AsyncClient(base_client.BaseClient):
|
|
|
520
520
|
for pkt in p.packets:
|
|
521
521
|
await self._receive_packet(pkt)
|
|
522
522
|
|
|
523
|
-
self.
|
|
524
|
-
|
|
523
|
+
if self.write_loop_task: # pragma: no branch
|
|
524
|
+
self.logger.info('Waiting for write loop task to end')
|
|
525
|
+
await self.write_loop_task
|
|
525
526
|
if self.state == 'connected':
|
|
526
527
|
await self._trigger_event('disconnect', run_async=False)
|
|
527
528
|
try:
|
|
@@ -541,8 +542,10 @@ class AsyncClient(base_client.BaseClient):
|
|
|
541
542
|
timeout=self.ping_interval + self.ping_timeout)
|
|
542
543
|
if not isinstance(p.data, (str, bytes)): # pragma: no cover
|
|
543
544
|
self.logger.warning(
|
|
544
|
-
'Server sent
|
|
545
|
-
|
|
545
|
+
'Server sent %s packet data %s, aborting',
|
|
546
|
+
'close' if p.type in [aiohttp.WSMsgType.CLOSE,
|
|
547
|
+
aiohttp.WSMsgType.CLOSING]
|
|
548
|
+
else str(p.type), str(p.data))
|
|
546
549
|
await self.queue.put(None)
|
|
547
550
|
break # the connection is broken
|
|
548
551
|
p = p.data
|
|
@@ -571,8 +574,9 @@ class AsyncClient(base_client.BaseClient):
|
|
|
571
574
|
break
|
|
572
575
|
await self._receive_packet(pkt)
|
|
573
576
|
|
|
574
|
-
self.
|
|
575
|
-
|
|
577
|
+
if self.write_loop_task: # pragma: no branch
|
|
578
|
+
self.logger.info('Waiting for write loop task to end')
|
|
579
|
+
await self.write_loop_task
|
|
576
580
|
if self.state == 'connected':
|
|
577
581
|
await self._trigger_event('disconnect', run_async=False)
|
|
578
582
|
try:
|
|
@@ -630,7 +634,7 @@ class AsyncClient(base_client.BaseClient):
|
|
|
630
634
|
if r.status < 200 or r.status >= 300:
|
|
631
635
|
self.logger.warning('Unexpected status code %s in server '
|
|
632
636
|
'response, aborting', r.status)
|
|
633
|
-
|
|
637
|
+
self.write_loop_task = None
|
|
634
638
|
break
|
|
635
639
|
else:
|
|
636
640
|
# websocket
|
|
@@ -19,7 +19,12 @@ class ASGIApp:
|
|
|
19
19
|
:param other_asgi_app: A separate ASGI app that receives all other traffic.
|
|
20
20
|
:param engineio_path: The endpoint where the Engine.IO application should
|
|
21
21
|
be installed. The default value is appropriate for
|
|
22
|
-
most cases.
|
|
22
|
+
most cases. With a value of ``None``, all incoming
|
|
23
|
+
traffic is directed to the Engine.IO server, with the
|
|
24
|
+
assumption that routing, if necessary, is handled by
|
|
25
|
+
a different layer. When this option is set to
|
|
26
|
+
``None``, ``static_files`` and ``other_asgi_app`` are
|
|
27
|
+
ignored.
|
|
23
28
|
:param on_startup: function to be called on application startup; can be
|
|
24
29
|
coroutine
|
|
25
30
|
:param on_shutdown: function to be called on application shutdown; can be
|
|
@@ -44,24 +49,26 @@ class ASGIApp:
|
|
|
44
49
|
self.engineio_server = engineio_server
|
|
45
50
|
self.other_asgi_app = other_asgi_app
|
|
46
51
|
self.engineio_path = engineio_path
|
|
47
|
-
if
|
|
48
|
-
self.engineio_path
|
|
49
|
-
|
|
50
|
-
self.engineio_path
|
|
52
|
+
if self.engineio_path is not None:
|
|
53
|
+
if not self.engineio_path.startswith('/'):
|
|
54
|
+
self.engineio_path = '/' + self.engineio_path
|
|
55
|
+
if not self.engineio_path.endswith('/'):
|
|
56
|
+
self.engineio_path += '/'
|
|
51
57
|
self.static_files = static_files or {}
|
|
52
58
|
self.on_startup = on_startup
|
|
53
59
|
self.on_shutdown = on_shutdown
|
|
54
60
|
|
|
55
61
|
async def __call__(self, scope, receive, send):
|
|
56
|
-
if scope['type']
|
|
57
|
-
|
|
62
|
+
if scope['type'] == 'lifespan':
|
|
63
|
+
await self.lifespan(scope, receive, send)
|
|
64
|
+
elif scope['type'] in ['http', 'websocket'] and (
|
|
65
|
+
self.engineio_path is None
|
|
66
|
+
or scope['path'].startswith(self.engineio_path)):
|
|
58
67
|
await self.engineio_server.handle_request(scope, receive, send)
|
|
59
68
|
else:
|
|
60
69
|
static_file = get_static_file(scope['path'], self.static_files) \
|
|
61
70
|
if scope['type'] == 'http' and self.static_files else None
|
|
62
|
-
if
|
|
63
|
-
await self.lifespan(scope, receive, send)
|
|
64
|
-
elif static_file and os.path.exists(static_file['filename']):
|
|
71
|
+
if static_file and os.path.exists(static_file['filename']):
|
|
65
72
|
await self.serve_static_file(static_file, receive, send)
|
|
66
73
|
elif self.other_asgi_app is not None:
|
|
67
74
|
await self.other_asgi_app(scope, receive, send)
|
|
@@ -524,9 +524,9 @@ class AsyncServer(base_server.BaseServer):
|
|
|
524
524
|
try:
|
|
525
525
|
await asyncio.wait_for(self.service_task_event.wait(),
|
|
526
526
|
timeout=self.ping_timeout)
|
|
527
|
-
except asyncio.TimeoutError:
|
|
528
527
|
break
|
|
529
|
-
|
|
528
|
+
except asyncio.TimeoutError:
|
|
529
|
+
continue
|
|
530
530
|
|
|
531
531
|
# go through the entire client list in a ping interval cycle
|
|
532
532
|
sleep_interval = self.ping_timeout / len(self.sockets)
|
|
@@ -546,8 +546,9 @@ class AsyncServer(base_server.BaseServer):
|
|
|
546
546
|
try:
|
|
547
547
|
await asyncio.wait_for(self.service_task_event.wait(),
|
|
548
548
|
timeout=sleep_interval)
|
|
549
|
-
except asyncio.TimeoutError:
|
|
550
549
|
raise KeyboardInterrupt()
|
|
550
|
+
except asyncio.TimeoutError:
|
|
551
|
+
continue
|
|
551
552
|
except (
|
|
552
553
|
SystemExit,
|
|
553
554
|
KeyboardInterrupt,
|
|
@@ -450,7 +450,7 @@ class Client(base_client.BaseClient):
|
|
|
450
450
|
|
|
451
451
|
def _read_loop_polling(self):
|
|
452
452
|
"""Read packets by polling the Engine.IO server."""
|
|
453
|
-
while self.state == 'connected':
|
|
453
|
+
while self.state == 'connected' and self.write_loop_task:
|
|
454
454
|
self.logger.info(
|
|
455
455
|
'Sending polling GET request to ' + self.base_url)
|
|
456
456
|
r = self._send_request(
|
|
@@ -476,8 +476,9 @@ class Client(base_client.BaseClient):
|
|
|
476
476
|
for pkt in p.packets:
|
|
477
477
|
self._receive_packet(pkt)
|
|
478
478
|
|
|
479
|
-
self.
|
|
480
|
-
|
|
479
|
+
if self.write_loop_task: # pragma: no branch
|
|
480
|
+
self.logger.info('Waiting for write loop task to end')
|
|
481
|
+
self.write_loop_task.join()
|
|
481
482
|
if self.state == 'connected':
|
|
482
483
|
self._trigger_event('disconnect', run_async=False)
|
|
483
484
|
try:
|
|
@@ -493,6 +494,9 @@ class Client(base_client.BaseClient):
|
|
|
493
494
|
p = None
|
|
494
495
|
try:
|
|
495
496
|
p = self.ws.recv()
|
|
497
|
+
if len(p) == 0 and not self.ws.connected: # pragma: no cover
|
|
498
|
+
# websocket client can return an empty string after close
|
|
499
|
+
raise websocket.WebSocketConnectionClosedException()
|
|
496
500
|
except websocket.WebSocketTimeoutException:
|
|
497
501
|
self.logger.warning(
|
|
498
502
|
'Server has stopped communicating, aborting')
|
|
@@ -503,10 +507,15 @@ class Client(base_client.BaseClient):
|
|
|
503
507
|
'WebSocket connection was closed, aborting')
|
|
504
508
|
self.queue.put(None)
|
|
505
509
|
break
|
|
506
|
-
except Exception as e:
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
+
except Exception as e: # pragma: no cover
|
|
511
|
+
if type(e) is OSError and e.errno == 9:
|
|
512
|
+
self.logger.info(
|
|
513
|
+
'WebSocket connection is closing, aborting',
|
|
514
|
+
str(e))
|
|
515
|
+
else:
|
|
516
|
+
self.logger.info(
|
|
517
|
+
'Unexpected error receiving packet: "%s", aborting',
|
|
518
|
+
str(e))
|
|
510
519
|
self.queue.put(None)
|
|
511
520
|
break
|
|
512
521
|
try:
|
|
@@ -518,8 +527,9 @@ class Client(base_client.BaseClient):
|
|
|
518
527
|
break
|
|
519
528
|
self._receive_packet(pkt)
|
|
520
529
|
|
|
521
|
-
self.
|
|
522
|
-
|
|
530
|
+
if self.write_loop_task: # pragma: no branch
|
|
531
|
+
self.logger.info('Waiting for write loop task to end')
|
|
532
|
+
self.write_loop_task.join()
|
|
523
533
|
if self.state == 'connected':
|
|
524
534
|
self._trigger_event('disconnect', run_async=False)
|
|
525
535
|
try:
|
|
@@ -575,7 +585,7 @@ class Client(base_client.BaseClient):
|
|
|
575
585
|
if r.status_code < 200 or r.status_code >= 300:
|
|
576
586
|
self.logger.warning('Unexpected status code %s in server '
|
|
577
587
|
'response, aborting', r.status_code)
|
|
578
|
-
self.
|
|
588
|
+
self.write_loop_task = None
|
|
579
589
|
break
|
|
580
590
|
else:
|
|
581
591
|
# websocket
|
|
@@ -514,3 +514,30 @@ class AsgiTests(unittest.TestCase):
|
|
|
514
514
|
environ['asgi.send'].mock.assert_called_with(
|
|
515
515
|
{'type': 'websocket.close'}
|
|
516
516
|
)
|
|
517
|
+
|
|
518
|
+
def test_sub_app_routing(self):
|
|
519
|
+
|
|
520
|
+
class ASGIDispatcher:
|
|
521
|
+
def __init__(self, routes):
|
|
522
|
+
self.routes = routes
|
|
523
|
+
|
|
524
|
+
async def __call__(self, scope, receive, send):
|
|
525
|
+
path = scope['path']
|
|
526
|
+
for prefix, app in self.routes.items():
|
|
527
|
+
if path.startswith(prefix):
|
|
528
|
+
await app(scope, receive, send)
|
|
529
|
+
return
|
|
530
|
+
assert False, 'No route found'
|
|
531
|
+
|
|
532
|
+
other_app = AsyncMock()
|
|
533
|
+
mock_server = mock.MagicMock()
|
|
534
|
+
mock_server.handle_request = AsyncMock()
|
|
535
|
+
eio_app = async_asgi.ASGIApp(mock_server, engineio_path=None)
|
|
536
|
+
root_app = ASGIDispatcher({'/foo': other_app, '/eio': eio_app})
|
|
537
|
+
scope = {'type': 'http', 'path': '/foo/bar'}
|
|
538
|
+
_run(root_app(scope, 'receive', 'send'))
|
|
539
|
+
other_app.mock.assert_called_once_with(scope, 'receive', 'send')
|
|
540
|
+
scope = {'type': 'http', 'path': '/eio/'}
|
|
541
|
+
_run(root_app(scope, 'receive', 'send'))
|
|
542
|
+
eio_app.engineio_server.handle_request.mock.assert_called_once_with(
|
|
543
|
+
scope, 'receive', 'send')
|
|
@@ -1378,7 +1378,8 @@ class TestAsyncClient(unittest.TestCase):
|
|
|
1378
1378
|
headers={'Content-Type': 'text/plain'},
|
|
1379
1379
|
timeout=5,
|
|
1380
1380
|
)
|
|
1381
|
-
assert c.state == '
|
|
1381
|
+
assert c.state == 'connected'
|
|
1382
|
+
assert c.write_loop_task is None
|
|
1382
1383
|
|
|
1383
1384
|
def test_write_loop_websocket_one_packet(self):
|
|
1384
1385
|
c = async_client.AsyncClient()
|
|
@@ -1631,7 +1631,8 @@ class TestClient(unittest.TestCase):
|
|
|
1631
1631
|
headers={'Content-Type': 'text/plain'},
|
|
1632
1632
|
timeout=5,
|
|
1633
1633
|
)
|
|
1634
|
-
assert c.state == '
|
|
1634
|
+
assert c.state == 'connected'
|
|
1635
|
+
assert c.write_loop_task is None
|
|
1635
1636
|
|
|
1636
1637
|
def test_write_loop_websocket_one_packet(self):
|
|
1637
1638
|
c = client.Client()
|
|
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-engineio-4.8.2 → python_engineio-4.9.1}/src/engineio/async_drivers/_websocket_wsgi.py
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
|
{python-engineio-4.8.2 → python_engineio-4.9.1}/src/python_engineio.egg-info/dependency_links.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
|