python-engineio 4.9.1__tar.gz → 4.10.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.
Files changed (75) hide show
  1. {python_engineio-4.9.1/src/python_engineio.egg-info → python_engineio-4.10.1}/PKG-INFO +1 -1
  2. {python_engineio-4.9.1 → python_engineio-4.10.1}/pyproject.toml +1 -1
  3. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_client.py +1 -1
  4. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/asgi.py +7 -1
  5. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/eventlet.py +1 -1
  6. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_server.py +25 -14
  7. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/client.py +1 -2
  8. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/server.py +23 -16
  9. {python_engineio-4.9.1 → python_engineio-4.10.1/src/python_engineio.egg-info}/PKG-INFO +1 -1
  10. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/python_engineio.egg-info/SOURCES.txt +0 -1
  11. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_asgi.py +43 -0
  12. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_server.py +26 -0
  13. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/test_server.py +39 -1
  14. {python_engineio-4.9.1 → python_engineio-4.10.1}/tox.ini +2 -2
  15. python_engineio-4.9.1/tests/common/test_async_eventlet.py +0 -29
  16. {python_engineio-4.9.1 → python_engineio-4.10.1}/LICENSE +0 -0
  17. {python_engineio-4.9.1 → python_engineio-4.10.1}/MANIFEST.in +0 -0
  18. {python_engineio-4.9.1 → python_engineio-4.10.1}/README.md +0 -0
  19. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/Makefile +0 -0
  20. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/_static/README.md +0 -0
  21. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/api.rst +0 -0
  22. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/client.rst +0 -0
  23. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/conf.py +0 -0
  24. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/index.rst +0 -0
  25. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/intro.rst +0 -0
  26. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/make.bat +0 -0
  27. {python_engineio-4.9.1 → python_engineio-4.10.1}/docs/server.rst +0 -0
  28. {python_engineio-4.9.1 → python_engineio-4.10.1}/setup.cfg +0 -0
  29. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/__init__.py +0 -0
  30. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/__init__.py +0 -0
  31. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/_websocket_wsgi.py +0 -0
  32. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/aiohttp.py +0 -0
  33. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/gevent.py +0 -0
  34. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/gevent_uwsgi.py +0 -0
  35. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/sanic.py +0 -0
  36. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/threading.py +0 -0
  37. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_drivers/tornado.py +0 -0
  38. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/async_socket.py +0 -0
  39. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/base_client.py +0 -0
  40. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/base_server.py +0 -0
  41. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/base_socket.py +0 -0
  42. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/exceptions.py +0 -0
  43. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/json.py +0 -0
  44. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/middleware.py +0 -0
  45. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/packet.py +0 -0
  46. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/payload.py +0 -0
  47. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/socket.py +0 -0
  48. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/engineio/static_files.py +0 -0
  49. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/python_engineio.egg-info/dependency_links.txt +0 -0
  50. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/python_engineio.egg-info/not-zip-safe +0 -0
  51. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/python_engineio.egg-info/requires.txt +0 -0
  52. {python_engineio-4.9.1 → python_engineio-4.10.1}/src/python_engineio.egg-info/top_level.txt +0 -0
  53. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/__init__.py +0 -0
  54. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/__init__.py +0 -0
  55. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/index.html +0 -0
  56. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_aiohttp.py +0 -0
  57. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_client.py +0 -0
  58. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_sanic.py +0 -0
  59. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_socket.py +0 -0
  60. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/async/test_tornado.py +0 -0
  61. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/__init__.py +0 -0
  62. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/index.html +0 -0
  63. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/test_client.py +0 -0
  64. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/test_middleware.py +0 -0
  65. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/test_packet.py +0 -0
  66. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/test_payload.py +0 -0
  67. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/common/test_socket.py +0 -0
  68. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/README.md +0 -0
  69. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/binary_b64_packet.py +0 -0
  70. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/binary_packet.py +0 -0
  71. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/json_packet.py +0 -0
  72. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/payload.py +0 -0
  73. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/run.sh +0 -0
  74. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/server_receive.py +0 -0
  75. {python_engineio-4.9.1 → python_engineio-4.10.1}/tests/performance/text_packet.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-engineio
3
- Version: 4.9.1
3
+ Version: 4.10.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-engineio"
3
- version = "4.9.1"
3
+ version = "4.10.1"
4
4
  authors = [{name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com"}]
5
5
  description = "Engine.IO server and client for Python"
6
6
  classifiers = [
@@ -62,7 +62,7 @@ class AsyncClient(base_client.BaseClient):
62
62
  :param http_session: an initialized ``aiohttp.ClientSession`` object to be
63
63
  used when sending requests to the server. Use it if
64
64
  you need to add special client options such as proxy
65
- servers, SSL certificates, etc.
65
+ servers, SSL certificates, custom CA bundle, etc.
66
66
  :param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to
67
67
  skip SSL certificate verification, allowing
68
68
  connections to servers with self signed certificates.
@@ -63,7 +63,8 @@ class ASGIApp:
63
63
  await self.lifespan(scope, receive, send)
64
64
  elif scope['type'] in ['http', 'websocket'] and (
65
65
  self.engineio_path is None
66
- or scope['path'].startswith(self.engineio_path)):
66
+ or self._ensure_trailing_slash(scope['path']).startswith(
67
+ self.engineio_path)):
67
68
  await self.engineio_server.handle_request(scope, receive, send)
68
69
  else:
69
70
  static_file = get_static_file(scope['path'], self.static_files) \
@@ -127,6 +128,11 @@ class ASGIApp:
127
128
  await send({'type': 'http.response.body',
128
129
  'body': b'Not Found'})
129
130
 
131
+ def _ensure_trailing_slash(self, path):
132
+ if not path.endswith('/'):
133
+ path += '/'
134
+ return path
135
+
130
136
 
131
137
  async def translate_request(scope, receive, send):
132
138
  class AwaitablePayload(object): # pragma: no cover
@@ -25,7 +25,7 @@ class EventletThread: # pragma: no cover
25
25
  return self.g.wait()
26
26
 
27
27
 
28
- class WebSocketWSGI(_WebSocketWSGI):
28
+ class WebSocketWSGI(_WebSocketWSGI): # pragma: no cover
29
29
  def __init__(self, handler, server):
30
30
  try:
31
31
  super().__init__(
@@ -70,6 +70,10 @@ class AsyncServer(base_server.BaseServer):
70
70
  :param async_handlers: If set to ``True``, run message event handlers in
71
71
  non-blocking threads. To run handlers synchronously,
72
72
  set to ``False``. The default is ``True``.
73
+ :param monitor_clients: If set to ``True``, a background task will ensure
74
+ inactive clients are closed. Set to ``False`` to
75
+ disable the monitoring task (not recommended). The
76
+ default is ``True``.
73
77
  :param transports: The list of allowed transports. Valid transports
74
78
  are ``'polling'`` and ``'websocket'``. Defaults to
75
79
  ``['polling', 'websocket']``.
@@ -265,11 +269,11 @@ class AsyncServer(base_server.BaseServer):
265
269
  'bad-jsonp-index')
266
270
  r = self._bad_request('Invalid JSONP index number')
267
271
  elif method == 'GET':
272
+ upgrade_header = environ.get('HTTP_UPGRADE').lower() \
273
+ if 'HTTP_UPGRADE' in environ else None
268
274
  if sid is None:
269
275
  # transport must be one of 'polling' or 'websocket'.
270
276
  # if 'websocket', the HTTP_UPGRADE header must match.
271
- upgrade_header = environ.get('HTTP_UPGRADE').lower() \
272
- if 'HTTP_UPGRADE' in environ else None
273
277
  if transport == 'polling' \
274
278
  or transport == upgrade_header == 'websocket':
275
279
  r = await self._handle_connect(environ, transport,
@@ -284,18 +288,25 @@ class AsyncServer(base_server.BaseServer):
284
288
  r = self._bad_request('Invalid session ' + sid)
285
289
  else:
286
290
  socket = self._get_socket(sid)
287
- try:
288
- packets = await socket.handle_get_request(environ)
289
- if isinstance(packets, list):
290
- r = self._ok(packets, jsonp_index=jsonp_index)
291
- else:
292
- r = packets
293
- except exceptions.EngineIOError:
294
- if sid in self.sockets: # pragma: no cover
295
- await self.disconnect(sid)
296
- r = self._bad_request()
297
- if sid in self.sockets and self.sockets[sid].closed:
298
- del self.sockets[sid]
291
+ if self.transport(sid) != transport and \
292
+ transport != upgrade_header:
293
+ self._log_error_once(
294
+ 'Invalid transport for session ' + sid,
295
+ 'bad-transport')
296
+ r = self._bad_request('Invalid transport')
297
+ else:
298
+ try:
299
+ packets = await socket.handle_get_request(environ)
300
+ if isinstance(packets, list):
301
+ r = self._ok(packets, jsonp_index=jsonp_index)
302
+ else:
303
+ r = packets
304
+ except exceptions.EngineIOError:
305
+ if sid in self.sockets: # pragma: no cover
306
+ await self.disconnect(sid)
307
+ r = self._bad_request()
308
+ if sid in self.sockets and self.sockets[sid].closed:
309
+ del self.sockets[sid]
299
310
  elif method == 'POST':
300
311
  if sid is None or sid not in self.sockets:
301
312
  self._log_error_once('Invalid session ' + sid, 'bad-sid')
@@ -510,8 +510,7 @@ class Client(base_client.BaseClient):
510
510
  except Exception as e: # pragma: no cover
511
511
  if type(e) is OSError and e.errno == 9:
512
512
  self.logger.info(
513
- 'WebSocket connection is closing, aborting',
514
- str(e))
513
+ 'WebSocket connection is closing, aborting')
515
514
  else:
516
515
  self.logger.info(
517
516
  'Unexpected error receiving packet: "%s", aborting',
@@ -180,7 +180,7 @@ class Server(base_server.BaseServer):
180
180
  if sid in self.sockets: # pragma: no cover
181
181
  del self.sockets[sid]
182
182
  else:
183
- for client in self.sockets.values():
183
+ for client in self.sockets.copy().values():
184
184
  client.close()
185
185
  self.sockets = {}
186
186
 
@@ -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,
@@ -270,19 +270,26 @@ class Server(base_server.BaseServer):
270
270
  r = self._bad_request('Invalid session')
271
271
  else:
272
272
  socket = self._get_socket(sid)
273
- try:
274
- packets = socket.handle_get_request(
275
- environ, start_response)
276
- if isinstance(packets, list):
277
- r = self._ok(packets, jsonp_index=jsonp_index)
278
- else:
279
- r = packets
280
- except exceptions.EngineIOError:
281
- if sid in self.sockets: # pragma: no cover
282
- self.disconnect(sid)
283
- r = self._bad_request()
284
- if sid in self.sockets and self.sockets[sid].closed:
285
- del self.sockets[sid]
273
+ if self.transport(sid) != transport and \
274
+ transport != upgrade_header:
275
+ self._log_error_once(
276
+ 'Invalid transport for session ' + sid,
277
+ 'bad-transport')
278
+ r = self._bad_request('Invalid transport')
279
+ else:
280
+ try:
281
+ packets = socket.handle_get_request(
282
+ environ, start_response)
283
+ if isinstance(packets, list):
284
+ r = self._ok(packets, jsonp_index=jsonp_index)
285
+ else:
286
+ r = packets
287
+ except exceptions.EngineIOError:
288
+ if sid in self.sockets: # pragma: no cover
289
+ self.disconnect(sid)
290
+ r = self._bad_request()
291
+ if sid in self.sockets and self.sockets[sid].closed:
292
+ del self.sockets[sid]
286
293
  elif method == 'POST':
287
294
  if sid is None or sid not in self.sockets:
288
295
  self._log_error_once(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-engineio
3
- Version: 4.9.1
3
+ Version: 4.10.1
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
@@ -56,7 +56,6 @@ tests/async/test_socket.py
56
56
  tests/async/test_tornado.py
57
57
  tests/common/__init__.py
58
58
  tests/common/index.html
59
- tests/common/test_async_eventlet.py
60
59
  tests/common/test_client.py
61
60
  tests/common/test_middleware.py
62
61
  tests/common/test_packet.py
@@ -38,12 +38,55 @@ class AsgiTests(unittest.TestCase):
38
38
  def test_engineio_routing(self):
39
39
  mock_server = mock.MagicMock()
40
40
  mock_server.handle_request = AsyncMock()
41
+
41
42
  app = async_asgi.ASGIApp(mock_server)
42
43
  scope = {'type': 'http', 'path': '/engine.io/'}
43
44
  _run(app(scope, 'receive', 'send'))
44
45
  mock_server.handle_request.mock.assert_called_once_with(
45
46
  scope, 'receive', 'send'
46
47
  )
48
+ mock_server.handle_request.mock.reset_mock()
49
+ scope = {'type': 'http', 'path': '/engine.io/'}
50
+ _run(app(scope, 'receive', 'send'))
51
+ mock_server.handle_request.mock.assert_called_once_with(
52
+ scope, 'receive', 'send'
53
+ )
54
+ mock_server.handle_request.mock.reset_mock()
55
+ scope = {'type': 'http', 'path': '/engine.iofoo/'}
56
+ _run(app(scope, 'receive', AsyncMock()))
57
+ mock_server.handle_request.mock.assert_not_called()
58
+
59
+ app = async_asgi.ASGIApp(mock_server, engineio_path=None)
60
+ mock_server.handle_request.mock.reset_mock()
61
+ scope = {'type': 'http', 'path': '/foo'}
62
+ _run(app(scope, 'receive', 'send'))
63
+ mock_server.handle_request.mock.assert_called_once_with(
64
+ scope, 'receive', 'send'
65
+ )
66
+
67
+ app = async_asgi.ASGIApp(mock_server, engineio_path='mysocket.io')
68
+ mock_server.handle_request.mock.reset_mock()
69
+ scope = {'type': 'http', 'path': '/mysocket.io'}
70
+ _run(app(scope, 'receive', 'send'))
71
+ mock_server.handle_request.mock.assert_called_once_with(
72
+ scope, 'receive', 'send'
73
+ )
74
+ mock_server.handle_request.mock.reset_mock()
75
+ scope = {'type': 'http', 'path': '/mysocket.io/'}
76
+ _run(app(scope, 'receive', 'send'))
77
+ mock_server.handle_request.mock.assert_called_once_with(
78
+ scope, 'receive', 'send'
79
+ )
80
+ mock_server.handle_request.mock.reset_mock()
81
+ scope = {'type': 'http', 'path': '/mysocket.io/foo'}
82
+ _run(app(scope, 'receive', 'send'))
83
+ mock_server.handle_request.mock.assert_called_once_with(
84
+ scope, 'receive', 'send'
85
+ )
86
+ mock_server.handle_request.mock.reset_mock()
87
+ scope = {'type': 'http', 'path': '/mysocket.iofoo'}
88
+ _run(app(scope, 'receive', AsyncMock()))
89
+ mock_server.handle_request.mock.assert_not_called()
47
90
 
48
91
  def test_other_app_routing(self):
49
92
  other_app = AsyncMock()
@@ -659,6 +659,32 @@ class TestAsyncServer(unittest.TestCase):
659
659
  assert len(s.sockets) == 0
660
660
  assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
661
661
 
662
+ @mock.patch('importlib.import_module')
663
+ def test_get_request_bad_websocket_transport(self, import_module):
664
+ a = self.get_async_mock(
665
+ {'REQUEST_METHOD': 'GET',
666
+ 'QUERY_STRING': 'EIO=4&transport=websocket&sid=foo'}
667
+ )
668
+ import_module.side_effect = [a]
669
+ s = async_server.AsyncServer()
670
+ s.sockets['foo'] = mock_socket = self._get_mock_socket()
671
+ mock_socket.upgraded = False
672
+ _run(s.handle_request('request'))
673
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
674
+
675
+ @mock.patch('importlib.import_module')
676
+ def test_get_request_bad_polling_transport(self, import_module):
677
+ a = self.get_async_mock(
678
+ {'REQUEST_METHOD': 'GET',
679
+ 'QUERY_STRING': 'EIO=4&transport=polling&sid=foo'}
680
+ )
681
+ import_module.side_effect = [a]
682
+ s = async_server.AsyncServer()
683
+ s.sockets['foo'] = mock_socket = self._get_mock_socket()
684
+ mock_socket.upgraded = True
685
+ _run(s.handle_request('request'))
686
+ assert a._async['make_response'].call_args[0][0] == '400 BAD REQUEST'
687
+
662
688
  @mock.patch('importlib.import_module')
663
689
  def test_post_request_with_bad_sid(self, import_module):
664
690
  a = self.get_async_mock(
@@ -111,6 +111,14 @@ class TestServer(unittest.TestCase):
111
111
  del sys.modules['engineio.async_drivers.threading']
112
112
 
113
113
  def test_async_mode_eventlet(self):
114
+ sys.modules['eventlet'] = mock.MagicMock()
115
+ sys.modules['eventlet'].green = mock.MagicMock()
116
+ sys.modules['eventlet.green'] = sys.modules['eventlet'].green
117
+ sys.modules['eventlet.green'].threading = mock.MagicMock()
118
+ sys.modules['eventlet.green.threading'] = \
119
+ sys.modules['eventlet.green'].threading
120
+ sys.modules['eventlet'].websocket = mock.MagicMock()
121
+ sys.modules['eventlet.websocket'] = sys.modules['eventlet'].websocket
114
122
  s = server.Server(async_mode='eventlet')
115
123
  assert s.async_mode == 'eventlet'
116
124
 
@@ -120,6 +128,11 @@ class TestServer(unittest.TestCase):
120
128
  assert s._async['thread'] == async_eventlet.EventletThread
121
129
  assert s._async['queue'] == queue.Queue
122
130
  assert s._async['websocket'] == async_eventlet.WebSocketWSGI
131
+ del sys.modules['eventlet']
132
+ del sys.modules['eventlet.green']
133
+ del sys.modules['eventlet.green.threading']
134
+ del sys.modules['eventlet.websocket']
135
+ del sys.modules['engineio.async_drivers.eventlet']
123
136
 
124
137
  @mock.patch('importlib.import_module', side_effect=_mock_import)
125
138
  def test_async_mode_gevent_uwsgi(self, import_module):
@@ -228,7 +241,10 @@ class TestServer(unittest.TestCase):
228
241
  with pytest.raises(ValueError):
229
242
  server.Server(async_mode='foo')
230
243
 
231
- @mock.patch('importlib.import_module', side_effect=[_mock_async])
244
+ @mock.patch(
245
+ 'importlib.import_module',
246
+ side_effect=[_mock_async],
247
+ )
232
248
  def test_async_mode_auto_eventlet(self, import_module):
233
249
  s = server.Server()
234
250
  assert s.async_mode == 'eventlet'
@@ -867,6 +883,28 @@ class TestServer(unittest.TestCase):
867
883
  assert start_response.call_args[0][0] == '400 BAD REQUEST'
868
884
  assert len(s.sockets) == 0
869
885
 
886
+ def test_get_request_bad_websocket_transport(self):
887
+ s = server.Server()
888
+ mock_socket = self._get_mock_socket()
889
+ mock_socket.upgraded = False
890
+ s.sockets['foo'] = mock_socket
891
+ environ = {'REQUEST_METHOD': 'GET',
892
+ 'QUERY_STRING': 'EIO=4&transport=websocket&sid=foo'}
893
+ start_response = mock.MagicMock()
894
+ s.handle_request(environ, start_response)
895
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
896
+
897
+ def test_get_request_bad_polling_transport(self):
898
+ s = server.Server()
899
+ mock_socket = self._get_mock_socket()
900
+ mock_socket.upgraded = True
901
+ s.sockets['foo'] = mock_socket
902
+ environ = {'REQUEST_METHOD': 'GET',
903
+ 'QUERY_STRING': 'EIO=4&transport=polling&sid=foo'}
904
+ start_response = mock.MagicMock()
905
+ s.handle_request(environ, start_response)
906
+ assert start_response.call_args[0][0] == '400 BAD REQUEST'
907
+
870
908
  def test_post_request(self):
871
909
  s = server.Server()
872
910
  mock_socket = self._get_mock_socket()
@@ -1,5 +1,5 @@
1
1
  [tox]
2
- envlist=flake8,py38,py39,py310,py311,py312,pypy3,docs
2
+ envlist=flake8,py38,py39,py310,py311,py312,py313,pypy3,docs
3
3
  skip_missing_interpreters=True
4
4
 
5
5
  [gh-actions]
@@ -9,6 +9,7 @@ python =
9
9
  3.10: py310
10
10
  3.11: py311
11
11
  3.12: py312
12
+ 3.13: py313
12
13
  pypy-3: pypy3
13
14
 
14
15
  [testenv]
@@ -18,7 +19,6 @@ commands=
18
19
  deps=
19
20
  pytest
20
21
  pytest-cov
21
- eventlet
22
22
  aiohttp
23
23
  tornado
24
24
  requests
@@ -1,29 +0,0 @@
1
- import logging
2
- import unittest
3
- from unittest import mock
4
-
5
- from engineio.async_drivers import eventlet as async_eventlet
6
- import pytest
7
-
8
-
9
- class TestAsyncEventlet(unittest.TestCase):
10
- def setUp(self):
11
- logging.getLogger('engineio').setLevel(logging.NOTSET)
12
-
13
- def test_bad_environ(self):
14
- wsgi = async_eventlet.WebSocketWSGI(None, mock.MagicMock())
15
- environ = {'foo': 'bar'}
16
- start_response = 'bar'
17
- with pytest.raises(RuntimeError):
18
- wsgi(environ, start_response)
19
-
20
- @mock.patch(
21
- 'engineio.async_drivers.eventlet._WebSocketWSGI.__call__',
22
- return_value='data',
23
- )
24
- def test_wsgi_call(self, _WebSocketWSGI):
25
- _WebSocketWSGI.__call__ = lambda e, s: 'data'
26
- environ = {'eventlet.input': mock.MagicMock()}
27
- start_response = 'bar'
28
- wsgi = async_eventlet.WebSocketWSGI(None, mock.MagicMock())
29
- assert wsgi(environ, start_response) == 'data'